diff options
239 files changed, 48987 insertions, 8993 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0298939 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,260 @@ +name: build + +on: + push: + schedule: + - cron: '0 0 1 * *' + +jobs: + build-linux-ubuntu: + runs-on: ubuntu-latest + steps: + - name: install dependencies + run: | + sudo apt-get update + pip install cython + - name: prepare environment + run: | + echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV + - name: fetch libplist + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libplist-latest_${{env.target_triplet}} + repo: libimobiledevice/libplist + - name: fetch libusbmuxd + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libusbmuxd-latest_${{env.target_triplet}} + repo: libimobiledevice/libusbmuxd + - name: fetch libimobiledevice-glue + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libimobiledevice-glue-latest_${{env.target_triplet}} + repo: libimobiledevice/libimobiledevice-glue + - name: fetch libtatsu + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libtatsu-latest_${{env.target_triplet}} + repo: libimobiledevice/libtatsu + - name: install external dependencies + run: | + mkdir extract + for I in *.tar; do + tar -C extract -xvf $I + done + sudo cp -r extract/* / + sudo ldconfig + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: autogen + run: ./autogen.sh PKG_CONFIG_PATH=/usr/local/lib/pkgconfig LDFLAGS="-Wl,-rpath=/usr/local/lib" --enable-debug + - name: make + run: make + - name: make install + run: sudo make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libimobiledevice.tar usr + - name: publish artifact + uses: actions/upload-artifact@v4 + with: + name: libimobiledevice-latest_${{env.target_triplet}} + path: libimobiledevice.tar + build-macOS: + runs-on: macOS-latest + steps: + - name: install dependencies + run: | + if test -x "`which port`"; then + sudo port install libtool autoconf automake pkgconfig + else + brew install libtool autoconf automake pkgconfig + fi + pip3 install --break-system-packages cython + shell: bash + - name: fetch libplist + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libplist-latest_macOS + repo: libimobiledevice/libplist + - name: fetch libusbmuxd + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libusbmuxd-latest_macOS + repo: libimobiledevice/libusbmuxd + - name: fetch libimobiledevice-glue + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libimobiledevice-glue-latest_macOS + repo: libimobiledevice/libimobiledevice-glue + - name: fetch libtatsu + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libtatsu-latest_macOS + repo: libimobiledevice/libtatsu + - name: install external dependencies + run: | + mkdir extract + for I in *.tar; do + tar -C extract -xvf $I + done + sudo cp -r extract/* / + - uses: actions/checkout@v4 + - name: install additional requirements + run: | + mkdir -p lib + curl -o lib/libcrypto.35.tbd -Ls \ + https://gist.github.com/nikias/94c99fd145a75a5104415e5117b0cafa/raw/5209dfbff5a871a14272afe4794e76eb4cf6f062/libcrypto.35.tbd + curl -o lib/libssl.35.tbd -Ls \ + https://gist.github.com/nikias/94c99fd145a75a5104415e5117b0cafa/raw/5209dfbff5a871a14272afe4794e76eb4cf6f062/libssl.35.tbd + echo "LIBSSL=`pwd`/lib/libssl.35.tbd" >> $GITHUB_ENV + echo "LIBCRYPTO=`pwd`/lib/libcrypto.35.tbd" >> $GITHUB_ENV + LIBRESSL_VER=2.2.7 + echo "LIBRESSL_VER=$LIBRESSL_VER" >> $GITHUB_ENV + FILENAME="libressl-$LIBRESSL_VER.tar.gz" + curl -o $FILENAME -Ls "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/$FILENAME" + mkdir -p deps + tar -C deps -xzf $FILENAME + echo "DEPSDIR=`pwd`/deps" >> $GITHUB_ENV + - name: autogen + run: | + SDKDIR=`xcrun --sdk macosx --show-sdk-path` + TESTARCHS="arm64 x86_64" + USEARCHS= + for ARCH in $TESTARCHS; do + if echo "int main(int argc, char **argv) { return 0; }" |clang -arch $ARCH -o /dev/null -isysroot $SDKDIR -x c - 2>/dev/null; then + USEARCHS="$USEARCHS -arch $ARCH" + fi + done + export CFLAGS="$USEARCHS -isysroot $SDKDIR" + echo "Using CFLAGS: $CFLAGS" + PYTHON3_BIN=`xcrun -f python3` + if test -x $PYTHON3_BIN; then + export PYTHON=$PYTHON3_BIN + PYTHON_VER=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('VERSION'))"` + PYTHON_EXEC_PREFIX=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('exec_prefix'))"` + PYTHON_LIBS_PATH=$PYTHON_EXEC_PREFIX/lib + PYTHON_FRAMEWORK_PATH=$PYTHON_EXEC_PREFIX/Python3 + export PYTHON_LIBS="-L$PYTHON_LIBS_PATH -lpython$PYTHON_VER" + export PYTHON_EXTRA_LDFLAGS="-Wl,-stack_size,1000000 -framework CoreFoundation $PYTHON_FRAMEWORK_PATH" + fi + ./autogen.sh PKG_CONFIG_PATH=/usr/local/lib/pkgconfig --enable-debug \ + openssl_CFLAGS="-I${{ env.DEPSDIR }}/libressl-${{ env.LIBRESSL_VER }}/include" \ + openssl_LIBS="-Xlinker ${{ env.LIBSSL }} -Xlinker ${{ env.LIBCRYPTO }}" + - name: make + run: make + - name: make install + run: sudo make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libimobiledevice.tar usr + - name: publish artifact + uses: actions/upload-artifact@v4 + with: + name: libimobiledevice-latest_macOS + path: libimobiledevice.tar + build-windows: + runs-on: windows-2019 + defaults: + run: + shell: msys2 {0} + strategy: + fail-fast: false + matrix: + include: [ + { msystem: MINGW64, arch: x86_64 }, + { msystem: MINGW32, arch: i686 } + ] + steps: + - uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + release: false + update: false + install: >- + base-devel + git + mingw-w64-${{ matrix.arch }}-gcc + make + libtool + autoconf + automake-wrapper + - name: prepare environment + run: | + dest=`echo ${{ matrix.msystem }} |tr [:upper:] [:lower:]` + echo "dest=$dest" >> $GITHUB_ENV + echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV + git config --global core.autocrlf false + - name: fetch libplist + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libplist-latest_${{ matrix.arch }}-${{ env.dest }} + repo: libimobiledevice/libplist + - name: fetch libusbmuxd + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libusbmuxd-latest_${{ matrix.arch }}-${{ env.dest }} + repo: libimobiledevice/libusbmuxd + - name: fetch libimobiledevice-glue + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libimobiledevice-glue-latest_${{ matrix.arch }}-${{ env.dest }} + repo: libimobiledevice/libimobiledevice-glue + - name: fetch libtatsu + uses: dawidd6/action-download-artifact@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + name: libtatsu-latest_${{ matrix.arch }}-${{ env.dest }} + repo: libimobiledevice/libtatsu + - name: install external dependencies + run: | + mkdir extract + for I in *.tar; do + tar -C extract -xvf $I + done + cp -r extract/* / + - uses: actions/checkout@v4 + - name: autogen + run: ./autogen.sh CC=gcc CXX=g++ --enable-debug + - name: make + run: make + - name: make install + run: make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libimobiledevice.tar ${{ env.dest }} + - name: publish artifact + uses: actions/upload-artifact@v4 + with: + name: libimobiledevice-latest_${{ matrix.arch }}-${{ env.dest }} + path: libimobiledevice.tar @@ -35,17 +35,10 @@ stamp-h1 src/.libs docs/html libimobiledevice-1.0.pc -dev/.libs/* -dev/afccheck -dev/ideviceclient -dev/idevicesyslog -dev/lckd-client -dev/msyncclient -dev/ideviceenterrecovery tools/.libs/* -tools/idevice_id -tools/ideviceinfo -tools/idevicesyslog -tools/idevicebackup -tools/ideviceimagemounter -tools/idevicescreenshot +tools/idevice* +tools/afcclient +!tools/idevice*.[ch] +cython/.libs/* +cython/*.c +doxygen.cfg diff --git a/3rd_party/Makefile.am b/3rd_party/Makefile.am new file mode 100644 index 0000000..a196ea3 --- /dev/null +++ b/3rd_party/Makefile.am @@ -0,0 +1,6 @@ +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 +SUBDIRS = +if HAVE_WIRELESS_PAIRING +SUBDIRS += ed25519 libsrp6a-sha512 +endif diff --git a/3rd_party/README.md b/3rd_party/README.md new file mode 100644 index 0000000..0bec640 --- /dev/null +++ b/3rd_party/README.md @@ -0,0 +1,36 @@ +# Third party components/libraries + +This folder contains third party components or libraries that are used +within the libimobiledevice project. They have been bundled since they +are either not readily available on the intended target platforms and/or +have been modified. + +Their respective licenses are provided in each corresponding folder in a +file called LICENSE. + + +## ed25519 + +Source: https://github.com/orlp/ed25519 +Based on commit 7fa6712ef5d581a6981ec2b08ee623314cd1d1c4. +[LICENCE](ed25519/LICENSE) + +The original source has not been modified, except that the file `test.c` +and the contained DLL files have been removed. To allow building within +libimobiledevice, a `Makefile.am` has been added. + + +## libsrp6a-sha512 + +Source: https://github.com/secure-remote-password/stanford-srp +Based on commit 587900d32777348f98477cb25123d5761fbe3725. +[LICENCE](libsrp6a-sha512/LICENSE) + +For the usage within libimobiledevice, only [libsrp](https://github.com/secure-remote-password/stanford-srp/tree/master/libsrp) +has been used as a basis. +It has been adapted to the needs of the libimobiledevice project, and +contains just a part of the original code; it only supports the SRP6a +client method which has been modified to use SHA512 instead of SHA1, +hence the name was changed to `libsrp6a-sha512`. +More details about the modifications can be found in [libsrp6a-sha512/README.md](libsrp6a-sha512/README.md). + diff --git a/3rd_party/ed25519/LICENSE b/3rd_party/ed25519/LICENSE new file mode 100644 index 0000000..c1503f9 --- /dev/null +++ b/3rd_party/ed25519/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) 2015 Orson Peters <orsonpeters@gmail.com> + +This software is provided 'as-is', without any express or implied warranty. In no event will the +authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial +applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the + original software. If you use this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as + being the original software. + +3. This notice may not be removed or altered from any source distribution. diff --git a/3rd_party/ed25519/Makefile.am b/3rd_party/ed25519/Makefile.am new file mode 100644 index 0000000..d8e4e04 --- /dev/null +++ b/3rd_party/ed25519/Makefile.am @@ -0,0 +1,26 @@ +AUTOMAKE_OPTIONS = foreign no-dependencies + +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) + +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + $(ssl_lib_CFLAGS) + +AM_LDFLAGS = + +noinst_LTLIBRARIES = libed25519.la +libed25519_la_LIBADD = +libed25519_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined +libed25519_la_SOURCES = \ + add_scalar.c \ + fe.c \ + ge.c \ + keypair.c \ + key_exchange.c \ + sc.c \ + seed.c \ + sign.c \ + sha512.c \ + verify.c diff --git a/3rd_party/ed25519/README.md b/3rd_party/ed25519/README.md new file mode 100644 index 0000000..2c431c2 --- /dev/null +++ b/3rd_party/ed25519/README.md @@ -0,0 +1,165 @@ +Ed25519 +======= + +This is a portable implementation of [Ed25519](http://ed25519.cr.yp.to/) based +on the SUPERCOP "ref10" implementation. Additionally there is key exchanging +and scalar addition included to further aid building a PKI using Ed25519. All +code is licensed under the permissive zlib license. + +All code is pure ANSI C without any dependencies, except for the random seed +generation which uses standard OS cryptography APIs (`CryptGenRandom` on +Windows, `/dev/urandom` on nix). If you wish to be entirely portable define +`ED25519_NO_SEED`. This disables the `ed25519_create_seed` function, so if your +application requires key generation you must supply your own seeding function +(which is simply a 256 bit (32 byte) cryptographic random number generator). + + +Performance +----------- + +On a Windows machine with an Intel Pentium B970 @ 2.3GHz I got the following +speeds (running on only one a single core): + + Seed generation: 64us (15625 per second) + Key generation: 88us (11364 per second) + Message signing (short message): 87us (11494 per second) + Message verifying (short message): 228us (4386 per second) + Scalar addition: 100us (10000 per second) + Key exchange: 220us (4545 per second) + +The speeds on other machines may vary. Sign/verify times will be higher with +longer messages. The implementation significantly benefits from 64 bit +architectures, if possible compile as 64 bit. + + +Usage +----- + +Simply add all .c and .h files in the `src/` folder to your project and include +`ed25519.h` in any file you want to use the API. If you prefer to use a shared +library, only copy `ed25519.h` and define `ED25519_DLL` before importing. + +There are no defined types for seeds, private keys, public keys, shared secrets +or signatures. Instead simple `unsigned char` buffers are used with the +following sizes: + +```c +unsigned char seed[32]; +unsigned char signature[64]; +unsigned char public_key[32]; +unsigned char private_key[64]; +unsigned char scalar[32]; +unsigned char shared_secret[32]; +``` + +API +--- + +```c +int ed25519_create_seed(unsigned char *seed); +``` + +Creates a 32 byte random seed in `seed` for key generation. `seed` must be a +writable 32 byte buffer. Returns 0 on success, and nonzero on failure. + +```c +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, + const unsigned char *seed); +``` + +Creates a new key pair from the given seed. `public_key` must be a writable 32 +byte buffer, `private_key` must be a writable 64 byte buffer and `seed` must be +a 32 byte buffer. + +```c +void ed25519_sign(unsigned char *signature, + const unsigned char *message, size_t message_len, + const unsigned char *public_key, const unsigned char *private_key); +``` + +Creates a signature of the given message with the given key pair. `signature` +must be a writable 64 byte buffer. `message` must have at least `message_len` +bytes to be read. + +```c +int ed25519_verify(const unsigned char *signature, + const unsigned char *message, size_t message_len, + const unsigned char *public_key); +``` + +Verifies the signature on the given message using `public_key`. `signature` +must be a readable 64 byte buffer. `message` must have at least `message_len` +bytes to be read. Returns 1 if the signature matches, 0 otherwise. + +```c +void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, + const unsigned char *scalar); +``` + +Adds `scalar` to the given key pair where scalar is a 32 byte buffer (possibly +generated with `ed25519_create_seed`), generating a new key pair. You can +calculate the public key sum without knowing the private key and vice versa by +passing in `NULL` for the key you don't know. This is useful for enforcing +randomness on a key pair by a third party while only knowing the public key, +among other things. Warning: the last bit of the scalar is ignored - if +comparing scalars make sure to clear it with `scalar[31] &= 127`. + + +```c +void ed25519_key_exchange(unsigned char *shared_secret, + const unsigned char *public_key, const unsigned char *private_key); +``` + +Performs a key exchange on the given public key and private key, producing a +shared secret. It is recommended to hash the shared secret before using it. +`shared_secret` must be a 32 byte writable buffer where the shared secret will +be stored. + +Example +------- + +```c +unsigned char seed[32], public_key[32], private_key[64], signature[64]; +unsigned char other_public_key[32], other_private_key[64], shared_secret[32]; +const unsigned char message[] = "TEST MESSAGE"; + +/* create a random seed, and a key pair out of that seed */ +if (ed25519_create_seed(seed)) { + printf("error while generating seed\n"); + exit(1); +} + +ed25519_create_keypair(public_key, private_key, seed); + +/* create signature on the message with the key pair */ +ed25519_sign(signature, message, strlen(message), public_key, private_key); + +/* verify the signature */ +if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("valid signature\n"); +} else { + printf("invalid signature\n"); +} + +/* create a dummy keypair to use for a key exchange, normally you'd only have +the public key and receive it through some communication channel */ +if (ed25519_create_seed(seed)) { + printf("error while generating seed\n"); + exit(1); +} + +ed25519_create_keypair(other_public_key, other_private_key, seed); + +/* do a key exchange with other_public_key */ +ed25519_key_exchange(shared_secret, other_public_key, private_key); + +/* + the magic here is that ed25519_key_exchange(shared_secret, public_key, + other_private_key); would result in the same shared_secret +*/ + +``` + +License +------- +All code is released under the zlib license. See LICENSE for details. diff --git a/3rd_party/ed25519/add_scalar.c b/3rd_party/ed25519/add_scalar.c new file mode 100644 index 0000000..7528a7a --- /dev/null +++ b/3rd_party/ed25519/add_scalar.c @@ -0,0 +1,69 @@ +#include "ed25519.h" +#include "ge.h" +#include "sc.h" +#include "sha512.h" + + +/* see http://crypto.stackexchange.com/a/6215/4697 */ +void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar) { + const unsigned char SC_1[32] = {1}; /* scalar with value 1 */ + + unsigned char n[32]; + ge_p3 nB; + ge_p1p1 A_p1p1; + ge_p3 A; + ge_p3 public_key_unpacked; + ge_cached T; + + sha512_context hash; + unsigned char hashbuf[64]; + + int i; + + /* copy the scalar and clear highest bit */ + for (i = 0; i < 31; ++i) { + n[i] = scalar[i]; + } + n[31] = scalar[31] & 127; + + /* private key: a = n + t */ + if (private_key) { + sc_muladd(private_key, SC_1, n, private_key); + + // https://github.com/orlp/ed25519/issues/3 + sha512_init(&hash); + sha512_update(&hash, private_key + 32, 32); + sha512_update(&hash, scalar, 32); + sha512_final(&hash, hashbuf); + for (i = 0; i < 32; ++i) { + private_key[32 + i] = hashbuf[i]; + } + } + + /* public key: A = nB + T */ + if (public_key) { + /* if we know the private key we don't need a point addition, which is faster */ + /* using a "timing attack" you could find out wether or not we know the private + key, but this information seems rather useless - if this is important pass + public_key and private_key seperately in 2 function calls */ + if (private_key) { + ge_scalarmult_base(&A, private_key); + } else { + /* unpack public key into T */ + ge_frombytes_negate_vartime(&public_key_unpacked, public_key); + fe_neg(public_key_unpacked.X, public_key_unpacked.X); /* undo negate */ + fe_neg(public_key_unpacked.T, public_key_unpacked.T); /* undo negate */ + ge_p3_to_cached(&T, &public_key_unpacked); + + /* calculate n*B */ + ge_scalarmult_base(&nB, n); + + /* A = n*B + T */ + ge_add(&A_p1p1, &nB, &T); + ge_p1p1_to_p3(&A, &A_p1p1); + } + + /* pack public key */ + ge_p3_tobytes(public_key, &A); + } +} diff --git a/3rd_party/ed25519/ed25519.h b/3rd_party/ed25519/ed25519.h new file mode 100644 index 0000000..8924659 --- /dev/null +++ b/3rd_party/ed25519/ed25519.h @@ -0,0 +1,38 @@ +#ifndef ED25519_H +#define ED25519_H + +#include <stddef.h> + +#if defined(_WIN32) + #if defined(ED25519_BUILD_DLL) + #define ED25519_DECLSPEC __declspec(dllexport) + #elif defined(ED25519_DLL) + #define ED25519_DECLSPEC __declspec(dllimport) + #else + #define ED25519_DECLSPEC + #endif +#else + #define ED25519_DECLSPEC +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ED25519_NO_SEED +int ED25519_DECLSPEC ed25519_create_seed(unsigned char *seed); +#endif + +void ED25519_DECLSPEC ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +void ED25519_DECLSPEC ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key); +int ED25519_DECLSPEC ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key); +void ED25519_DECLSPEC ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar); +void ED25519_DECLSPEC ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/3rd_party/ed25519/fe.c b/3rd_party/ed25519/fe.c new file mode 100644 index 0000000..2105eb7 --- /dev/null +++ b/3rd_party/ed25519/fe.c @@ -0,0 +1,1491 @@ +#include "fixedint.h" +#include "fe.h" + + +/* + helper functions +*/ +static uint64_t load_3(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + + return result; +} + +static uint64_t load_4(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + result |= ((uint64_t) in[3]) << 24; + + return result; +} + + + +/* + h = 0 +*/ + +void fe_0(fe h) { + h[0] = 0; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = 1 +*/ + +void fe_1(fe h) { + h[0] = 1; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = f + g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + + Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_add(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 + g0; + int32_t h1 = f1 + g1; + int32_t h2 = f2 + g2; + int32_t h3 = f3 + g3; + int32_t h4 = f4 + g4; + int32_t h5 = f5 + g5; + int32_t h6 = f6 + g6; + int32_t h7 = f7 + g7; + int32_t h8 = f8 + g8; + int32_t h9 = f9 + g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* + Replace (f,g) with (g,g) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cmov(fe f, const fe g, unsigned int b) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t x0 = f0 ^ g0; + int32_t x1 = f1 ^ g1; + int32_t x2 = f2 ^ g2; + int32_t x3 = f3 ^ g3; + int32_t x4 = f4 ^ g4; + int32_t x5 = f5 ^ g5; + int32_t x6 = f6 ^ g6; + int32_t x7 = f7 ^ g7; + int32_t x8 = f8 ^ g8; + int32_t x9 = f9 ^ g9; + + b = (unsigned int) (- (int) b); /* silence warning */ + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; +} + +/* + Replace (f,g) with (g,f) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cswap(fe f,fe g,unsigned int b) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t x0 = f0 ^ g0; + int32_t x1 = f1 ^ g1; + int32_t x2 = f2 ^ g2; + int32_t x3 = f3 ^ g3; + int32_t x4 = f4 ^ g4; + int32_t x5 = f5 ^ g5; + int32_t x6 = f6 ^ g6; + int32_t x7 = f7 ^ g7; + int32_t x8 = f8 ^ g8; + int32_t x9 = f9 ^ g9; + b = (unsigned int) (- (int) b); /* silence warning */ + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; + g[0] = g0 ^ x0; + g[1] = g1 ^ x1; + g[2] = g2 ^ x2; + g[3] = g3 ^ x3; + g[4] = g4 ^ x4; + g[5] = g5 ^ x5; + g[6] = g6 ^ x6; + g[7] = g7 ^ x7; + g[8] = g8 ^ x8; + g[9] = g9 ^ x9; +} + + + +/* + h = f +*/ + +void fe_copy(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + + h[0] = f0; + h[1] = f1; + h[2] = f2; + h[3] = f3; + h[4] = f4; + h[5] = f5; + h[6] = f6; + h[7] = f7; + h[8] = f8; + h[9] = f9; +} + + + +/* + Ignores top bit of h. +*/ + +void fe_frombytes(fe h, const unsigned char *s) { + int64_t h0 = load_4(s); + int64_t h1 = load_3(s + 4) << 6; + int64_t h2 = load_3(s + 7) << 5; + int64_t h3 = load_3(s + 10) << 3; + int64_t h4 = load_3(s + 13) << 2; + int64_t h5 = load_4(s + 16); + int64_t h6 = load_3(s + 20) << 7; + int64_t h7 = load_3(s + 23) << 5; + int64_t h8 = load_3(s + 26) << 4; + int64_t h9 = (load_3(s + 29) & 8388607) << 2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + + +void fe_invert(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + fe t3; + int i; + + fe_sq(t0, z); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_sq(t1, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t2, t0); + + for (i = 1; i < 1; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t1, t2); + fe_sq(t2, t1); + + for (i = 1; i < 5; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for (i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for (i = 1; i < 20; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for (i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for (i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for (i = 1; i < 100; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for (i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(out, t1, t0); +} + + + +/* + return 1 if f is in {1,3,5,...,q-2} + return 0 if f is in {0,2,4,...,q-1} + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnegative(const fe f) { + unsigned char s[32]; + + fe_tobytes(s, f); + + return s[0] & 1; +} + + + +/* + return 1 if f == 0 + return 0 if f != 0 + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnonzero(const fe f) { + unsigned char s[32]; + unsigned char r; + + fe_tobytes(s, f); + + r = s[0]; + #define F(i) r |= s[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); + #undef F + + return r != 0; +} + + + +/* + h = f * g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + + Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. + */ + + /* + Notes on implementation strategy: + + Using schoolbook multiplication. + Karatsuba would save a little in some cost models. + + Most multiplications by 2 and 19 are 32-bit precomputations; + cheaper than 64-bit postcomputations. + + There is one remaining multiplication by 19 in the carry chain; + one *19 precomputation can be merged into this, + but the resulting data flow is considerably less clean. + + There are 12 carries below. + 10 of them are 2-way parallelizable and vectorizable. + Can get away with 11 carries, but then data flow is much deeper. + + With tighter constraints on inputs can squeeze carries into int32. +*/ + +void fe_mul(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */ + int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ + int32_t g3_19 = 19 * g3; + int32_t g4_19 = 19 * g4; + int32_t g5_19 = 19 * g5; + int32_t g6_19 = 19 * g6; + int32_t g7_19 = 19 * g7; + int32_t g8_19 = 19 * g8; + int32_t g9_19 = 19 * g9; + int32_t f1_2 = 2 * f1; + int32_t f3_2 = 2 * f3; + int32_t f5_2 = 2 * f5; + int32_t f7_2 = 2 * f7; + int32_t f9_2 = 2 * f9; + int64_t f0g0 = f0 * (int64_t) g0; + int64_t f0g1 = f0 * (int64_t) g1; + int64_t f0g2 = f0 * (int64_t) g2; + int64_t f0g3 = f0 * (int64_t) g3; + int64_t f0g4 = f0 * (int64_t) g4; + int64_t f0g5 = f0 * (int64_t) g5; + int64_t f0g6 = f0 * (int64_t) g6; + int64_t f0g7 = f0 * (int64_t) g7; + int64_t f0g8 = f0 * (int64_t) g8; + int64_t f0g9 = f0 * (int64_t) g9; + int64_t f1g0 = f1 * (int64_t) g0; + int64_t f1g1_2 = f1_2 * (int64_t) g1; + int64_t f1g2 = f1 * (int64_t) g2; + int64_t f1g3_2 = f1_2 * (int64_t) g3; + int64_t f1g4 = f1 * (int64_t) g4; + int64_t f1g5_2 = f1_2 * (int64_t) g5; + int64_t f1g6 = f1 * (int64_t) g6; + int64_t f1g7_2 = f1_2 * (int64_t) g7; + int64_t f1g8 = f1 * (int64_t) g8; + int64_t f1g9_38 = f1_2 * (int64_t) g9_19; + int64_t f2g0 = f2 * (int64_t) g0; + int64_t f2g1 = f2 * (int64_t) g1; + int64_t f2g2 = f2 * (int64_t) g2; + int64_t f2g3 = f2 * (int64_t) g3; + int64_t f2g4 = f2 * (int64_t) g4; + int64_t f2g5 = f2 * (int64_t) g5; + int64_t f2g6 = f2 * (int64_t) g6; + int64_t f2g7 = f2 * (int64_t) g7; + int64_t f2g8_19 = f2 * (int64_t) g8_19; + int64_t f2g9_19 = f2 * (int64_t) g9_19; + int64_t f3g0 = f3 * (int64_t) g0; + int64_t f3g1_2 = f3_2 * (int64_t) g1; + int64_t f3g2 = f3 * (int64_t) g2; + int64_t f3g3_2 = f3_2 * (int64_t) g3; + int64_t f3g4 = f3 * (int64_t) g4; + int64_t f3g5_2 = f3_2 * (int64_t) g5; + int64_t f3g6 = f3 * (int64_t) g6; + int64_t f3g7_38 = f3_2 * (int64_t) g7_19; + int64_t f3g8_19 = f3 * (int64_t) g8_19; + int64_t f3g9_38 = f3_2 * (int64_t) g9_19; + int64_t f4g0 = f4 * (int64_t) g0; + int64_t f4g1 = f4 * (int64_t) g1; + int64_t f4g2 = f4 * (int64_t) g2; + int64_t f4g3 = f4 * (int64_t) g3; + int64_t f4g4 = f4 * (int64_t) g4; + int64_t f4g5 = f4 * (int64_t) g5; + int64_t f4g6_19 = f4 * (int64_t) g6_19; + int64_t f4g7_19 = f4 * (int64_t) g7_19; + int64_t f4g8_19 = f4 * (int64_t) g8_19; + int64_t f4g9_19 = f4 * (int64_t) g9_19; + int64_t f5g0 = f5 * (int64_t) g0; + int64_t f5g1_2 = f5_2 * (int64_t) g1; + int64_t f5g2 = f5 * (int64_t) g2; + int64_t f5g3_2 = f5_2 * (int64_t) g3; + int64_t f5g4 = f5 * (int64_t) g4; + int64_t f5g5_38 = f5_2 * (int64_t) g5_19; + int64_t f5g6_19 = f5 * (int64_t) g6_19; + int64_t f5g7_38 = f5_2 * (int64_t) g7_19; + int64_t f5g8_19 = f5 * (int64_t) g8_19; + int64_t f5g9_38 = f5_2 * (int64_t) g9_19; + int64_t f6g0 = f6 * (int64_t) g0; + int64_t f6g1 = f6 * (int64_t) g1; + int64_t f6g2 = f6 * (int64_t) g2; + int64_t f6g3 = f6 * (int64_t) g3; + int64_t f6g4_19 = f6 * (int64_t) g4_19; + int64_t f6g5_19 = f6 * (int64_t) g5_19; + int64_t f6g6_19 = f6 * (int64_t) g6_19; + int64_t f6g7_19 = f6 * (int64_t) g7_19; + int64_t f6g8_19 = f6 * (int64_t) g8_19; + int64_t f6g9_19 = f6 * (int64_t) g9_19; + int64_t f7g0 = f7 * (int64_t) g0; + int64_t f7g1_2 = f7_2 * (int64_t) g1; + int64_t f7g2 = f7 * (int64_t) g2; + int64_t f7g3_38 = f7_2 * (int64_t) g3_19; + int64_t f7g4_19 = f7 * (int64_t) g4_19; + int64_t f7g5_38 = f7_2 * (int64_t) g5_19; + int64_t f7g6_19 = f7 * (int64_t) g6_19; + int64_t f7g7_38 = f7_2 * (int64_t) g7_19; + int64_t f7g8_19 = f7 * (int64_t) g8_19; + int64_t f7g9_38 = f7_2 * (int64_t) g9_19; + int64_t f8g0 = f8 * (int64_t) g0; + int64_t f8g1 = f8 * (int64_t) g1; + int64_t f8g2_19 = f8 * (int64_t) g2_19; + int64_t f8g3_19 = f8 * (int64_t) g3_19; + int64_t f8g4_19 = f8 * (int64_t) g4_19; + int64_t f8g5_19 = f8 * (int64_t) g5_19; + int64_t f8g6_19 = f8 * (int64_t) g6_19; + int64_t f8g7_19 = f8 * (int64_t) g7_19; + int64_t f8g8_19 = f8 * (int64_t) g8_19; + int64_t f8g9_19 = f8 * (int64_t) g9_19; + int64_t f9g0 = f9 * (int64_t) g0; + int64_t f9g1_38 = f9_2 * (int64_t) g1_19; + int64_t f9g2_19 = f9 * (int64_t) g2_19; + int64_t f9g3_38 = f9_2 * (int64_t) g3_19; + int64_t f9g4_19 = f9 * (int64_t) g4_19; + int64_t f9g5_38 = f9_2 * (int64_t) g5_19; + int64_t f9g6_19 = f9 * (int64_t) g6_19; + int64_t f9g7_38 = f9_2 * (int64_t) g7_19; + int64_t f9g8_19 = f9 * (int64_t) g8_19; + int64_t f9g9_38 = f9_2 * (int64_t) g9_19; + int64_t h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38; + int64_t h1 = f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19; + int64_t h2 = f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38; + int64_t h3 = f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19; + int64_t h4 = f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38; + int64_t h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19; + int64_t h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38; + int64_t h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19; + int64_t h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38; + int64_t h9 = f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0 ; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = f * 121666 +Can overlap h with f. + +Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_mul121666(fe h, fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int64_t h0 = f0 * (int64_t) 121666; + int64_t h1 = f1 * (int64_t) 121666; + int64_t h2 = f2 * (int64_t) 121666; + int64_t h3 = f3 * (int64_t) 121666; + int64_t h4 = f4 * (int64_t) 121666; + int64_t h5 = f5 * (int64_t) 121666; + int64_t h6 = f6 * (int64_t) 121666; + int64_t h7 = f7 * (int64_t) 121666; + int64_t h8 = f8 * (int64_t) 121666; + int64_t h9 = f9 * (int64_t) 121666; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = -f + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_neg(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t h0 = -f0; + int32_t h1 = -f1; + int32_t h2 = -f2; + int32_t h3 = -f3; + int32_t h4 = -f4; + int32_t h5 = -f5; + int32_t h6 = -f6; + int32_t h7 = -f7; + int32_t h8 = -f8; + int32_t h9 = -f9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + +void fe_pow22523(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + int i; + fe_sq(t0, z); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_sq(t1, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t0, t0); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for (i = 1; i < 20; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for (i = 1; i < 100; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t0, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t0, t0); + } + + fe_mul(out, t0, z); + return; +} + + +/* +h = f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = 2 * f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq2(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + h0 += h0; + h1 += h1; + h2 += h2; + h3 += h3; + h4 += h4; + h5 += h5; + h6 += h6; + h7 += h7; + h8 += h8; + h9 += h9; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry1 = (h1 + (int64_t) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry2 = (h2 + (int64_t) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry3 = (h3 + (int64_t) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry4 = (h4 + (int64_t) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = (h9 + (int64_t) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry0 = (h0 + (int64_t) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = f - g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_sub(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 - g0; + int32_t h1 = f1 - g1; + int32_t h2 = f2 - g2; + int32_t h3 = f3 - g3; + int32_t h4 = f4 - g4; + int32_t h5 = f5 - g5; + int32_t h6 = f6 - g6; + int32_t h7 = f7 - g7; + int32_t h8 = f8 - g8; + int32_t h9 = f9 - g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* +Preconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Write p=2^255-19; q=floor(h/p). +Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). + +Proof: + Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. + Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. + + Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). + Then 0<y<1. + + Write r=h-pq. + Have 0<=r<=p-1=2^255-20. + Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1. + + Write x=r+19(2^-255)r+y. + Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q. + + Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1)) + so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q. +*/ + +void fe_tobytes(unsigned char *s, const fe h) { + int32_t h0 = h[0]; + int32_t h1 = h[1]; + int32_t h2 = h[2]; + int32_t h3 = h[3]; + int32_t h4 = h[4]; + int32_t h5 = h[5]; + int32_t h6 = h[6]; + int32_t h7 = h[7]; + int32_t h8 = h[8]; + int32_t h9 = h[9]; + int32_t q; + int32_t carry0; + int32_t carry1; + int32_t carry2; + int32_t carry3; + int32_t carry4; + int32_t carry5; + int32_t carry6; + int32_t carry7; + int32_t carry8; + int32_t carry9; + q = (19 * h9 + (((int32_t) 1) << 24)) >> 25; + q = (h0 + q) >> 26; + q = (h1 + q) >> 25; + q = (h2 + q) >> 26; + q = (h3 + q) >> 25; + q = (h4 + q) >> 26; + q = (h5 + q) >> 25; + q = (h6 + q) >> 26; + q = (h7 + q) >> 25; + q = (h8 + q) >> 26; + q = (h9 + q) >> 25; + /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ + h0 += 19 * q; + /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ + carry0 = h0 >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry1 = h1 >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry2 = h2 >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry3 = h3 >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry4 = h4 >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry5 = h5 >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry6 = h6 >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry7 = h7 >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry8 = h8 >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = h9 >> 25; + h9 -= carry9 << 25; + + /* h10 = carry9 */ + /* + Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + Have h0+...+2^230 h9 between 0 and 2^255-1; + evidently 2^255 h10-2^255 q = 0. + Goal: Output h0+...+2^230 h9. + */ + s[0] = (unsigned char) (h0 >> 0); + s[1] = (unsigned char) (h0 >> 8); + s[2] = (unsigned char) (h0 >> 16); + s[3] = (unsigned char) ((h0 >> 24) | (h1 << 2)); + s[4] = (unsigned char) (h1 >> 6); + s[5] = (unsigned char) (h1 >> 14); + s[6] = (unsigned char) ((h1 >> 22) | (h2 << 3)); + s[7] = (unsigned char) (h2 >> 5); + s[8] = (unsigned char) (h2 >> 13); + s[9] = (unsigned char) ((h2 >> 21) | (h3 << 5)); + s[10] = (unsigned char) (h3 >> 3); + s[11] = (unsigned char) (h3 >> 11); + s[12] = (unsigned char) ((h3 >> 19) | (h4 << 6)); + s[13] = (unsigned char) (h4 >> 2); + s[14] = (unsigned char) (h4 >> 10); + s[15] = (unsigned char) (h4 >> 18); + s[16] = (unsigned char) (h5 >> 0); + s[17] = (unsigned char) (h5 >> 8); + s[18] = (unsigned char) (h5 >> 16); + s[19] = (unsigned char) ((h5 >> 24) | (h6 << 1)); + s[20] = (unsigned char) (h6 >> 7); + s[21] = (unsigned char) (h6 >> 15); + s[22] = (unsigned char) ((h6 >> 23) | (h7 << 3)); + s[23] = (unsigned char) (h7 >> 5); + s[24] = (unsigned char) (h7 >> 13); + s[25] = (unsigned char) ((h7 >> 21) | (h8 << 4)); + s[26] = (unsigned char) (h8 >> 4); + s[27] = (unsigned char) (h8 >> 12); + s[28] = (unsigned char) ((h8 >> 20) | (h9 << 6)); + s[29] = (unsigned char) (h9 >> 2); + s[30] = (unsigned char) (h9 >> 10); + s[31] = (unsigned char) (h9 >> 18); +} diff --git a/3rd_party/ed25519/fe.h b/3rd_party/ed25519/fe.h new file mode 100644 index 0000000..b4b62d2 --- /dev/null +++ b/3rd_party/ed25519/fe.h @@ -0,0 +1,41 @@ +#ifndef FE_H +#define FE_H + +#include "fixedint.h" + + +/* + fe means field element. + Here the field is \Z/(2^255-19). + An element t, entries t[0]...t[9], represents the integer + t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. + Bounds on each t[i] vary depending on context. +*/ + + +typedef int32_t fe[10]; + + +void fe_0(fe h); +void fe_1(fe h); + +void fe_frombytes(fe h, const unsigned char *s); +void fe_tobytes(unsigned char *s, const fe h); + +void fe_copy(fe h, const fe f); +int fe_isnegative(const fe f); +int fe_isnonzero(const fe f); +void fe_cmov(fe f, const fe g, unsigned int b); +void fe_cswap(fe f, fe g, unsigned int b); + +void fe_neg(fe h, const fe f); +void fe_add(fe h, const fe f, const fe g); +void fe_invert(fe out, const fe z); +void fe_sq(fe h, const fe f); +void fe_sq2(fe h, const fe f); +void fe_mul(fe h, const fe f, const fe g); +void fe_mul121666(fe h, fe f); +void fe_pow22523(fe out, const fe z); +void fe_sub(fe h, const fe f, const fe g); + +#endif diff --git a/3rd_party/ed25519/fixedint.h b/3rd_party/ed25519/fixedint.h new file mode 100644 index 0000000..1a8745b --- /dev/null +++ b/3rd_party/ed25519/fixedint.h @@ -0,0 +1,72 @@ +/* + Portable header to provide the 32 and 64 bits type. + + Not a compatible replacement for <stdint.h>, do not blindly use it as such. +*/ + +#if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__WATCOMC__) && (defined(_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_) || defined(__UINT_FAST64_TYPE__)) )) && !defined(FIXEDINT_H_INCLUDED) + #include <stdint.h> + #define FIXEDINT_H_INCLUDED + + #if defined(__WATCOMC__) && __WATCOMC__ >= 1250 && !defined(UINT64_C) + #include <limits.h> + #define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX)) + #endif +#endif + + +#ifndef FIXEDINT_H_INCLUDED + #define FIXEDINT_H_INCLUDED + + #include <limits.h> + + /* (u)int32_t */ + #ifndef uint32_t + #if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long uint32_t; + #elif (UINT_MAX == 0xffffffffUL) + typedef unsigned int uint32_t; + #elif (USHRT_MAX == 0xffffffffUL) + typedef unsigned short uint32_t; + #endif + #endif + + + #ifndef int32_t + #if (LONG_MAX == 0x7fffffffL) + typedef signed long int32_t; + #elif (INT_MAX == 0x7fffffffL) + typedef signed int int32_t; + #elif (SHRT_MAX == 0x7fffffffL) + typedef signed short int32_t; + #endif + #endif + + + /* (u)int64_t */ + #if (defined(__STDC__) && defined(__STDC_VERSION__) && __STDC__ && __STDC_VERSION__ >= 199901L) + typedef long long int64_t; + typedef unsigned long long uint64_t; + + #define UINT64_C(v) v ##ULL + #define INT64_C(v) v ##LL + #elif defined(__GNUC__) + __extension__ typedef long long int64_t; + __extension__ typedef unsigned long long uint64_t; + + #define UINT64_C(v) v ##ULL + #define INT64_C(v) v ##LL + #elif defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) || defined(__APPLE_CC__) || defined(_LONG_LONG) || defined(_CRAYC) + typedef long long int64_t; + typedef unsigned long long uint64_t; + + #define UINT64_C(v) v ##ULL + #define INT64_C(v) v ##LL + #elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || defined(__alpha) || defined(__DECC) + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; + + #define UINT64_C(v) v ##UI64 + #define INT64_C(v) v ##I64 + #endif +#endif diff --git a/3rd_party/ed25519/ge.c b/3rd_party/ed25519/ge.c new file mode 100644 index 0000000..87c691b --- /dev/null +++ b/3rd_party/ed25519/ge.c @@ -0,0 +1,467 @@ +#include "ge.h" +#include "precomp_data.h" + + +/* +r = p + q +*/ + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YplusX); + fe_mul(r->Y, r->Y, q->YminusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +static void slide(signed char *r, const unsigned char *a) { + int i; + int b; + int k; + + for (i = 0; i < 256; ++i) { + r[i] = 1 & (a[i >> 3] >> (i & 7)); + } + + for (i = 0; i < 256; ++i) + if (r[i]) { + for (b = 1; b <= 6 && i + b < 256; ++b) { + if (r[i + b]) { + if (r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; + r[i + b] = 0; + } else if (r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + + for (k = i + b; k < 256; ++k) { + if (!r[k]) { + r[k] = 1; + break; + } + + r[k] = 0; + } + } else { + break; + } + } + } + } +} + +/* +r = a * A + b * B +where a = a[0]+256*a[1]+...+256^31 a[31]. +and b = b[0]+256*b[1]+...+256^31 b[31]. +B is the Ed25519 base point (x,4/5) with x positive. +*/ + +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { + signed char aslide[256]; + signed char bslide[256]; + ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ + ge_p1p1 t; + ge_p3 u; + ge_p3 A2; + int i; + slide(aslide, a); + slide(bslide, b); + ge_p3_to_cached(&Ai[0], A); + ge_p3_dbl(&t, A); + ge_p1p1_to_p3(&A2, &t); + ge_add(&t, &A2, &Ai[0]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[1], &u); + ge_add(&t, &A2, &Ai[1]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[2], &u); + ge_add(&t, &A2, &Ai[2]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[3], &u); + ge_add(&t, &A2, &Ai[3]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[4], &u); + ge_add(&t, &A2, &Ai[4]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[5], &u); + ge_add(&t, &A2, &Ai[5]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[6], &u); + ge_add(&t, &A2, &Ai[6]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[7], &u); + ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) { + break; + } + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i] / 2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &Bi[bslide[i] / 2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + + +static const fe d = { + -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116 +}; + +static const fe sqrtm1 = { + -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482 +}; + +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) { + fe u; + fe v; + fe v3; + fe vxx; + fe check; + fe_frombytes(h->Y, s); + fe_1(h->Z); + fe_sq(u, h->Y); + fe_mul(v, u, d); + fe_sub(u, u, h->Z); /* u = y^2-1 */ + fe_add(v, v, h->Z); /* v = dy^2+1 */ + fe_sq(v3, v); + fe_mul(v3, v3, v); /* v3 = v^3 */ + fe_sq(h->X, v3); + fe_mul(h->X, h->X, v); + fe_mul(h->X, h->X, u); /* x = uv^7 */ + fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */ + fe_mul(h->X, h->X, v3); + fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */ + fe_sq(vxx, h->X); + fe_mul(vxx, vxx, v); + fe_sub(check, vxx, u); /* vx^2-u */ + + if (fe_isnonzero(check)) { + fe_add(check, vxx, u); /* vx^2+u */ + + if (fe_isnonzero(check)) { + return -1; + } + + fe_mul(h->X, h->X, sqrtm1); + } + + if (fe_isnegative(h->X) == (s[31] >> 7)) { + fe_neg(h->X, h->X); + } + + fe_mul(h->T, h->X, h->Y); + return 0; +} + + +/* +r = p + q +*/ + +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yplusx); + fe_mul(r->Y, r->Y, q->yminusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +/* +r = p - q +*/ + +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yminusx); + fe_mul(r->Y, r->Y, q->yplusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +/* +r = p +*/ + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); +} + + + +/* +r = p +*/ + +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); + fe_mul(r->T, p->X, p->Y); +} + + +void ge_p2_0(ge_p2 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); +} + + + +/* +r = 2 * p +*/ + +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) { + fe t0; + + fe_sq(r->X, p->X); + fe_sq(r->Z, p->Y); + fe_sq2(r->T, p->Z); + fe_add(r->Y, p->X, p->Y); + fe_sq(t0, r->Y); + fe_add(r->Y, r->Z, r->X); + fe_sub(r->Z, r->Z, r->X); + fe_sub(r->X, t0, r->Y); + fe_sub(r->T, r->T, r->Z); +} + + +void ge_p3_0(ge_p3 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); + fe_0(h->T); +} + + +/* +r = 2 * p +*/ + +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { + ge_p2 q; + ge_p3_to_p2(&q, p); + ge_p2_dbl(r, &q); +} + + + +/* +r = p +*/ + +static const fe d2 = { + -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199 +}; + +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) { + fe_add(r->YplusX, p->Y, p->X); + fe_sub(r->YminusX, p->Y, p->X); + fe_copy(r->Z, p->Z); + fe_mul(r->T2d, p->T, d2); +} + + +/* +r = p +*/ + +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) { + fe_copy(r->X, p->X); + fe_copy(r->Y, p->Y); + fe_copy(r->Z, p->Z); +} + + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} + + +static unsigned char equal(signed char b, signed char c) { + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + uint64_t y = x; /* 0: yes; 1..255: no */ + y -= 1; /* large: yes; 0..254: no */ + y >>= 63; /* 1: yes; 0: no */ + return (unsigned char) y; +} + +static unsigned char negative(signed char b) { + uint64_t x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>= 63; /* 1: yes; 0: no */ + return (unsigned char) x; +} + +static void cmov(ge_precomp *t, const ge_precomp *u, unsigned char b) { + fe_cmov(t->yplusx, u->yplusx, b); + fe_cmov(t->yminusx, u->yminusx, b); + fe_cmov(t->xy2d, u->xy2d, b); +} + + +static void select(ge_precomp *t, int pos, signed char b) { + ge_precomp minust; + unsigned char bnegative = negative(b); + unsigned char babs = b - (((-bnegative) & b) << 1); + fe_1(t->yplusx); + fe_1(t->yminusx); + fe_0(t->xy2d); + cmov(t, &base[pos][0], equal(babs, 1)); + cmov(t, &base[pos][1], equal(babs, 2)); + cmov(t, &base[pos][2], equal(babs, 3)); + cmov(t, &base[pos][3], equal(babs, 4)); + cmov(t, &base[pos][4], equal(babs, 5)); + cmov(t, &base[pos][5], equal(babs, 6)); + cmov(t, &base[pos][6], equal(babs, 7)); + cmov(t, &base[pos][7], equal(babs, 8)); + fe_copy(minust.yplusx, t->yminusx); + fe_copy(minust.yminusx, t->yplusx); + fe_neg(minust.xy2d, t->xy2d); + cmov(t, &minust, bnegative); +} + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { + signed char e[64]; + signed char carry; + ge_p1p1 r; + ge_p2 s; + ge_precomp t; + int i; + + for (i = 0; i < 32; ++i) { + e[2 * i + 0] = (a[i] >> 0) & 15; + e[2 * i + 1] = (a[i] >> 4) & 15; + } + + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + carry = 0; + + for (i = 0; i < 63; ++i) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= carry << 4; + } + + e[63] += carry; + /* each e[i] is between -8 and 8 */ + ge_p3_0(h); + + for (i = 1; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } + + ge_p3_dbl(&r, h); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p3(h, &r); + + for (i = 0; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } +} + + +/* +r = p - q +*/ + +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YminusX); + fe_mul(r->Y, r->Y, q->YplusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +void ge_tobytes(unsigned char *s, const ge_p2 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} diff --git a/3rd_party/ed25519/ge.h b/3rd_party/ed25519/ge.h new file mode 100644 index 0000000..17fde2d --- /dev/null +++ b/3rd_party/ed25519/ge.h @@ -0,0 +1,74 @@ +#ifndef GE_H +#define GE_H + +#include "fe.h" + + +/* +ge means group element. + +Here the group is the set of pairs (x,y) of field elements (see fe.h) +satisfying -x^2 + y^2 = 1 + d x^2y^2 +where d = -121665/121666. + +Representations: + ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z + ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT + ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + ge_precomp (Duif): (y+x,y-x,2dxy) +*/ + +typedef struct { + fe X; + fe Y; + fe Z; +} ge_p2; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p3; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p1p1; + +typedef struct { + fe yplusx; + fe yminusx; + fe xy2d; +} ge_precomp; + +typedef struct { + fe YplusX; + fe YminusX; + fe Z; + fe T2d; +} ge_cached; + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h); +void ge_tobytes(unsigned char *s, const ge_p2 *h); +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s); + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b); +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a); + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p); +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p); +void ge_p2_0(ge_p2 *h); +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p); +void ge_p3_0(ge_p3 *h); +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p); +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p); +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p); + +#endif diff --git a/3rd_party/ed25519/key_exchange.c b/3rd_party/ed25519/key_exchange.c new file mode 100644 index 0000000..abd75da --- /dev/null +++ b/3rd_party/ed25519/key_exchange.c @@ -0,0 +1,79 @@ +#include "ed25519.h" +#include "fe.h" + +void ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key) { + unsigned char e[32]; + unsigned int i; + + fe x1; + fe x2; + fe z2; + fe x3; + fe z3; + fe tmp0; + fe tmp1; + + int pos; + unsigned int swap; + unsigned int b; + + /* copy the private key and make sure it's valid */ + for (i = 0; i < 32; ++i) { + e[i] = private_key[i]; + } + + e[0] &= 248; + e[31] &= 63; + e[31] |= 64; + + /* unpack the public key and convert edwards to montgomery */ + /* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */ + fe_frombytes(x1, public_key); + fe_1(tmp1); + fe_add(tmp0, x1, tmp1); + fe_sub(tmp1, tmp1, x1); + fe_invert(tmp1, tmp1); + fe_mul(x1, tmp0, tmp1); + + fe_1(x2); + fe_0(z2); + fe_copy(x3, x1); + fe_1(z3); + + swap = 0; + for (pos = 254; pos >= 0; --pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + swap ^= b; + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; + + /* from montgomery.h */ + fe_sub(tmp0, x3, z3); + fe_sub(tmp1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, tmp0, x2); + fe_mul(z2, z2, tmp1); + fe_sq(tmp0, tmp1); + fe_sq(tmp1, x2); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, tmp1, tmp0); + fe_sub(tmp1, tmp1, tmp0); + fe_sq(z2, z2); + fe_mul121666(z3, tmp1); + fe_sq(x3, x3); + fe_add(tmp0, tmp0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, tmp1, tmp0); + } + + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(shared_secret, x2); +} diff --git a/3rd_party/ed25519/keypair.c b/3rd_party/ed25519/keypair.c new file mode 100644 index 0000000..dc1b8ec --- /dev/null +++ b/3rd_party/ed25519/keypair.c @@ -0,0 +1,16 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" + + +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { + ge_p3 A; + + sha512(seed, 32, private_key); + private_key[0] &= 248; + private_key[31] &= 63; + private_key[31] |= 64; + + ge_scalarmult_base(&A, private_key); + ge_p3_tobytes(public_key, &A); +} diff --git a/3rd_party/ed25519/precomp_data.h b/3rd_party/ed25519/precomp_data.h new file mode 100644 index 0000000..ff23986 --- /dev/null +++ b/3rd_party/ed25519/precomp_data.h @@ -0,0 +1,1391 @@ +static const ge_precomp Bi[8] = { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { -22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877 }, + { -6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951 }, + { 4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784 }, + }, + { + { -25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436 }, + { 25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918 }, + { 23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877 }, + }, + { + { -33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800 }, + { -25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305 }, + { -13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300 }, + }, + { + { -3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876 }, + { -24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619 }, + { -3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683 }, + }, +}; + + +/* base[i][j] = (j+1)*256^i*B */ +static const ge_precomp base[32][8] = { + { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303 }, + { -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081 }, + { 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540 }, + { 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397 }, + { 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777 }, + { -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737 }, + { -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726 }, + { -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955 }, + { 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425 }, + }, + }, + { + { + { -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171 }, + { 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510 }, + { 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660 }, + }, + { + { -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639 }, + { 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963 }, + { 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950 }, + }, + { + { -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568 }, + { 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335 }, + { 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628 }, + }, + { + { -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007 }, + { -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772 }, + { -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653 }, + }, + { + { 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567 }, + { 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686 }, + { 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372 }, + }, + { + { -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887 }, + { -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954 }, + { -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953 }, + }, + { + { 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833 }, + { -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532 }, + { -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876 }, + }, + { + { 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268 }, + { 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214 }, + { 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038 }, + }, + }, + { + { + { 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800 }, + { 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645 }, + { -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664 }, + }, + { + { 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933 }, + { -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182 }, + { -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222 }, + }, + { + { -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991 }, + { 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880 }, + { 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092 }, + }, + { + { -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295 }, + { 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788 }, + { 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553 }, + }, + { + { -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026 }, + { 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347 }, + { -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033 }, + }, + { + { -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395 }, + { -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278 }, + { 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890 }, + }, + { + { 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995 }, + { -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596 }, + { -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891 }, + }, + { + { 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060 }, + { 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608 }, + { -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606 }, + }, + }, + { + { + { 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389 }, + { -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016 }, + { -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341 }, + }, + { + { -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505 }, + { 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553 }, + { -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655 }, + }, + { + { 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220 }, + { 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631 }, + { -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099 }, + }, + { + { 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556 }, + { 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749 }, + { 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930 }, + }, + { + { 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391 }, + { 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253 }, + { 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066 }, + }, + { + { 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958 }, + { -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082 }, + { -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383 }, + }, + { + { -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521 }, + { -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807 }, + { 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948 }, + }, + { + { 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134 }, + { -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455 }, + { 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629 }, + }, + }, + { + { + { -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069 }, + { -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746 }, + { 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919 }, + }, + { + { 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837 }, + { 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906 }, + { -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771 }, + }, + { + { -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817 }, + { 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098 }, + { 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409 }, + }, + { + { -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504 }, + { -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727 }, + { 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420 }, + }, + { + { -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003 }, + { -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605 }, + { -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384 }, + }, + { + { -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701 }, + { -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683 }, + { 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708 }, + }, + { + { -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563 }, + { -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260 }, + { -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387 }, + }, + { + { -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672 }, + { 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686 }, + { -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665 }, + }, + }, + { + { + { 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182 }, + { -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277 }, + { 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628 }, + }, + { + { -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474 }, + { -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539 }, + { -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822 }, + }, + { + { -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970 }, + { 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756 }, + { -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508 }, + }, + { + { -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683 }, + { -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655 }, + { -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158 }, + }, + { + { -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125 }, + { -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839 }, + { -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664 }, + }, + { + { 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294 }, + { -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899 }, + { -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070 }, + }, + { + { 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294 }, + { -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949 }, + { -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083 }, + }, + { + { 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420 }, + { -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940 }, + { 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396 }, + }, + }, + { + { + { -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567 }, + { 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127 }, + { -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294 }, + }, + { + { -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887 }, + { 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964 }, + { 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195 }, + }, + { + { 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244 }, + { 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999 }, + { -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762 }, + }, + { + { -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274 }, + { -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236 }, + { -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605 }, + }, + { + { -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761 }, + { -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884 }, + { -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482 }, + }, + { + { -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638 }, + { -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490 }, + { -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170 }, + }, + { + { 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736 }, + { 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124 }, + { -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392 }, + }, + { + { 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029 }, + { 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048 }, + { 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958 }, + }, + }, + { + { + { 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593 }, + { 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071 }, + { -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692 }, + }, + { + { 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687 }, + { -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441 }, + { -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001 }, + }, + { + { -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460 }, + { -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007 }, + { -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762 }, + }, + { + { 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005 }, + { -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674 }, + { 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035 }, + }, + { + { 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590 }, + { -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957 }, + { -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812 }, + }, + { + { 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740 }, + { -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122 }, + { -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158 }, + }, + { + { 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885 }, + { 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140 }, + { 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857 }, + }, + { + { 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155 }, + { 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260 }, + { 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483 }, + }, + }, + { + { + { -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677 }, + { 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815 }, + { 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751 }, + }, + { + { -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203 }, + { -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208 }, + { 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230 }, + }, + { + { 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850 }, + { -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389 }, + { -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968 }, + }, + { + { -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689 }, + { 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880 }, + { 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304 }, + }, + { + { 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632 }, + { -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412 }, + { 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566 }, + }, + { + { -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038 }, + { -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232 }, + { -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943 }, + }, + { + { 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856 }, + { 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738 }, + { 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971 }, + }, + { + { -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718 }, + { -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697 }, + { -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883 }, + }, + }, + { + { + { 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912 }, + { -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358 }, + { 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849 }, + }, + { + { 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307 }, + { -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977 }, + { -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335 }, + }, + { + { -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644 }, + { -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616 }, + { -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735 }, + }, + { + { -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099 }, + { 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341 }, + { -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336 }, + }, + { + { -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646 }, + { 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425 }, + { -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388 }, + }, + { + { -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743 }, + { -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822 }, + { -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462 }, + }, + { + { 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985 }, + { 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702 }, + { -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797 }, + }, + { + { 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293 }, + { 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100 }, + { 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688 }, + }, + }, + { + { + { 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186 }, + { 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610 }, + { -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707 }, + }, + { + { 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220 }, + { 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025 }, + { 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044 }, + }, + { + { 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992 }, + { -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027 }, + { 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197 }, + }, + { + { 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901 }, + { 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952 }, + { 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878 }, + }, + { + { -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390 }, + { 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730 }, + { 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730 }, + }, + { + { -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180 }, + { -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272 }, + { -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715 }, + }, + { + { -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970 }, + { -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772 }, + { -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865 }, + }, + { + { 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750 }, + { 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373 }, + { 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348 }, + }, + }, + { + { + { 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144 }, + { -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195 }, + { 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086 }, + }, + { + { -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684 }, + { -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518 }, + { -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233 }, + }, + { + { -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793 }, + { -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794 }, + { 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435 }, + }, + { + { 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921 }, + { 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518 }, + { 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563 }, + }, + { + { 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278 }, + { -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024 }, + { 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030 }, + }, + { + { 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783 }, + { 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717 }, + { 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844 }, + }, + { + { 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333 }, + { 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048 }, + { 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760 }, + }, + { + { -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760 }, + { -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757 }, + { -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112 }, + }, + }, + { + { + { -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468 }, + { 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184 }, + { 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289 }, + }, + { + { 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066 }, + { 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882 }, + { 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226 }, + }, + { + { 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101 }, + { 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279 }, + { -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811 }, + }, + { + { 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709 }, + { 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714 }, + { -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121 }, + }, + { + { 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464 }, + { 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847 }, + { 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400 }, + }, + { + { 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414 }, + { -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158 }, + { 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045 }, + }, + { + { -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415 }, + { -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459 }, + { -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079 }, + }, + { + { 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412 }, + { -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743 }, + { -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836 }, + }, + }, + { + { + { 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022 }, + { 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429 }, + { -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065 }, + }, + { + { 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861 }, + { 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000 }, + { -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101 }, + }, + { + { 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815 }, + { 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642 }, + { 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966 }, + }, + { + { 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574 }, + { -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742 }, + { -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689 }, + }, + { + { 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020 }, + { -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772 }, + { 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982 }, + }, + { + { -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953 }, + { -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218 }, + { -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265 }, + }, + { + { 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073 }, + { -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325 }, + { -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798 }, + }, + { + { -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870 }, + { -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863 }, + { -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927 }, + }, + }, + { + { + { -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267 }, + { -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663 }, + { 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862 }, + }, + { + { -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673 }, + { 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943 }, + { 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020 }, + }, + { + { -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238 }, + { 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064 }, + { 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795 }, + }, + { + { 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052 }, + { -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904 }, + { 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531 }, + }, + { + { -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979 }, + { -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841 }, + { 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431 }, + }, + { + { 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324 }, + { -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940 }, + { 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320 }, + }, + { + { -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184 }, + { 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114 }, + { 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878 }, + }, + { + { 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784 }, + { -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091 }, + { -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585 }, + }, + }, + { + { + { -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208 }, + { 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864 }, + { 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661 }, + }, + { + { 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233 }, + { 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212 }, + { -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525 }, + }, + { + { -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068 }, + { 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397 }, + { -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988 }, + }, + { + { 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889 }, + { 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038 }, + { 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697 }, + }, + { + { 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875 }, + { -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905 }, + { -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656 }, + }, + { + { 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818 }, + { 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714 }, + { 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203 }, + }, + { + { 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931 }, + { -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024 }, + { -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084 }, + }, + { + { -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204 }, + { 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817 }, + { 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667 }, + }, + }, + { + { + { 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504 }, + { -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768 }, + { -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255 }, + }, + { + { 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790 }, + { 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438 }, + { -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333 }, + }, + { + { 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971 }, + { 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905 }, + { 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409 }, + }, + { + { 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409 }, + { 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499 }, + { -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363 }, + }, + { + { 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664 }, + { -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324 }, + { -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940 }, + }, + { + { 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990 }, + { -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914 }, + { -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290 }, + }, + { + { 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257 }, + { -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433 }, + { -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236 }, + }, + { + { -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045 }, + { 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093 }, + { -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347 }, + }, + }, + { + { + { -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191 }, + { -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507 }, + { -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906 }, + }, + { + { 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018 }, + { -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109 }, + { -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926 }, + }, + { + { -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528 }, + { 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625 }, + { -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286 }, + }, + { + { 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033 }, + { 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866 }, + { 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896 }, + }, + { + { 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075 }, + { 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347 }, + { -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437 }, + }, + { + { -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165 }, + { -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588 }, + { -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193 }, + }, + { + { -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017 }, + { -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883 }, + { 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961 }, + }, + { + { 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043 }, + { 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663 }, + { -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362 }, + }, + }, + { + { + { -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860 }, + { 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466 }, + { -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063 }, + }, + { + { -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997 }, + { -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295 }, + { -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369 }, + }, + { + { 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385 }, + { 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109 }, + { 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906 }, + }, + { + { 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424 }, + { -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185 }, + { 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962 }, + }, + { + { -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325 }, + { 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593 }, + { 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404 }, + }, + { + { -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644 }, + { 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801 }, + { 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804 }, + }, + { + { -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884 }, + { -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577 }, + { -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849 }, + }, + { + { 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473 }, + { -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644 }, + { -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319 }, + }, + }, + { + { + { -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599 }, + { -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768 }, + { -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084 }, + }, + { + { -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328 }, + { -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369 }, + { 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920 }, + }, + { + { 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815 }, + { -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025 }, + { -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397 }, + }, + { + { -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448 }, + { 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981 }, + { 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165 }, + }, + { + { 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501 }, + { 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073 }, + { -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861 }, + }, + { + { 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845 }, + { -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211 }, + { 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870 }, + }, + { + { 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096 }, + { 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803 }, + { -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168 }, + }, + { + { 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965 }, + { -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505 }, + { 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598 }, + }, + }, + { + { + { 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782 }, + { 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900 }, + { -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479 }, + }, + { + { -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208 }, + { 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232 }, + { 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719 }, + }, + { + { 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271 }, + { -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326 }, + { -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132 }, + }, + { + { 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300 }, + { 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570 }, + { 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670 }, + }, + { + { -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994 }, + { -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913 }, + { 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317 }, + }, + { + { -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730 }, + { 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096 }, + { -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078 }, + }, + { + { -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411 }, + { -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905 }, + { -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654 }, + }, + { + { -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870 }, + { -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498 }, + { 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579 }, + }, + }, + { + { + { 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677 }, + { 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647 }, + { -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743 }, + }, + { + { -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468 }, + { 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375 }, + { -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155 }, + }, + { + { 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725 }, + { -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612 }, + { -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943 }, + }, + { + { -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944 }, + { 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928 }, + { 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406 }, + }, + { + { 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139 }, + { -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963 }, + { -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693 }, + }, + { + { 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734 }, + { -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680 }, + { -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410 }, + }, + { + { -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931 }, + { -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654 }, + { 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710 }, + }, + { + { 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180 }, + { -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684 }, + { -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895 }, + }, + }, + { + { + { 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501 }, + { -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413 }, + { 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880 }, + }, + { + { -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874 }, + { 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962 }, + { -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899 }, + }, + { + { 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152 }, + { 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063 }, + { 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080 }, + }, + { + { -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146 }, + { -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183 }, + { -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133 }, + }, + { + { -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421 }, + { -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622 }, + { -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197 }, + }, + { + { 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663 }, + { 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753 }, + { 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755 }, + }, + { + { -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862 }, + { -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118 }, + { 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171 }, + }, + { + { 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380 }, + { 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824 }, + { 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270 }, + }, + }, + { + { + { -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438 }, + { -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584 }, + { -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562 }, + }, + { + { 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471 }, + { 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610 }, + { 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269 }, + }, + { + { -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650 }, + { 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369 }, + { 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461 }, + }, + { + { 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462 }, + { -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793 }, + { -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218 }, + }, + { + { -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226 }, + { 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019 }, + { -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037 }, + }, + { + { 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171 }, + { -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132 }, + { -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841 }, + }, + { + { 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181 }, + { -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210 }, + { -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040 }, + }, + { + { 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935 }, + { 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105 }, + { -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814 }, + }, + }, + { + { + { 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852 }, + { 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581 }, + { -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646 }, + }, + { + { 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844 }, + { 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025 }, + { 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453 }, + }, + { + { -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068 }, + { 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192 }, + { -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921 }, + }, + { + { -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259 }, + { -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426 }, + { -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072 }, + }, + { + { -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305 }, + { 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832 }, + { 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943 }, + }, + { + { -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011 }, + { 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447 }, + { 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494 }, + }, + { + { -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245 }, + { -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859 }, + { 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915 }, + }, + { + { 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707 }, + { 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848 }, + { -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224 }, + }, + }, + { + { + { -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391 }, + { 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215 }, + { -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101 }, + }, + { + { 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713 }, + { 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849 }, + { -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930 }, + }, + { + { -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940 }, + { -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031 }, + { -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404 }, + }, + { + { -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243 }, + { -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116 }, + { -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525 }, + }, + { + { -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509 }, + { -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883 }, + { 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865 }, + }, + { + { -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660 }, + { 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273 }, + { -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138 }, + }, + { + { -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560 }, + { -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135 }, + { 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941 }, + }, + { + { -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739 }, + { 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756 }, + { -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819 }, + }, + }, + { + { + { -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347 }, + { -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028 }, + { 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075 }, + }, + { + { 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799 }, + { -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609 }, + { -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817 }, + }, + { + { -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989 }, + { -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523 }, + { 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278 }, + }, + { + { 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045 }, + { 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377 }, + { 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480 }, + }, + { + { 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016 }, + { 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426 }, + { 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525 }, + }, + { + { 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396 }, + { 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080 }, + { 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892 }, + }, + { + { 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275 }, + { 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074 }, + { 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140 }, + }, + { + { -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717 }, + { -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101 }, + { 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127 }, + }, + }, + { + { + { -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632 }, + { -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415 }, + { -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160 }, + }, + { + { 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876 }, + { 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625 }, + { -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478 }, + }, + { + { 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164 }, + { 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595 }, + { -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248 }, + }, + { + { -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858 }, + { 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193 }, + { 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184 }, + }, + { + { -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942 }, + { -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635 }, + { 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948 }, + }, + { + { 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935 }, + { -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415 }, + { -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416 }, + }, + { + { -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018 }, + { 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778 }, + { 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659 }, + }, + { + { -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385 }, + { 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503 }, + { 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329 }, + }, + }, + { + { + { 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056 }, + { -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838 }, + { 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948 }, + }, + { + { -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691 }, + { -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118 }, + { -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517 }, + }, + { + { -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269 }, + { -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904 }, + { -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589 }, + }, + { + { -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193 }, + { -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910 }, + { -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930 }, + }, + { + { -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667 }, + { 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481 }, + { -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876 }, + }, + { + { 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640 }, + { -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278 }, + { -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112 }, + }, + { + { 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272 }, + { 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012 }, + { -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221 }, + }, + { + { 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046 }, + { 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345 }, + { -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310 }, + }, + }, + { + { + { 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937 }, + { 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636 }, + { -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008 }, + }, + { + { -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429 }, + { -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576 }, + { 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066 }, + }, + { + { -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490 }, + { -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104 }, + { 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053 }, + }, + { + { 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275 }, + { -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511 }, + { 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095 }, + }, + { + { -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439 }, + { 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939 }, + { -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424 }, + }, + { + { 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310 }, + { 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608 }, + { -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079 }, + }, + { + { -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101 }, + { 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418 }, + { 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576 }, + }, + { + { 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356 }, + { 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996 }, + { -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099 }, + }, + }, + { + { + { -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728 }, + { -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658 }, + { -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242 }, + }, + { + { -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001 }, + { -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766 }, + { 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373 }, + }, + { + { 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458 }, + { -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628 }, + { -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657 }, + }, + { + { -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062 }, + { 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616 }, + { 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014 }, + }, + { + { 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383 }, + { -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814 }, + { -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718 }, + }, + { + { 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417 }, + { 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222 }, + { 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444 }, + }, + { + { -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597 }, + { 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970 }, + { 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799 }, + }, + { + { -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647 }, + { 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511 }, + { -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032 }, + }, + }, + { + { + { 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834 }, + { -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461 }, + { 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062 }, + }, + { + { -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516 }, + { -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547 }, + { -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240 }, + }, + { + { -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038 }, + { -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741 }, + { 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103 }, + }, + { + { -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747 }, + { -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323 }, + { 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016 }, + }, + { + { -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373 }, + { 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228 }, + { -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141 }, + }, + { + { 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399 }, + { 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831 }, + { -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376 }, + }, + { + { -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313 }, + { -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958 }, + { -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577 }, + }, + { + { -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743 }, + { 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684 }, + { -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476 }, + }, + }, +}; diff --git a/3rd_party/ed25519/sc.c b/3rd_party/ed25519/sc.c new file mode 100644 index 0000000..ca5bad2 --- /dev/null +++ b/3rd_party/ed25519/sc.c @@ -0,0 +1,809 @@ +#include "fixedint.h" +#include "sc.h" + +static uint64_t load_3(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + + return result; +} + +static uint64_t load_4(const unsigned char *in) { + uint64_t result; + + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + result |= ((uint64_t) in[3]) << 24; + + return result; +} + +/* +Input: + s[0]+256*s[1]+...+256^63*s[63] = s + +Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + Overwrites s in place. +*/ + +void sc_reduce(unsigned char *s) { + int64_t s0 = 2097151 & load_3(s); + int64_t s1 = 2097151 & (load_4(s + 2) >> 5); + int64_t s2 = 2097151 & (load_3(s + 5) >> 2); + int64_t s3 = 2097151 & (load_4(s + 7) >> 7); + int64_t s4 = 2097151 & (load_4(s + 10) >> 4); + int64_t s5 = 2097151 & (load_3(s + 13) >> 1); + int64_t s6 = 2097151 & (load_4(s + 15) >> 6); + int64_t s7 = 2097151 & (load_3(s + 18) >> 3); + int64_t s8 = 2097151 & load_3(s + 21); + int64_t s9 = 2097151 & (load_4(s + 23) >> 5); + int64_t s10 = 2097151 & (load_3(s + 26) >> 2); + int64_t s11 = 2097151 & (load_4(s + 28) >> 7); + int64_t s12 = 2097151 & (load_4(s + 31) >> 4); + int64_t s13 = 2097151 & (load_3(s + 34) >> 1); + int64_t s14 = 2097151 & (load_4(s + 36) >> 6); + int64_t s15 = 2097151 & (load_3(s + 39) >> 3); + int64_t s16 = 2097151 & load_3(s + 42); + int64_t s17 = 2097151 & (load_4(s + 44) >> 5); + int64_t s18 = 2097151 & (load_3(s + 47) >> 2); + int64_t s19 = 2097151 & (load_4(s + 49) >> 7); + int64_t s20 = 2097151 & (load_4(s + 52) >> 4); + int64_t s21 = 2097151 & (load_3(s + 55) >> 1); + int64_t s22 = 2097151 & (load_4(s + 57) >> 6); + int64_t s23 = (load_4(s + 60) >> 3); + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (unsigned char) (s0 >> 0); + s[1] = (unsigned char) (s0 >> 8); + s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5)); + s[3] = (unsigned char) (s1 >> 3); + s[4] = (unsigned char) (s1 >> 11); + s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2)); + s[6] = (unsigned char) (s2 >> 6); + s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7)); + s[8] = (unsigned char) (s3 >> 1); + s[9] = (unsigned char) (s3 >> 9); + s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4)); + s[11] = (unsigned char) (s4 >> 4); + s[12] = (unsigned char) (s4 >> 12); + s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1)); + s[14] = (unsigned char) (s5 >> 7); + s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6)); + s[16] = (unsigned char) (s6 >> 2); + s[17] = (unsigned char) (s6 >> 10); + s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3)); + s[19] = (unsigned char) (s7 >> 5); + s[20] = (unsigned char) (s7 >> 13); + s[21] = (unsigned char) (s8 >> 0); + s[22] = (unsigned char) (s8 >> 8); + s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5)); + s[24] = (unsigned char) (s9 >> 3); + s[25] = (unsigned char) (s9 >> 11); + s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2)); + s[27] = (unsigned char) (s10 >> 6); + s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7)); + s[29] = (unsigned char) (s11 >> 1); + s[30] = (unsigned char) (s11 >> 9); + s[31] = (unsigned char) (s11 >> 17); +} + + + +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t c0 = 2097151 & load_3(c); + int64_t c1 = 2097151 & (load_4(c + 2) >> 5); + int64_t c2 = 2097151 & (load_3(c + 5) >> 2); + int64_t c3 = 2097151 & (load_4(c + 7) >> 7); + int64_t c4 = 2097151 & (load_4(c + 10) >> 4); + int64_t c5 = 2097151 & (load_3(c + 13) >> 1); + int64_t c6 = 2097151 & (load_4(c + 15) >> 6); + int64_t c7 = 2097151 & (load_3(c + 18) >> 3); + int64_t c8 = 2097151 & load_3(c + 21); + int64_t c9 = 2097151 & (load_4(c + 23) >> 5); + int64_t c10 = 2097151 & (load_3(c + 26) >> 2); + int64_t c11 = (load_4(c + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry18 = (s18 + (1 << 20)) >> 21; + s19 += carry18; + s18 -= carry18 << 21; + carry20 = (s20 + (1 << 20)) >> 21; + s21 += carry20; + s20 -= carry20 << 21; + carry22 = (s22 + (1 << 20)) >> 21; + s23 += carry22; + s22 -= carry22 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + carry17 = (s17 + (1 << 20)) >> 21; + s18 += carry17; + s17 -= carry17 << 21; + carry19 = (s19 + (1 << 20)) >> 21; + s20 += carry19; + s19 -= carry19 << 21; + carry21 = (s21 + (1 << 20)) >> 21; + s22 += carry21; + s21 -= carry21 << 21; + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (unsigned char) (s0 >> 0); + s[1] = (unsigned char) (s0 >> 8); + s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5)); + s[3] = (unsigned char) (s1 >> 3); + s[4] = (unsigned char) (s1 >> 11); + s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2)); + s[6] = (unsigned char) (s2 >> 6); + s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7)); + s[8] = (unsigned char) (s3 >> 1); + s[9] = (unsigned char) (s3 >> 9); + s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4)); + s[11] = (unsigned char) (s4 >> 4); + s[12] = (unsigned char) (s4 >> 12); + s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1)); + s[14] = (unsigned char) (s5 >> 7); + s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6)); + s[16] = (unsigned char) (s6 >> 2); + s[17] = (unsigned char) (s6 >> 10); + s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3)); + s[19] = (unsigned char) (s7 >> 5); + s[20] = (unsigned char) (s7 >> 13); + s[21] = (unsigned char) (s8 >> 0); + s[22] = (unsigned char) (s8 >> 8); + s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5)); + s[24] = (unsigned char) (s9 >> 3); + s[25] = (unsigned char) (s9 >> 11); + s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2)); + s[27] = (unsigned char) (s10 >> 6); + s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7)); + s[29] = (unsigned char) (s11 >> 1); + s[30] = (unsigned char) (s11 >> 9); + s[31] = (unsigned char) (s11 >> 17); +} diff --git a/3rd_party/ed25519/sc.h b/3rd_party/ed25519/sc.h new file mode 100644 index 0000000..e29e7fa --- /dev/null +++ b/3rd_party/ed25519/sc.h @@ -0,0 +1,12 @@ +#ifndef SC_H +#define SC_H + +/* +The set of scalars is \Z/l +where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_reduce(unsigned char *s); +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); + +#endif diff --git a/3rd_party/ed25519/seed.c b/3rd_party/ed25519/seed.c new file mode 100644 index 0000000..cf252b8 --- /dev/null +++ b/3rd_party/ed25519/seed.c @@ -0,0 +1,40 @@ +#include "ed25519.h" + +#ifndef ED25519_NO_SEED + +#ifdef _WIN32 +#include <windows.h> +#include <wincrypt.h> +#else +#include <stdio.h> +#endif + +int ed25519_create_seed(unsigned char *seed) { +#ifdef _WIN32 + HCRYPTPROV prov; + + if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return 1; + } + + if (!CryptGenRandom(prov, 32, seed)) { + CryptReleaseContext(prov, 0); + return 1; + } + + CryptReleaseContext(prov, 0); +#else + FILE *f = fopen("/dev/urandom", "rb"); + + if (f == NULL) { + return 1; + } + + if(fread(seed, 1, 32, f)){} + fclose(f); +#endif + + return 0; +} + +#endif diff --git a/3rd_party/ed25519/sha512.c b/3rd_party/ed25519/sha512.c new file mode 100644 index 0000000..cb8ae71 --- /dev/null +++ b/3rd_party/ed25519/sha512.c @@ -0,0 +1,275 @@ +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtom.org + */ + +#include "fixedint.h" +#include "sha512.h" + +/* the K array */ +static const uint64_t K[80] = { + UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), + UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc), + UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), + UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118), + UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), + UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2), + UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), + UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694), + UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), + UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65), + UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), + UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5), + UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), + UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4), + UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), + UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70), + UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), + UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df), + UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), + UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b), + UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001), + UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30), + UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), + UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8), + UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), + UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8), + UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), + UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3), + UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), + UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec), + UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), + UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b), + UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), + UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178), + UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), + UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b), + UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), + UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c), + UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), + UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817) +}; + +/* Various logical functions */ + +#define ROR64c(x, y) \ + ( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)(y)&UINT64_C(63))) | \ + ((x)<<((uint64_t)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF)) + +#define STORE64H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ + (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ + (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ + (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } + +#define LOAD64H(x, y) \ + { x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \ + (((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \ + (((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \ + (((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); } + + +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) (((x | y) & z) | (x & y)) +#define S(x, n) ROR64c(x, n) +#define R(x, n) (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)n)) +#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39)) +#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41)) +#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7)) +#define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6)) +#ifndef MIN + #define MIN(x, y) ( ((x)<(y))?(x):(y) ) +#endif + +/* compress 1024-bits */ +static int sha512_compress(sha512_context *md, unsigned char *buf) +{ + uint64_t S[8], W[80], t0, t1; + int i; + + /* copy state into S */ + for (i = 0; i < 8; i++) { + S[i] = md->state[i]; + } + + /* copy the state into 1024-bits into W[0..15] */ + for (i = 0; i < 16; i++) { + LOAD64H(W[i], buf + (8*i)); + } + + /* fill W[16..79] */ + for (i = 16; i < 80; i++) { + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } + +/* Compress */ + #define RND(a,b,c,d,e,f,g,h,i) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c);\ + d += t0; \ + h = t0 + t1; + + for (i = 0; i < 80; i += 8) { + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); + } + + #undef RND + + + + /* feedback */ + for (i = 0; i < 8; i++) { + md->state[i] = md->state[i] + S[i]; + } + + return 0; +} + + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return 0 if successful +*/ +int sha512_init(sha512_context * md) { + if (md == NULL) return 1; + + md->curlen = 0; + md->length = 0; + md->state[0] = UINT64_C(0x6a09e667f3bcc908); + md->state[1] = UINT64_C(0xbb67ae8584caa73b); + md->state[2] = UINT64_C(0x3c6ef372fe94f82b); + md->state[3] = UINT64_C(0xa54ff53a5f1d36f1); + md->state[4] = UINT64_C(0x510e527fade682d1); + md->state[5] = UINT64_C(0x9b05688c2b3e6c1f); + md->state[6] = UINT64_C(0x1f83d9abfb41bd6b); + md->state[7] = UINT64_C(0x5be0cd19137e2179); + + return 0; +} + +/** + Process a block of memory though the hash + @param md The hash state + @param in The data to hash + @param inlen The length of the data (octets) + @return 0 if successful +*/ +int sha512_update (sha512_context * md, const unsigned char *in, size_t inlen) +{ + size_t n; + size_t i; + int err; + if (md == NULL) return 1; + if (in == NULL) return 1; + if (md->curlen > sizeof(md->buf)) { + return 1; + } + while (inlen > 0) { + if (md->curlen == 0 && inlen >= 128) { + if ((err = sha512_compress (md, (unsigned char *)in)) != 0) { + return err; + } + md->length += 128 * 8; + in += 128; + inlen -= 128; + } else { + n = MIN(inlen, (128 - md->curlen)); + + for (i = 0; i < n; i++) { + md->buf[i + md->curlen] = in[i]; + } + + + md->curlen += n; + in += n; + inlen -= n; + if (md->curlen == 128) { + if ((err = sha512_compress (md, md->buf)) != 0) { + return err; + } + md->length += 8*128; + md->curlen = 0; + } + } + } + return 0; +} + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (64 bytes) + @return 0 if successful +*/ + int sha512_final(sha512_context * md, unsigned char *out) + { + int i; + + if (md == NULL) return 1; + if (out == NULL) return 1; + + if (md->curlen >= sizeof(md->buf)) { + return 1; + } + + /* increase the length of the message */ + md->length += md->curlen * UINT64_C(8); + + /* append the '1' bit */ + md->buf[md->curlen++] = (unsigned char)0x80; + + /* if the length is currently above 112 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if (md->curlen > 112) { + while (md->curlen < 128) { + md->buf[md->curlen++] = (unsigned char)0; + } + sha512_compress(md, md->buf); + md->curlen = 0; + } + + /* pad upto 120 bytes of zeroes + * note: that from 112 to 120 is the 64 MSB of the length. We assume that you won't hash + * > 2^64 bits of data... :-) + */ +while (md->curlen < 120) { + md->buf[md->curlen++] = (unsigned char)0; +} + + /* store length */ +STORE64H(md->length, md->buf+120); +sha512_compress(md, md->buf); + + /* copy output */ +for (i = 0; i < 8; i++) { + STORE64H(md->state[i], out+(8*i)); +} + +return 0; +} + +int sha512(const unsigned char *message, size_t message_len, unsigned char *out) +{ + sha512_context ctx; + int ret; + if ((ret = sha512_init(&ctx))) return ret; + if ((ret = sha512_update(&ctx, message, message_len))) return ret; + if ((ret = sha512_final(&ctx, out))) return ret; + return 0; +} diff --git a/3rd_party/ed25519/sha512.h b/3rd_party/ed25519/sha512.h new file mode 100644 index 0000000..a34dd5e --- /dev/null +++ b/3rd_party/ed25519/sha512.h @@ -0,0 +1,21 @@ +#ifndef SHA512_H +#define SHA512_H + +#include <stddef.h> + +#include "fixedint.h" + +/* state */ +typedef struct sha512_context_ { + uint64_t length, state[8]; + size_t curlen; + unsigned char buf[128]; +} sha512_context; + + +int sha512_init(sha512_context * md); +int sha512_final(sha512_context * md, unsigned char *out); +int sha512_update(sha512_context * md, const unsigned char *in, size_t inlen); +int sha512(const unsigned char *message, size_t message_len, unsigned char *out); + +#endif diff --git a/3rd_party/ed25519/sign.c b/3rd_party/ed25519/sign.c new file mode 100644 index 0000000..199a839 --- /dev/null +++ b/3rd_party/ed25519/sign.c @@ -0,0 +1,31 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + + +void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) { + sha512_context hash; + unsigned char hram[64]; + unsigned char r[64]; + ge_p3 R; + + + sha512_init(&hash); + sha512_update(&hash, private_key + 32, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, r); + + sc_reduce(r); + ge_scalarmult_base(&R, r); + ge_p3_tobytes(signature, &R); + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, hram); + + sc_reduce(hram); + sc_muladd(signature + 32, hram, private_key, r); +} diff --git a/3rd_party/ed25519/verify.c b/3rd_party/ed25519/verify.c new file mode 100644 index 0000000..32f988e --- /dev/null +++ b/3rd_party/ed25519/verify.c @@ -0,0 +1,77 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + +static int consttime_equal(const unsigned char *x, const unsigned char *y) { + unsigned char r = 0; + + r = x[0] ^ y[0]; + #define F(i) r |= x[i] ^ y[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); + #undef F + + return !r; +} + +int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key) { + unsigned char h[64]; + unsigned char checker[32]; + sha512_context hash; + ge_p3 A; + ge_p2 R; + + if (signature[63] & 224) { + return 0; + } + + if (ge_frombytes_negate_vartime(&A, public_key) != 0) { + return 0; + } + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, h); + + sc_reduce(h); + ge_double_scalarmult_vartime(&R, h, &A, signature + 32); + ge_tobytes(checker, &R); + + if (!consttime_equal(checker, signature)) { + return 0; + } + + return 1; +} diff --git a/3rd_party/libsrp6a-sha512/LICENSE b/3rd_party/libsrp6a-sha512/LICENSE new file mode 100644 index 0000000..7f70640 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/LICENSE @@ -0,0 +1,62 @@ +Licensing +--------- + +SRP is royalty-free worldwide for commercial and non-commercial use. +The SRP library has been carefully written not to depend on any +encumbered algorithms, and it is distributed under a standard +BSD-style Open Source license which is shown below. This license +covers implementations based on the SRP library as well as +independent implementations based on RFC 2945. + +The SRP distribution itself contains algorithms and code from +various freeware packages; these parts fall under both the SRP +Open Source license and the packages' own licenses. Care has +been taken to ensure that these licenses are compatible with +Open Source distribution, but it is the responsibility of the +licensee to comply with the terms of these licenses. This +disclaimer also applies to third-party libraries that may be +linked into the distribution, since they may contain patented +intellectual property. The file "Copyrights" contains a list +of the copyrights incorporated by portions of the software. + +Broader use of the SRP authentication technology, such as variants +incorporating the use of an explicit server secret (SRP-Z), may +require a license; please contact the Stanford Office of Technology +Licensing (http://otl.stanford.edu/) for more information about +terms and conditions. + +This software is covered under the following copyright: + +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +Address all questions regarding this license to: + + Tom Wu + tjw@cs.Stanford.EDU diff --git a/3rd_party/libsrp6a-sha512/Makefile.am b/3rd_party/libsrp6a-sha512/Makefile.am new file mode 100644 index 0000000..2acd582 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/Makefile.am @@ -0,0 +1,31 @@ +AUTOMAKE_OPTIONS = foreign no-dependencies + +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) \ + -Wno-incompatible-pointer-types + +AM_CFLAGS = -DHAVE_CONFIG_H +if HAVE_OPENSSL +AM_CFLAGS += -DOPENSSL=1 $(openssl_CFLAGS) +else +if HAVE_GCRYPT +AM_CFLAGS += -DGCRYPT=1 $(libgcrypt_CFLAGS) +else +if HAVE_MBEDTLS +AM_CFLAGS += -DMBEDTLS=1 $(mbedtls_CFLAGS) +endif +endif +endif + +noinst_LTLIBRARIES = libsrp6a-sha512.la + +libsrp6a_sha512_la_SOURCES = \ + t_conv.c t_math.c t_misc.c \ + t_truerand.c cstr.c \ + srp.c srp6a_sha512_client.c \ + srp.h srp_aux.h cstr.h \ + t_sha.c +#if !HAVE_OPENSSL +#libsrp6a_sha512_la_SOURCES += t_sha.c +#endif diff --git a/3rd_party/libsrp6a-sha512/README.md b/3rd_party/libsrp6a-sha512/README.md new file mode 100644 index 0000000..4affe4a --- /dev/null +++ b/3rd_party/libsrp6a-sha512/README.md @@ -0,0 +1,35 @@ +# SRP6a-sha512 library + +## About + +This library is based on Stanford's Secure Remote Password (SRP) protocol +implementation, or more precise on the `libsrp` part thereof. +The entire source code for the SRP project can be obtained from [here](https://github.com/secure-remote-password/stanford-srp). + +It has been adapted to the needs of the libimobiledevice project, and +contains just a part of the original code; it only supports the SRP6a +client method which has been modified to use SHA512 instead of SHA1. +The only supported SRP method is `SRP6a_sha512_client_method()`. +Besides that, support for MbedTLS has been added. + +Also, all server-side code has been removed, and the client-side code +has been reduced to a minimum, so that basically only the following +functions remain operational: + +- `SRP_initialize_library` +- `SRP_new` +- `SRP_free` +- `SRP_set_user_raw` +- `SRP_set_params` +- `SRP_set_auth_password` +- `SRP_gen_pub` +- `SRP_compute_key` +- `SRP_respond` +- `SRP_verify` + +Anything else has not been tested and must be considered non-functional. + +## License + +The license of the original work does still apply and can be found in the +LICENSE file that comes with the code. diff --git a/3rd_party/libsrp6a-sha512/cstr.c b/3rd_party/libsrp6a-sha512/cstr.c new file mode 100644 index 0000000..9856f46 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/cstr.c @@ -0,0 +1,226 @@ +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "cstr.h" + +#define EXPFACTOR 2 /* Minimum expansion factor */ +#define MINSIZE 4 /* Absolute minimum - one word */ + +static char cstr_empty_string[] = { '\0' }; +static cstr_allocator * default_alloc = NULL; + +/* + * It is assumed, for efficiency, that it is okay to pass more arguments + * to a function than are called for, as long as the required arguments + * are in proper form. If extra arguments to malloc() and free() cause + * problems, define PEDANTIC_ARGS below. + */ +#ifdef PEDANTIC_ARGS +static void * Cmalloc(int n, void * heap) { return malloc(n); } +static void Cfree(void * p, void * heap) { free(p); } +static cstr_allocator malloc_allocator = { Cmalloc, Cfree, NULL }; +#else +static cstr_allocator malloc_allocator = { malloc, free, NULL }; +#endif + +_TYPE( void ) +cstr_set_allocator(cstr_allocator * alloc) +{ + default_alloc = alloc; +} + +_TYPE( cstr * ) +cstr_new_alloc(cstr_allocator * alloc) +{ + cstr * str; + + if(alloc == NULL) { + if(default_alloc == NULL) { + default_alloc = &malloc_allocator; + } + alloc = default_alloc; + } + + str = (cstr *) (*alloc->alloc)(sizeof(cstr), alloc->heap); + if(str) { + str->data = cstr_empty_string; + str->length = str->cap = 0; + str->ref = 1; + str->allocator = alloc; + } + return str; +} + +_TYPE( cstr * ) +cstr_new() +{ + return cstr_new_alloc(NULL); +} + +_TYPE( cstr * ) +cstr_dup_alloc(const cstr * str, cstr_allocator * alloc) +{ + cstr * nstr = cstr_new_alloc(alloc); + if(nstr) + cstr_setn(nstr, str->data, str->length); + return nstr; +} + +_TYPE( cstr * ) +cstr_dup(const cstr * str) +{ + return cstr_dup_alloc(str, NULL); +} + +_TYPE( cstr * ) +cstr_create(const char * s) +{ + return cstr_createn(s, strlen(s)); +} + +_TYPE( cstr * ) +cstr_createn(const char * s, int len) +{ + cstr * str = cstr_new(); + if(str) { + cstr_setn(str, s, len); + } + return str; +} + +_TYPE( void ) +cstr_use(cstr * str) +{ + ++str->ref; +} + +_TYPE( void ) +cstr_clear_free(cstr * str) +{ + if(--str->ref == 0) { + if(str->cap > 0) { + memset(str->data, 0, str->cap); + (*str->allocator->free)(str->data, str->allocator->heap); + } + (*str->allocator->free)(str, str->allocator->heap); + } +} + +_TYPE( void ) +cstr_free(cstr * str) +{ + if(--str->ref == 0) { + if(str->cap > 0) + (*str->allocator->free)(str->data, str->allocator->heap); + (*str->allocator->free)(str, str->allocator->heap); + } +} + +_TYPE( void ) +cstr_empty(cstr * str) +{ + if(str->cap > 0) + (*str->allocator->free)(str->data, str->allocator->heap); + str->data = cstr_empty_string; + str->length = str->cap = 0; +} + +static int +cstr_alloc(cstr * str, int len) +{ + char * t; + + if(len > str->cap) { + if(len < EXPFACTOR * str->cap) + len = EXPFACTOR * str->cap; + if(len < MINSIZE) + len = MINSIZE; + + t = (char *) (*str->allocator->alloc)(len * sizeof(char), + str->allocator->heap); + if(t) { + if(str->data) { + t[str->length] = 0; + if(str->cap > 0) { + if(str->length > 0) + memcpy(t, str->data, str->length); + free(str->data); + } + } + str->data = t; + str->cap = len; + return 1; + } + else + return -1; + } + else + return 0; +} + +_TYPE( int ) +cstr_copy(cstr * dst, const cstr * src) +{ + return cstr_setn(dst, src->data, src->length); +} + +_TYPE( int ) +cstr_set(cstr * str, const char * s) +{ + return cstr_setn(str, s, strlen(s)); +} + +_TYPE( int ) +cstr_setn(cstr * str, const char * s, int len) +{ + if(cstr_alloc(str, len + 1) < 0) + return -1; + str->data[len] = 0; + if(s != NULL && len > 0) + memmove(str->data, s, len); + str->length = len; + return 1; +} + +_TYPE( int ) +cstr_set_length(cstr * str, int len) +{ + if(len < str->length) { + str->data[len] = 0; + str->length = len; + return 1; + } + else if(len > str->length) { + if(cstr_alloc(str, len + 1) < 0) + return -1; + memset(str->data + str->length, 0, len - str->length + 1); + str->length = len; + return 1; + } + else + return 0; +} + +_TYPE( int ) +cstr_append(cstr * str, const char * s) +{ + return cstr_appendn(str, s, strlen(s)); +} + +_TYPE( int ) +cstr_appendn(cstr * str, const char * s, int len) +{ + if(cstr_alloc(str, str->length + len + 1) < 0) + return -1; + memcpy(str->data + str->length, s, len); + str->length += len; + str->data[str->length] = 0; + return 1; +} + +_TYPE( int ) +cstr_append_str(cstr * dst, const cstr * src) +{ + return cstr_appendn(dst, src->data, src->length); +} diff --git a/3rd_party/libsrp6a-sha512/cstr.h b/3rd_party/libsrp6a-sha512/cstr.h new file mode 100644 index 0000000..7cc019a --- /dev/null +++ b/3rd_party/libsrp6a-sha512/cstr.h @@ -0,0 +1,94 @@ +#ifndef _CSTR_H_ +#define _CSTR_H_ + +/* A general-purpose string "class" for C */ + +#if !defined(P) +#ifdef __STDC__ +#define P(x) x +#else +#define P(x) () +#endif +#endif + +/* For building dynamic link libraries under windows, windows NT + * using MSVC1.5 or MSVC2.0 + */ + +#ifndef _DLLDECL +#define _DLLDECL + +#ifdef MSVC15 /* MSVC1.5 support for 16 bit apps */ +#define _MSVC15EXPORT _export +#define _MSVC20EXPORT +#define _DLLAPI _export _pascal +#define _CDECL +#define _TYPE(a) a _MSVC15EXPORT +#define DLLEXPORT 1 + +#elif defined(MSVC20) || (defined(_USRDLL) && defined(SRP_EXPORTS)) +#define _MSVC15EXPORT +#define _MSVC20EXPORT _declspec(dllexport) +#define _DLLAPI +#define _CDECL +#define _TYPE(a) _MSVC20EXPORT a +#define DLLEXPORT 1 + +#else /* Default, non-dll. Use this for Unix or DOS */ +#define _MSVC15DEXPORT +#define _MSVC20EXPORT +#define _DLLAPI +#if defined(WINDOWS) || defined(WIN32) +#define _CDECL _cdecl +#else +#define _CDECL +#endif +#define _TYPE(a) a _CDECL +#endif +#endif /* _DLLDECL */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Arguments to allocator methods ordered this way for compatibility */ +typedef struct cstr_alloc_st { + void * (_CDECL * alloc)(size_t n, void * heap); + void (_CDECL * free)(void * p, void * heap); + void * heap; +} cstr_allocator; + +typedef struct cstr_st { + char * data; /* Okay to access data and length fields directly */ + int length; + int cap; + int ref; /* Simple reference counter */ + cstr_allocator * allocator; +} cstr; + +_TYPE( void ) cstr_set_allocator P((cstr_allocator * alloc)); + +_TYPE( cstr * ) cstr_new P((void)); +_TYPE( cstr * ) cstr_new_alloc P((cstr_allocator * alloc)); +_TYPE( cstr * ) cstr_dup P((const cstr * str)); +_TYPE( cstr * ) cstr_dup_alloc P((const cstr * str, cstr_allocator * alloc)); +_TYPE( cstr * ) cstr_create P((const char * s)); +_TYPE( cstr * ) cstr_createn P((const char * s, int len)); + +_TYPE( void ) cstr_free P((cstr * str)); +_TYPE( void ) cstr_clear_free P((cstr * str)); +_TYPE( void ) cstr_use P((cstr * str)); +_TYPE( void ) cstr_empty P((cstr * str)); +_TYPE( int ) cstr_copy P((cstr * dst, const cstr * src)); +_TYPE( int ) cstr_set P((cstr * str, const char * s)); +_TYPE( int ) cstr_setn P((cstr * str, const char * s, int len)); +_TYPE( int ) cstr_set_length P((cstr * str, int len)); +_TYPE( int ) cstr_append P((cstr * str, const char * s)); +_TYPE( int ) cstr_appendn P((cstr * str, const char * s, int len)); +_TYPE( int ) cstr_append_str P((cstr * dst, const cstr * src)); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _CSTR_H_ */ diff --git a/3rd_party/libsrp6a-sha512/srp.c b/3rd_party/libsrp6a-sha512/srp.c new file mode 100644 index 0000000..74e1f98 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/srp.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +#include "t_defines.h" +#include "srp.h" + +static int library_initialized = 0; + +_TYPE( SRP_RESULT ) +SRP_initialize_library() +{ + if(library_initialized == 0) { + BigIntegerInitialize(); + t_stronginitrand(); + library_initialized = 1; + } + return SRP_SUCCESS; +} + +_TYPE( SRP_RESULT ) +SRP_finalize_library() +{ + if(library_initialized > 0) { + library_initialized = 0; + BigIntegerFinalize(); + } + return SRP_SUCCESS; +} + +static int srp_modulus_min_bits = SRP_DEFAULT_MIN_BITS; + +_TYPE( SRP_RESULT ) +SRP_set_modulus_min_bits(int minbits) +{ + srp_modulus_min_bits = minbits; + return SRP_SUCCESS; +} + +_TYPE( int ) +SRP_get_modulus_min_bits() +{ + return srp_modulus_min_bits; +} + +static int +default_secret_bits_cb(int modsize) +{ + return 256; + /*return modsize;*/ /* Warning: Very Slow */ +} + +static SRP_SECRET_BITS_CB srp_sb_cb = default_secret_bits_cb; + +_TYPE( SRP_RESULT ) +SRP_set_secret_bits_cb(SRP_SECRET_BITS_CB cb) +{ + srp_sb_cb = cb; + return SRP_SUCCESS; +} + +_TYPE( int ) +SRP_get_secret_bits(int modsize) +{ + return (*srp_sb_cb)(modsize); +} + +_TYPE( SRP * ) +SRP_new(SRP_METHOD * meth) +{ + SRP * srp = (SRP *) malloc(sizeof(SRP)); + + if(srp == NULL) + return NULL; + + srp->flags = 0; + srp->username = cstr_new(); + srp->bctx = BigIntegerCtxNew(); + srp->modulus = NULL; + srp->accel = NULL; + srp->generator = NULL; + srp->salt = NULL; + srp->verifier = NULL; + srp->password = NULL; + srp->pubkey = NULL; + srp->secret = NULL; + srp->u = NULL; + srp->key = NULL; + srp->ex_data = cstr_new(); + srp->param_cb = NULL; + srp->meth = meth; + srp->meth_data = NULL; + //srp->slu = NULL; + if(srp->meth->init == NULL || (*srp->meth->init)(srp) == SRP_SUCCESS) + return srp; + free(srp); + return NULL; +} + +_TYPE( SRP_RESULT ) +SRP_free(SRP * srp) +{ + if(srp->meth->finish) + (*srp->meth->finish)(srp); + + if(srp->username) + cstr_clear_free(srp->username); + if(srp->modulus) + BigIntegerFree(srp->modulus); + if(srp->accel) + BigIntegerModAccelFree(srp->accel); + if(srp->generator) + BigIntegerFree(srp->generator); + if(srp->salt) + cstr_clear_free(srp->salt); + if(srp->verifier) + BigIntegerClearFree(srp->verifier); + if(srp->password) + BigIntegerClearFree(srp->password); + if(srp->pubkey) + BigIntegerFree(srp->pubkey); + if(srp->secret) + BigIntegerClearFree(srp->secret); + if(srp->u) + BigIntegerFree(srp->u); + if(srp->key) + BigIntegerClearFree(srp->key); + if(srp->bctx) + BigIntegerCtxFree(srp->bctx); + if(srp->ex_data) + cstr_clear_free(srp->ex_data); + free(srp); + return SRP_SUCCESS; +} + +_TYPE( SRP_RESULT ) +SRP_set_client_param_verify_cb(SRP * srp, SRP_CLIENT_PARAM_VERIFY_CB cb) +{ + srp->param_cb = cb; + return SRP_SUCCESS; +} + +_TYPE( SRP_RESULT ) +SRP_set_username(SRP * srp, const char * username) +{ + cstr_set(srp->username, username); + return SRP_SUCCESS; +} + +_TYPE( SRP_RESULT ) +SRP_set_user_raw(SRP * srp, const unsigned char * user, int userlen) +{ + cstr_setn(srp->username, (const char*)user, userlen); + return SRP_SUCCESS; +} + +_TYPE( SRP_RESULT ) +SRP_set_params(SRP * srp, const unsigned char * modulus, int modlen, + const unsigned char * generator, int genlen, + const unsigned char * salt, int saltlen) +{ + SRP_RESULT rc; + + if(modulus == NULL || generator == NULL || salt == NULL) + return SRP_ERROR; + + /* Set fields in SRP context */ + srp->modulus = BigIntegerFromBytes(modulus, modlen); + if(srp->flags & SRP_FLAG_MOD_ACCEL) + srp->accel = BigIntegerModAccelNew(srp->modulus, srp->bctx); + srp->generator = BigIntegerFromBytes(generator, genlen); + if(srp->salt == NULL) + srp->salt = cstr_new(); + cstr_setn(srp->salt, (const char*)salt, saltlen); + + /* Now attempt to validate parameters */ + if(BigIntegerBitLen(srp->modulus) < SRP_get_modulus_min_bits()) + return SRP_ERROR; + + if(srp->param_cb) { + rc = (*srp->param_cb)(srp, modulus, modlen, generator, genlen); + if(!SRP_OK(rc)) + return rc; + } + + return (*srp->meth->params)(srp, modulus, modlen, generator, genlen, + salt, saltlen); +} + +_TYPE( SRP_RESULT ) +SRP_set_authenticator(SRP * srp, const unsigned char * a, int alen) +{ + return (*srp->meth->auth)(srp, a, alen); +} + +_TYPE( SRP_RESULT ) +SRP_set_auth_password(SRP * srp, const char * password) +{ + return (*srp->meth->passwd)(srp, (const unsigned char *)password, + strlen(password)); +} + +_TYPE( SRP_RESULT ) +SRP_set_auth_password_raw(SRP * srp, + const unsigned char * password, int passlen) +{ + return (*srp->meth->passwd)(srp, password, passlen); +} + +_TYPE( SRP_RESULT ) +SRP_gen_pub(SRP * srp, cstr ** result) +{ + return (*srp->meth->genpub)(srp, result); +} + +_TYPE( SRP_RESULT ) +SRP_add_ex_data(SRP * srp, const unsigned char * data, int datalen) +{ + cstr_appendn(srp->ex_data, (const char*)data, datalen); + return SRP_SUCCESS; +} + +_TYPE( SRP_RESULT ) +SRP_compute_key(SRP * srp, cstr ** result, + const unsigned char * pubkey, int pubkeylen) +{ + return (*srp->meth->key)(srp, result, pubkey, pubkeylen); +} + +_TYPE( SRP_RESULT ) +SRP_verify(SRP * srp, const unsigned char * proof, int prooflen) +{ + return (*srp->meth->verify)(srp, proof, prooflen); +} + +_TYPE( SRP_RESULT ) +SRP_respond(SRP * srp, cstr ** proof) +{ + return (*srp->meth->respond)(srp, proof); +} + +_TYPE( SRP_RESULT ) +SRP_use_engine(const char * engine) +{ + if(BigIntegerOK(BigIntegerUseEngine(engine))) + return SRP_SUCCESS; + else + return SRP_ERROR; +} diff --git a/3rd_party/libsrp6a-sha512/srp.h b/3rd_party/libsrp6a-sha512/srp.h new file mode 100644 index 0000000..b1d46af --- /dev/null +++ b/3rd_party/libsrp6a-sha512/srp.h @@ -0,0 +1,372 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ +#ifndef _SRP_H_ +#define _SRP_H_ + +#include "cstr.h" +#include "srp_aux.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* SRP library version identification */ +#define SRP_VERSION_MAJOR 2 +#define SRP_VERSION_MINOR 0 +#define SRP_VERSION_PATCHLEVEL 1 + +typedef int SRP_RESULT; +/* Returned codes for SRP API functions */ +#define SRP_OK(v) ((v) == SRP_SUCCESS) +#define SRP_SUCCESS 0 +#define SRP_ERROR -1 + +/* Set the minimum number of bits acceptable in an SRP modulus */ +#define SRP_DEFAULT_MIN_BITS 512 +_TYPE( SRP_RESULT ) SRP_set_modulus_min_bits P((int minbits)); +_TYPE( int ) SRP_get_modulus_min_bits P((void)); + +/* + * Sets the "secret size callback" function. + * This function is called with the modulus size in bits, + * and returns the size of the secret exponent in bits. + * The default function always returns 256 bits. + */ +typedef int (_CDECL * SRP_SECRET_BITS_CB)(int modsize); +_TYPE( SRP_RESULT ) SRP_set_secret_bits_cb P((SRP_SECRET_BITS_CB cb)); +_TYPE( int ) SRP_get_secret_bits P((int modsize)); + +typedef struct srp_st SRP; + +#if 0 +/* Server Lookup API */ +typedef struct srp_server_lu_st SRP_SERVER_LOOKUP; + +typedef struct srp_s_lu_meth_st { + const char * name; + + SRP_RESULT (_CDECL * init)(SRP_SERVER_LOOKUP * slu); + SRP_RESULT (_CDECL * finish)(SRP_SERVER_LOOKUP * slu); + + SRP_RESULT (_CDECL * lookup)(SRP_SERVER_LOOKUP * slu, SRP * srp, cstr * username); + + void * meth_data; +} SRP_SERVER_LOOKUP_METHOD; + +struct srp_server_lu_st { + SRP_SERVER_LOOKUP_METHOD * meth; + void * data; +}; + +/* + * The Server Lookup API deals with the server-side issue of + * mapping usernames to verifiers. Given a username, a lookup + * mechanism needs to provide parameters (N, g), salt (s), and + * password verifier (v) for that user. + * + * A SRP_SERVER_LOOKUP_METHOD describes the general mechanism + * for performing lookups (e.g. files, LDAP, database, etc.) + * A SRP_SERVER_LOOKUP is an active "object" that is actually + * called to do lookups. + */ +_TYPE( SRP_SERVER_LOOKUP * ) + SRP_SERVER_LOOKUP_new P((SRP_SERVER_LOOKUP_METHOD * meth)); +_TYPE( SRP_RESULT ) SRP_SERVER_LOOKUP_free P((SRP_SERVER_LOOKUP * slu)); +_TYPE( SRP_RESULT ) SRP_SERVER_do_lookup P((SRP_SERVER_LOOKUP * slu, + SRP * srp, cstr * username)); + +/* + * SRP_SERVER_system_lookup supercedes SRP_server_init_user. + */ +_TYPE( SRP_SERVER_LOOKUP * ) SRP_SERVER_system_lookup P((void)); +#endif + +/* + * Client Parameter Verification API + * + * This callback is called from the SRP client when the + * parameters (modulus and generator) are set. The callback + * should return SRP_SUCCESS if the parameters are okay, + * otherwise some error code to indicate that the parameters + * should be rejected. + */ +typedef SRP_RESULT (_CDECL * SRP_CLIENT_PARAM_VERIFY_CB)(SRP * srp, const unsigned char * mod, int modlen, const unsigned char * gen, int genlen); + +#if 0 +/* The default parameter verifier function */ +_TYPE( SRP_RESULT ) SRP_CLIENT_default_param_verify_cb(SRP * srp, const unsigned char * mod, int modlen, const unsigned char * gen, int genlen); +/* A parameter verifier that only accepts builtin params (no prime test) */ +_TYPE( SRP_RESULT ) SRP_CLIENT_builtin_param_verify_cb(SRP * srp, const unsigned char * mod, int modlen, const unsigned char * gen, int genlen); +/* The "classic" parameter verifier that accepts either builtin params + * immediately, and performs safe-prime tests on N and primitive-root + * tests on g otherwise. SECURITY WARNING: This may allow for certain + * attacks based on "trapdoor" moduli, so this is not recommended. */ +_TYPE( SRP_RESULT ) SRP_CLIENT_compat_param_verify_cb(SRP * srp, const unsigned char * mod, int modlen, const unsigned char * gen, int genlen); + +#endif + +/* + * Main SRP API - SRP and SRP_METHOD + */ + +/* SRP method definitions */ +typedef struct srp_meth_st { + const char * name; + + SRP_RESULT (_CDECL * init)(SRP * srp); + SRP_RESULT (_CDECL * finish)(SRP * srp); + + SRP_RESULT (_CDECL * params)(SRP * srp, + const unsigned char * modulus, int modlen, + const unsigned char * generator, int genlen, + const unsigned char * salt, int saltlen); + SRP_RESULT (_CDECL * auth)(SRP * srp, const unsigned char * a, int alen); + SRP_RESULT (_CDECL * passwd)(SRP * srp, + const unsigned char * pass, int passlen); + SRP_RESULT (_CDECL * genpub)(SRP * srp, cstr ** result); + SRP_RESULT (_CDECL * key)(SRP * srp, cstr ** result, + const unsigned char * pubkey, int pubkeylen); + SRP_RESULT (_CDECL * verify)(SRP * srp, + const unsigned char * proof, int prooflen); + SRP_RESULT (_CDECL * respond)(SRP * srp, cstr ** proof); + + void * data; +} SRP_METHOD; + +/* Magic numbers for the SRP context header */ +#define SRP_MAGIC_CLIENT 12 +#define SRP_MAGIC_SERVER 28 + +/* Flag bits for SRP struct */ +#define SRP_FLAG_MOD_ACCEL 0x1 /* accelerate modexp operations */ +#define SRP_FLAG_LEFT_PAD 0x2 /* left-pad to length-of-N inside hashes */ + +/* + * A hybrid structure that represents either client or server state. + */ +struct srp_st { + int magic; /* To distinguish client from server (and for sanity) */ + + int flags; + + cstr * username; + + BigInteger modulus; + BigInteger generator; + cstr * salt; + + BigInteger verifier; + BigInteger password; + + BigInteger pubkey; + BigInteger secret; + BigInteger u; + + BigInteger key; + + cstr * ex_data; + + SRP_METHOD * meth; + void * meth_data; + + BigIntegerCtx bctx; /* to cache temporaries if available */ + BigIntegerModAccel accel; /* to accelerate modexp if available */ + + SRP_CLIENT_PARAM_VERIFY_CB param_cb; /* to verify params */ + //SRP_SERVER_LOOKUP * slu; /* to look up users */ +}; + +/* + * Global initialization/de-initialization functions. + * Call SRP_initialize_library before using the library, + * and SRP_finalize_library when done. + */ +_TYPE( SRP_RESULT ) SRP_initialize_library(); +_TYPE( SRP_RESULT ) SRP_finalize_library(); + +/* + * SRP_new() creates a new SRP context object - + * the method determines which "sense" (client or server) + * the object operates in. SRP_free() frees it. + * (See RFC2945 method definitions below.) + */ +_TYPE( SRP * ) SRP_new P((SRP_METHOD * meth)); +_TYPE( SRP_RESULT ) SRP_free P((SRP * srp)); + +#if 0 +/* + * Use the supplied lookup object to look up user parameters and + * password verifier. The lookup function gets called during + * SRP_set_username/SRP_set_user_raw below. Using this function + * means that the server can avoid calling SRP_set_params and + * SRP_set_authenticator, since the lookup function handles that + * internally. + */ +_TYPE( SRP_RESULT ) SRP_set_server_lookup P((SRP * srp, + SRP_SERVER_LOOKUP * lookup)); +#endif + +/* + * Use the supplied callback function to verify parameters + * (modulus, generator) given to the client. + */ +_TYPE( SRP_RESULT ) + SRP_set_client_param_verify_cb P((SRP * srp, + SRP_CLIENT_PARAM_VERIFY_CB cb)); + +/* + * Both client and server must call both SRP_set_username and + * SRP_set_params, in that order, before calling anything else. + * SRP_set_user_raw is an alternative to SRP_set_username that + * accepts an arbitrary length-bounded octet string as input. + */ +_TYPE( SRP_RESULT ) SRP_set_username P((SRP * srp, const char * username)); +_TYPE( SRP_RESULT ) SRP_set_user_raw P((SRP * srp, const unsigned char * user, + int userlen)); +_TYPE( SRP_RESULT ) + SRP_set_params P((SRP * srp, + const unsigned char * modulus, int modlen, + const unsigned char * generator, int genlen, + const unsigned char * salt, int saltlen)); + +/* + * On the client, SRP_set_authenticator, SRP_gen_exp, and + * SRP_add_ex_data can be called in any order. + * On the server, SRP_set_authenticator must come first, + * followed by SRP_gen_exp and SRP_add_ex_data in either order. + */ +/* + * The authenticator is the secret possessed by either side. + * For the server, this is the bigendian verifier, as an octet string. + * For the client, this is the bigendian raw secret, as an octet string. + * The server's authenticator must be the generator raised to the power + * of the client's raw secret modulo the common modulus for authentication + * to succeed. + * + * SRP_set_auth_password computes the authenticator from a plaintext + * password and then calls SRP_set_authenticator automatically. This is + * usually used on the client side, while the server usually uses + * SRP_set_authenticator (since it doesn't know the plaintext password). + */ +_TYPE( SRP_RESULT ) + SRP_set_authenticator P((SRP * srp, const unsigned char * a, int alen)); +_TYPE( SRP_RESULT ) + SRP_set_auth_password P((SRP * srp, const char * password)); +_TYPE( SRP_RESULT ) + SRP_set_auth_password_raw P((SRP * srp, + const unsigned char * password, + int passlen)); + +/* + * SRP_gen_pub generates the random exponential residue to send + * to the other side. If using SRP-3/RFC2945, the server must + * withhold its result until it receives the client's number. + * If using SRP-6, the server can send its value immediately + * without waiting for the client. + * + * If "result" points to a NULL pointer, a new cstr object will be + * created to hold the result, and "result" will point to it. + * If "result" points to a non-NULL cstr pointer, the result will be + * placed there. + * If "result" itself is NULL, no result will be returned, + * although the big integer value will still be available + * through srp->pubkey in the SRP struct. + */ +_TYPE( SRP_RESULT ) SRP_gen_pub P((SRP * srp, cstr ** result)); +/* + * Append the data to the extra data segment. Authentication will + * not succeed unless both sides add precisely the same data in + * the same order. + */ +_TYPE( SRP_RESULT ) SRP_add_ex_data P((SRP * srp, const unsigned char * data, + int datalen)); + +/* + * SRP_compute_key must be called after the previous three methods. + */ +_TYPE( SRP_RESULT ) SRP_compute_key P((SRP * srp, cstr ** result, + const unsigned char * pubkey, + int pubkeylen)); + +/* + * On the client, call SRP_respond first to get the response to send + * to the server, and call SRP_verify to verify the server's response. + * On the server, call SRP_verify first to verify the client's response, + * and call SRP_respond ONLY if verification succeeds. + * + * It is an error to call SRP_respond with a NULL pointer. + */ +_TYPE( SRP_RESULT ) SRP_verify P((SRP * srp, + const unsigned char * proof, int prooflen)); +_TYPE( SRP_RESULT ) SRP_respond P((SRP * srp, cstr ** response)); + +/* RFC2945-style SRP authentication */ + +#define RFC2945_KEY_LEN 40 /* length of session key (bytes) */ +#define RFC2945_RESP_LEN 20 /* length of proof hashes (bytes) */ + +/* + * RFC2945-style SRP authentication methods. Use these like: + * SRP * srp = SRP_new(SRP_RFC2945_client_method()); + */ +_TYPE( SRP_METHOD * ) SRP_RFC2945_client_method P((void)); +_TYPE( SRP_METHOD * ) SRP_RFC2945_server_method P((void)); + +/* + * SRP-6 and SRP-6a authentication methods. + * SRP-6a is recommended for better resistance to 2-for-1 attacks. + */ +_TYPE( SRP_METHOD * ) SRP6_client_method P((void)); +_TYPE( SRP_METHOD * ) SRP6_server_method P((void)); +_TYPE( SRP_METHOD * ) SRP6a_client_method P((void)); +_TYPE( SRP_METHOD * ) SRP6a_server_method P((void)); + +_TYPE( SRP_METHOD * ) SRP6a_sha512_client_method P((void)); + +/* + * Convenience function - SRP_server_init_user + * Looks up the username from the system EPS configuration and calls + * SRP_set_username, SRP_set_params, and SRP_set_authenticator to + * initialize server state for that user. + * + * This is deprecated in favor of SRP_SERVER_system_lookup() and + * the Server Lookup API. + */ +_TYPE( SRP_RESULT ) SRP_server_init_user P((SRP * srp, const char * username)); + +/* + * Use the named engine for acceleration. + */ +_TYPE( SRP_RESULT ) SRP_use_engine P((const char * engine)); + +#ifdef __cplusplus +} +#endif + +#endif /* _SRP_H_ */ diff --git a/3rd_party/libsrp6a-sha512/srp6a_sha512_client.c b/3rd_party/libsrp6a-sha512/srp6a_sha512_client.c new file mode 100644 index 0000000..db59fe8 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/srp6a_sha512_client.c @@ -0,0 +1,363 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ +#include "t_defines.h" +#include "srp.h" +#include "t_sha.h" + +/* + * SRP-6/6a has two minor refinements relative to SRP-3/RFC2945: + * 1. The "g^x" value is multipled by three in the client's + * calculation of its session key. + * SRP-6a: The "g^x" value is multiplied by the hash of + * N and g in the client's session key calculation. + * 2. The value of u is taken as the hash of A and B, + * instead of the top 32 bits of the hash of B. + * This eliminates the old restriction where the + * server had to receive A before it could send B. + */ + +/****************************/ +#define SHA512_DIGESTSIZE 64 +#define SRP6_SHA512_KEY_LEN 64 + +/* + * The client keeps track of the running hash + * state via SHA512_CTX structures pointed to by the + * meth_data pointer. The "hash" member is the hash value that + * will be sent to the other side; the "ckhash" member is the + * hash value expected from the other side. + */ +struct sha512_client_meth_st { + SHA512_CTX hash; + SHA512_CTX ckhash; + unsigned char k[SRP6_SHA512_KEY_LEN]; +}; + +#define SHA512_CLIENT_CTXP(srp) ((struct sha512_client_meth_st *)(srp)->meth_data) + +static SRP_RESULT +srp6a_sha512_client_init(SRP * srp) +{ + srp->magic = SRP_MAGIC_CLIENT; + srp->flags = SRP_FLAG_MOD_ACCEL | SRP_FLAG_LEFT_PAD; + srp->meth_data = malloc(sizeof(struct sha512_client_meth_st)); + SHA512Init(&SHA512_CLIENT_CTXP(srp)->hash); + SHA512Init(&SHA512_CLIENT_CTXP(srp)->ckhash); + return SRP_SUCCESS; +} + +static SRP_RESULT +srp6_sha512_client_finish(SRP * srp) +{ + if(srp->meth_data) { + memset(srp->meth_data, 0, sizeof(struct sha512_client_meth_st)); + free(srp->meth_data); + } + return SRP_SUCCESS; +} + +static SRP_RESULT +srp6_sha512_client_params(SRP * srp, const unsigned char * modulus, int modlen, + const unsigned char * generator, int genlen, + const unsigned char * salt, int saltlen) +{ + int i; + unsigned char buf1[SHA512_DIGESTSIZE], buf2[SHA512_DIGESTSIZE]; + SHA512_CTX ctxt; + + /* Fields set by SRP_set_params */ + + /* Update hash state */ + SHA512Init(&ctxt); + SHA512Update(&ctxt, modulus, modlen); + SHA512Final(buf1, &ctxt); /* buf1 = H(modulus) */ + + SHA512Init(&ctxt); + SHA512Update(&ctxt, generator, genlen); + SHA512Final(buf2, &ctxt); /* buf2 = H(generator) */ + + for(i = 0; i < sizeof(buf1); ++i) + buf1[i] ^= buf2[i]; /* buf1 = H(modulus) xor H(generator) */ + + /* hash: H(N) xor H(g) */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->hash, buf1, sizeof(buf1)); + + SHA512Init(&ctxt); + SHA512Update(&ctxt, srp->username->data, srp->username->length); + SHA512Final(buf1, &ctxt); /* buf1 = H(user) */ + + /* hash: (H(N) xor H(g)) | H(U) */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->hash, buf1, sizeof(buf1)); + + /* hash: (H(N) xor H(g)) | H(U) | s */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->hash, salt, saltlen); + + return SRP_SUCCESS; +} + +static SRP_RESULT +srp6_sha512_client_auth(SRP * srp, const unsigned char * a, int alen) +{ + /* On the client, the authenticator is the raw password-derived hash */ + srp->password = BigIntegerFromBytes(a, alen); + + /* verifier = g^x mod N */ + srp->verifier = BigIntegerFromInt(0); + BigIntegerModExp(srp->verifier, srp->generator, srp->password, srp->modulus, srp->bctx, srp->accel); + + return SRP_SUCCESS; +} + +static SRP_RESULT +srp6_sha512_client_passwd(SRP * srp, const unsigned char * p, int plen) +{ + SHA512_CTX ctxt; + unsigned char dig[SHA512_DIGESTSIZE]; + int r; + + SHA512Init(&ctxt); + SHA512Update(&ctxt, srp->username->data, srp->username->length); + SHA512Update(&ctxt, ":", 1); + SHA512Update(&ctxt, p, plen); + SHA512Final(dig, &ctxt); /* dig = H(U | ":" | P) */ + + SHA512Init(&ctxt); + SHA512Update(&ctxt, srp->salt->data, srp->salt->length); + SHA512Update(&ctxt, dig, sizeof(dig)); + SHA512Final(dig, &ctxt); /* dig = H(s | H(U | ":" | P)) */ + memset(&ctxt, 0, sizeof(ctxt)); + + r = SRP_set_authenticator(srp, dig, sizeof(dig)); + memset(dig, 0, sizeof(dig)); + + return r; +} + +static SRP_RESULT +srp6_sha512_client_genpub(SRP * srp, cstr ** result) +{ + cstr * astr; + int slen = (SRP_get_secret_bits(BigIntegerBitLen(srp->modulus)) + 7) / 8; + + if(result == NULL) + astr = cstr_new(); + else { + if(*result == NULL) + *result = cstr_new(); + astr = *result; + } + + cstr_set_length(astr, BigIntegerByteLen(srp->modulus)); + t_random((unsigned char*)astr->data, slen); + srp->secret = BigIntegerFromBytes((const unsigned char*)astr->data, slen); + /* Force g^a mod n to "wrap around" by adding log[2](n) to "a". */ + BigIntegerAddInt(srp->secret, srp->secret, BigIntegerBitLen(srp->modulus)); + /* A = g^a mod n */ + srp->pubkey = BigIntegerFromInt(0); + BigIntegerModExp(srp->pubkey, srp->generator, srp->secret, srp->modulus, srp->bctx, srp->accel); + BigIntegerToCstr(srp->pubkey, astr); + + /* hash: (H(N) xor H(g)) | H(U) | s | A */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->hash, astr->data, astr->length); + /* ckhash: A */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->ckhash, astr->data, astr->length); + + if(result == NULL) /* astr was a temporary */ + cstr_clear_free(astr); + + return SRP_SUCCESS; +} + +static SRP_RESULT +srp6_sha512_client_key_ex(SRP * srp, cstr ** result, + const unsigned char * pubkey, int pubkeylen, BigInteger k) +{ + SHA512_CTX ctxt; + unsigned char dig[SHA512_DIGESTSIZE]; + BigInteger gb, e; + cstr * s; + int modlen; + + modlen = BigIntegerByteLen(srp->modulus); + if(pubkeylen > modlen) + return SRP_ERROR; + + /* Compute u from client's and server's values */ + SHA512Init(&ctxt); + /* Use s as a temporary to store client's value */ + s = cstr_new(); + if(srp->flags & SRP_FLAG_LEFT_PAD) { + BigIntegerToCstrEx(srp->pubkey, s, modlen); + SHA512Update(&ctxt, s->data, s->length); + if(pubkeylen < modlen) { + memcpy(s->data + (modlen - pubkeylen), pubkey, pubkeylen); + memset(s->data, 0, modlen - pubkeylen); + SHA512Update(&ctxt, s->data, modlen); + } + else + SHA512Update(&ctxt, pubkey, pubkeylen); + } + else { + BigIntegerToCstr(srp->pubkey, s); + SHA512Update(&ctxt, s->data, s->length); + SHA512Update(&ctxt, pubkey, pubkeylen); + } + SHA512Final(dig, &ctxt); + srp->u = BigIntegerFromBytes(dig, SHA512_DIGESTSIZE); + + /* hash: (H(N) xor H(g)) | H(U) | s | A | B */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->hash, pubkey, pubkeylen); + + gb = BigIntegerFromBytes(pubkey, pubkeylen); + /* reject B == 0, B >= modulus */ + if(BigIntegerCmp(gb, srp->modulus) >= 0 || BigIntegerCmpInt(gb, 0) == 0) { + BigIntegerFree(gb); + cstr_clear_free(s); + return SRP_ERROR; + } + e = BigIntegerFromInt(0); + srp->key = BigIntegerFromInt(0); + /* unblind g^b (mod N) */ + BigIntegerSub(srp->key, srp->modulus, srp->verifier); + /* use e as temporary, e == -k*v (mod N) */ + BigIntegerMul(e, k, srp->key, srp->bctx); + BigIntegerAdd(e, e, gb); + BigIntegerMod(gb, e, srp->modulus, srp->bctx); + + /* compute gb^(a + ux) (mod N) */ + BigIntegerMul(e, srp->password, srp->u, srp->bctx); + BigIntegerAdd(e, e, srp->secret); /* e = a + ux */ + + BigIntegerModExp(srp->key, gb, e, srp->modulus, srp->bctx, srp->accel); + BigIntegerClearFree(e); + BigIntegerClearFree(gb); + + /* convert srp->key into a session key, update hash states */ + BigIntegerToCstr(srp->key, s); + SHA512Init(&ctxt); + SHA512Update(&ctxt, s->data, s->length); + SHA512Final((unsigned char*)&SHA512_CLIENT_CTXP(srp)->k, &ctxt); + cstr_clear_free(s); + + /* hash: (H(N) xor H(g)) | H(U) | s | A | B | K */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->hash, SHA512_CLIENT_CTXP(srp)->k, SRP6_SHA512_KEY_LEN); + /* hash: (H(N) xor H(g)) | H(U) | s | A | B | K | ex_data */ + if(srp->ex_data->length > 0) + SHA512Update(&SHA512_CLIENT_CTXP(srp)->hash, + srp->ex_data->data, srp->ex_data->length); + if(result) { + if(*result == NULL) + *result = cstr_new(); + cstr_setn(*result, (const char*)SHA512_CLIENT_CTXP(srp)->k, SRP6_SHA512_KEY_LEN); + } + + return SRP_SUCCESS; +} + +static SRP_RESULT +srp6a_sha512_client_key(SRP * srp, cstr ** result, + const unsigned char * pubkey, int pubkeylen) +{ + SRP_RESULT ret; + BigInteger k; + cstr * s; + SHA512_CTX ctxt; + unsigned char dig[SHA512_DIGESTSIZE]; + + SHA512Init(&ctxt); + s = cstr_new(); + BigIntegerToCstr(srp->modulus, s); + SHA512Update(&ctxt, s->data, s->length); + if(srp->flags & SRP_FLAG_LEFT_PAD) + BigIntegerToCstrEx(srp->generator, s, s->length); + else + BigIntegerToCstr(srp->generator, s); + SHA512Update(&ctxt, s->data, s->length); + SHA512Final(dig, &ctxt); + cstr_free(s); + + k = BigIntegerFromBytes(dig, SHA512_DIGESTSIZE); + if(BigIntegerCmpInt(k, 0) == 0) + ret = SRP_ERROR; + else + ret = srp6_sha512_client_key_ex(srp, result, pubkey, pubkeylen, k); + BigIntegerClearFree(k); + return ret; +} + +static SRP_RESULT +srp6_sha512_client_verify(SRP * srp, const unsigned char * proof, int prooflen) +{ + unsigned char expected[SHA512_DIGESTSIZE]; + + SHA512Final(expected, &SHA512_CLIENT_CTXP(srp)->ckhash); + if(prooflen == SHA512_DIGESTSIZE && memcmp(expected, proof, prooflen) == 0) + return SRP_SUCCESS; + else + return SRP_ERROR; +} + +static SRP_RESULT +srp6_sha512_client_respond(SRP * srp, cstr ** proof) +{ + if(proof == NULL) + return SRP_ERROR; + + if(*proof == NULL) + *proof = cstr_new(); + + /* proof contains client's response */ + cstr_set_length(*proof, SHA512_DIGESTSIZE); + SHA512Final((unsigned char*)(*proof)->data, &SHA512_CLIENT_CTXP(srp)->hash); + + /* ckhash: A | M | K */ + SHA512Update(&SHA512_CLIENT_CTXP(srp)->ckhash, (*proof)->data, (*proof)->length); + SHA512Update(&SHA512_CLIENT_CTXP(srp)->ckhash, SHA512_CLIENT_CTXP(srp)->k, SRP6_SHA512_KEY_LEN); + return SRP_SUCCESS; +} + +static SRP_METHOD srp6a_sha512_client_meth = { + "SRP-6a sha512 client (tjw)", + srp6a_sha512_client_init, + srp6_sha512_client_finish, + srp6_sha512_client_params, + srp6_sha512_client_auth, + srp6_sha512_client_passwd, + srp6_sha512_client_genpub, + srp6a_sha512_client_key, + srp6_sha512_client_verify, + srp6_sha512_client_respond, + NULL +}; + +_TYPE( SRP_METHOD * ) +SRP6a_sha512_client_method() +{ + return &srp6a_sha512_client_meth; +} diff --git a/3rd_party/libsrp6a-sha512/srp_aux.h b/3rd_party/libsrp6a-sha512/srp_aux.h new file mode 100644 index 0000000..5088f08 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/srp_aux.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +#ifndef SRP_AUX_H +#define SRP_AUX_H + +#include "cstr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* BigInteger abstraction API */ + +#ifndef MATH_PRIV +typedef void * BigInteger; +typedef void * BigIntegerCtx; +typedef void * BigIntegerModAccel; +#endif + +/* + * Some functions return a BigIntegerResult. + * Use BigIntegerOK to test for success. + */ +#define BIG_INTEGER_SUCCESS 0 +#define BIG_INTEGER_ERROR -1 +#define BigIntegerOK(v) ((v) == BIG_INTEGER_SUCCESS) +typedef int BigIntegerResult; + +_TYPE( BigInteger ) BigIntegerFromInt P((unsigned int number)); +_TYPE( BigInteger ) BigIntegerFromBytes P((const unsigned char * bytes, + int length)); +#define BigIntegerByteLen(X) ((BigIntegerBitLen(X)+7)/8) +_TYPE( int ) BigIntegerToBytes P((BigInteger src, + unsigned char * dest, int destlen)); +_TYPE( BigIntegerResult ) BigIntegerToCstr P((BigInteger src, cstr * dest)); +_TYPE( BigIntegerResult ) BigIntegerToCstrEx P((BigInteger src, cstr * dest, int len)); +_TYPE( BigIntegerResult ) BigIntegerToHex P((BigInteger src, + char * dest, int destlen)); +_TYPE( BigIntegerResult ) BigIntegerToString P((BigInteger src, + char * dest, int destlen, + unsigned int radix)); +_TYPE( int ) BigIntegerBitLen P((BigInteger b)); +_TYPE( int ) BigIntegerCmp P((BigInteger c1, BigInteger c2)); +_TYPE( int ) BigIntegerCmpInt P((BigInteger c1, unsigned int c2)); +_TYPE( BigIntegerResult ) BigIntegerLShift P((BigInteger result, BigInteger x, + unsigned int bits)); +_TYPE( BigIntegerResult ) BigIntegerAdd P((BigInteger result, + BigInteger a1, BigInteger a2)); +_TYPE( BigIntegerResult ) BigIntegerAddInt P((BigInteger result, + BigInteger a1, unsigned int a2)); +_TYPE( BigIntegerResult ) BigIntegerSub P((BigInteger result, + BigInteger s1, BigInteger s2)); +_TYPE( BigIntegerResult ) BigIntegerSubInt P((BigInteger result, + BigInteger s1, unsigned int s2)); +/* For BigIntegerMul{,Int}: result != m1, m2 */ +_TYPE( BigIntegerResult ) BigIntegerMul P((BigInteger result, BigInteger m1, + BigInteger m2, BigIntegerCtx ctx)); +_TYPE( BigIntegerResult ) BigIntegerMulInt P((BigInteger result, + BigInteger m1, unsigned int m2, + BigIntegerCtx ctx)); +_TYPE( BigIntegerResult ) BigIntegerDivInt P((BigInteger result, + BigInteger d, unsigned int m, + BigIntegerCtx ctx)); +_TYPE( BigIntegerResult ) BigIntegerMod P((BigInteger result, BigInteger d, + BigInteger m, BigIntegerCtx ctx)); +_TYPE( unsigned int ) BigIntegerModInt P((BigInteger d, unsigned int m, + BigIntegerCtx ctx)); +_TYPE( BigIntegerResult ) BigIntegerModMul P((BigInteger result, + BigInteger m1, BigInteger m2, + BigInteger m, BigIntegerCtx ctx)); +_TYPE( BigIntegerResult ) BigIntegerModExp P((BigInteger result, + BigInteger base, BigInteger expt, + BigInteger modulus, + BigIntegerCtx ctx, + BigIntegerModAccel accel)); +_TYPE( int ) BigIntegerCheckPrime P((BigInteger n, BigIntegerCtx ctx)); + +_TYPE( BigIntegerResult ) BigIntegerFree P((BigInteger b)); +_TYPE( BigIntegerResult ) BigIntegerClearFree P((BigInteger b)); + +_TYPE( BigIntegerCtx ) BigIntegerCtxNew(); +_TYPE( BigIntegerResult ) BigIntegerCtxFree P((BigIntegerCtx ctx)); + +_TYPE( BigIntegerModAccel ) BigIntegerModAccelNew P((BigInteger m, + BigIntegerCtx ctx)); +_TYPE( BigIntegerResult ) BigIntegerModAccelFree P((BigIntegerModAccel accel)); + +_TYPE( BigIntegerResult ) BigIntegerInitialize(); +_TYPE( BigIntegerResult ) BigIntegerFinalize(); + +_TYPE( BigIntegerResult ) BigIntegerUseEngine P((const char * engine)); +_TYPE( BigIntegerResult ) BigIntegerReleaseEngine(); + +/* Miscellaneous functions - formerly in t_pwd.h */ + +/* + * "t_random" is a cryptographic random number generator, which is seeded + * from various high-entropy sources and uses a one-way hash function + * in a feedback configuration. + * "t_sessionkey" is the interleaved hash used to generate session keys + * from a large integer. + * "t_mgf1" is an implementation of MGF1 using SHA1 to generate session + * keys from large integers, and is preferred over the older + * interleaved hash, and is used with SRP6. + * "t_getpass" reads a password from the terminal without echoing. + */ +_TYPE( void ) t_random P((unsigned char *, unsigned)); +_TYPE( void ) t_stronginitrand(); +_TYPE( unsigned char * ) + t_sessionkey P((unsigned char *, unsigned char *, unsigned)); +_TYPE( void ) t_mgf1 P((unsigned char *, unsigned, + const unsigned char *, unsigned)); +_TYPE( int ) t_getpass P((char *, unsigned, const char *)); + +#ifdef __cplusplus +} +#endif + +#endif /* SRP_AUX_H */ diff --git a/3rd_party/libsrp6a-sha512/t_conv.c b/3rd_party/libsrp6a-sha512/t_conv.c new file mode 100644 index 0000000..76d4e58 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_conv.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +/*#define _POSIX_SOURCE*/ +#include <stdio.h> +#include "t_defines.h" +#include "cstr.h" + +static int +hexDigitToInt(char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if(c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; +} + +/* + * Convert a hex string to a string of bytes; return size of dst + */ +_TYPE( int ) +t_fromhex(char *dst, const char *src) +{ + register char *chp = dst; + register unsigned size = strlen(src); + + /* FIXME: handle whitespace and non-hex digits by setting size and src + appropriately. */ + + if(size % 2 == 1) { + *chp++ = hexDigitToInt(*src++); + --size; + } + while(size > 0) { + *chp++ = (hexDigitToInt(*src) << 4) | hexDigitToInt(*(src + 1)); + src += 2; + size -= 2; + } + return chp - dst; +} + +/* + * Convert a string of bytes to their hex representation + */ +_TYPE( char * ) +t_tohex(char *dst, const char *src, unsigned size) +{ + int notleading = 0; + + register char *chp = dst; + *dst = '\0'; + if (size != 0) do { + if(notleading || *src != '\0') { + if(!notleading && (*src & 0xf0) == 0) { + sprintf(chp, "%.1X", * (unsigned char *) src); + chp += 1; + } + else { + sprintf(chp, "%.2X", * (unsigned char *) src); + chp += 2; + } + notleading = 1; + } + ++src; + } while (--size != 0); + return dst; +} + +_TYPE( char * ) +t_tohexcstr(cstr *dst, const char *src, unsigned size) +{ + cstr_set_length(dst, 2 * size + 1); + return t_tohex(dst->data, src, size); +} + +static char b64table[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./"; + +/* + * Convert a base64 string into raw byte array representation. + */ +_TYPE( int ) +t_fromb64(char *dst, const char *src) +{ + unsigned char *a; + char *loc; + int i, j; + unsigned int size; + + while(*src && (*src == ' ' || *src == '\t' || *src == '\n')) + ++src; + size = strlen(src); + + a = malloc((size + 1) * sizeof(unsigned char)); + if(a == (unsigned char *) 0) + return -1; + + i = 0; + while(i < size) { + loc = strchr(b64table, src[i]); + if(loc == (char *) 0) + break; + else + a[i] = loc - b64table; + ++i; + } + size = i; + + i = size - 1; + j = size; + while(1) { + a[j] = a[i]; + if(--i < 0) + break; + a[j] |= (a[i] & 3) << 6; + --j; + a[j] = (unsigned char) ((a[i] & 0x3c) >> 2); + if(--i < 0) + break; + a[j] |= (a[i] & 0xf) << 4; + --j; + a[j] = (unsigned char) ((a[i] & 0x30) >> 4); + if(--i < 0) + break; + a[j] |= (a[i] << 2); + + a[--j] = 0; + if(--i < 0) + break; + } + + while(a[j] == 0 && j <= size) + ++j; + + memcpy(dst, a + j, size - j + 1); + free(a); + return size - j + 1; +} + +_TYPE( int ) +t_cstrfromb64(cstr *dst, const char *src) +{ + int len; + cstr_set_length(dst, (strlen(src) * 6 + 7) / 8); + len = t_fromb64(dst->data, src); + cstr_set_length(dst, len); + return len; +} + +/* + * Convert a raw byte string into a null-terminated base64 ASCII string. + */ +_TYPE( char * ) +t_tob64(char *dst, const char *src, unsigned size) +{ + int c, pos = size % 3; + unsigned char b0 = 0, b1 = 0, b2 = 0, notleading = 0; + char *olddst = dst; + + switch(pos) { + case 1: + b2 = src[0]; + break; + case 2: + b1 = src[0]; + b2 = src[1]; + break; + } + + while(1) { + c = (b0 & 0xfc) >> 2; + if(notleading || c != 0) { + *dst++ = b64table[c]; + notleading = 1; + } + c = ((b0 & 3) << 4) | ((b1 & 0xf0) >> 4); + if(notleading || c != 0) { + *dst++ = b64table[c]; + notleading = 1; + } + c = ((b1 & 0xf) << 2) | ((b2 & 0xc0) >> 6); + if(notleading || c != 0) { + *dst++ = b64table[c]; + notleading = 1; + } + c = b2 & 0x3f; + if(notleading || c != 0) { + *dst++ = b64table[c]; + notleading = 1; + } + if(pos >= size) + break; + else { + b0 = src[pos++]; + b1 = src[pos++]; + b2 = src[pos++]; + } + } + + *dst++ = '\0'; + return olddst; +} + +_TYPE( char * ) +t_tob64cstr(cstr *dst, const char *src, unsigned int sz) +{ + cstr_set_length(dst, (sz * 8 + 5) / 6 + 1); + return t_tob64(dst->data, src, sz); +} diff --git a/3rd_party/libsrp6a-sha512/t_defines.h b/3rd_party/libsrp6a-sha512/t_defines.h new file mode 100644 index 0000000..447263f --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_defines.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +#ifndef T_DEFINES_H +#define T_DEFINES_H + +#ifndef P +#if defined(__STDC__) || defined(__cplusplus) +#define P(x) x +#else +#define P(x) () +#endif +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifndef _DLLDECL +#define _DLLDECL + +#ifdef MSVC15 /* MSVC1.5 support for 16 bit apps */ +#define _MSVC15EXPORT _export +#define _MSVC20EXPORT +#define _DLLAPI _export _pascal +#define _CDECL +#define _TYPE(a) a _MSVC15EXPORT +#define DLLEXPORT 1 + +#elif defined(MSVC20) || (defined(_USRDLL) && defined(SRP_EXPORTS)) +#define _MSVC15EXPORT +#define _MSVC20EXPORT _declspec(dllexport) +#define _DLLAPI +#define _CDECL +#define _TYPE(a) _MSVC20EXPORT a +#define DLLEXPORT 1 + +#else /* Default, non-dll. Use this for Unix or DOS */ +#define _MSVC15DEXPORT +#define _MSVC20EXPORT +#define _DLLAPI +#if defined(WINDOWS) || defined(WIN32) +#define _CDECL _cdecl +#else +#define _CDECL +#endif +#define _TYPE(a) a _CDECL +#endif +#endif + +#if STDC_HEADERS +#include <stdlib.h> +#include <string.h> +#else /* not STDC_HEADERS */ +#ifndef HAVE_STRCHR +#define strchr index +#define strrchr rindex +#endif +char *strchr(), *strrchr(), *strtok(); +#ifndef HAVE_MEMCPY +#define memcpy(d, s, n) bcopy((s), (d), (n)) +#endif +#endif /* not STDC_HEADERS */ + +#include <sys/types.h> + +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else /* not TIME_WITH_SYS_TIME */ +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif /* not TIME_WITH_SYS_TIME */ + +#if HAVE_TERMIOS_H +#include <termios.h> +#define STTY(fd, termio) tcsetattr(fd, TCSANOW, termio) +#define GTTY(fd, termio) tcgetattr(fd, termio) +#define TERMIO struct termios +#define USE_TERMIOS +#elif HAVE_TERMIO_H +#include <sys/ioctl.h> +#include <termio.h> +#define STTY(fd, termio) ioctl(fd, TCSETA, termio) +#define GTTY(fd, termio) ioctl(fd, TCGETA, termio) +#define TEMRIO struct termio +#define USE_TERMIO +#elif HAVE_SGTTY_H +#include <sgtty.h> +#define STTY(fd, termio) stty(fd, termio) +#define GTTY(fd, termio) gtty(fd, termio) +#define TERMIO struct sgttyb +#define USE_SGTTY +#endif + +#ifdef WIN32 +#define USE_FTIME 1 +#define USE_RENAME 1 +#define NO_FCHMOD 1 +#endif + +#ifdef USE_FTIME +#include <sys/timeb.h> +#endif + +/* Looking for BigInteger math functions? They've moved to <srp_aux.h>. */ + +#endif diff --git a/3rd_party/libsrp6a-sha512/t_math.c b/3rd_party/libsrp6a-sha512/t_math.c new file mode 100644 index 0000000..dac19ec --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_math.c @@ -0,0 +1,968 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +#include <stdio.h> +#include <sys/types.h> + +#include "config.h" + +#ifdef OPENSSL +# include "openssl/opensslv.h" +# include "openssl/bn.h" +typedef BIGNUM * BigInteger; +typedef BN_CTX * BigIntegerCtx; +typedef BN_MONT_CTX * BigIntegerModAccel; +#include <limits.h> +#if OPENSSL_VERSION_NUMBER < 0x30000000L +# ifndef OPENSSL_NO_ENGINE +# define OPENSSL_ENGINE +# include "openssl/engine.h" +static ENGINE * default_engine = NULL; +# endif /* OPENSSL_ENGINE */ +#endif +typedef int (*modexp_meth)(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *mctx); +static modexp_meth default_modexp = NULL; +#elif defined(CRYPTOLIB) +# include "libcrypt.h" +typedef BigInt BigInteger; +typedef void * BigIntegerCtx; +typedef void * BigIntegerModAccel; +#elif defined(GNU_MP) +# include "gmp.h" +typedef MP_INT * BigInteger; +typedef void * BigIntegerCtx; +typedef void * BigIntegerModAccel; +# if __GNU_MP_VERSION >= 4 || (__GNU_MP_VERSION == 4 && __GNU_MP_VERSION_MINOR >= 1) +/* GMP 4.1 and up has fast import/export routines for integer conversion */ +# define GMP_IMPEXP 1 +# endif +#elif defined(TOMMATH) +# ifdef TOMCRYPT + /* as of v0.96 */ +# include "ltc_tommath.h" +# else +# include "tommath.h" +# endif +typedef mp_int * BigInteger; +typedef void * BigIntegerCtx; +typedef void * BigIntegerModAccel; +#elif defined(GCRYPT) +# include "gcrypt.h" +typedef gcry_mpi_t BigInteger; +typedef void * BigIntegerCtx; +typedef void * BigIntegerModAccel; +#elif defined(MPI) +# include "mpi.h" +typedef mp_int * BigInteger; +typedef void * BigIntegerCtx; +typedef void * BigIntegerModAccel; +#elif defined(MBEDTLS) +#include <mbedtls/bignum.h> +#include <mbedtls/error.h> +typedef mbedtls_mpi* BigInteger; +typedef void * BigIntegerCtx; +typedef void * BigIntegerModAccel; +#else +# error "no math library specified" +#endif +#define MATH_PRIV + +#include "t_defines.h" +#include "t_pwd.h" +#include "srp_aux.h" + +/* Math library interface stubs */ + +BigInteger +BigIntegerFromInt(unsigned int n) +{ +#ifdef OPENSSL + BIGNUM * a = BN_new(); + if(a) + BN_set_word(a, n); + return a; +#elif defined(CRYPTOLIB) + return bigInit(n); +#elif defined(GNU_MP) + BigInteger rv = (BigInteger) malloc(sizeof(MP_INT)); + if(rv) + mpz_init_set_ui(rv, n); + return rv; +#elif defined(GCRYPT) + BigInteger rv = gcry_mpi_new(32); + gcry_mpi_set_ui(rv, n); + return rv; +#elif defined(MPI) || defined(TOMMATH) + BigInteger rv = (BigInteger) malloc(sizeof(mp_int)); + if(rv) { + mp_init(rv); + mp_set_int(rv, n); + } + return rv; +#elif defined(MBEDTLS) + mbedtls_mpi* a = (mbedtls_mpi*)malloc(sizeof(mbedtls_mpi)); + if (a) { + mbedtls_mpi_init(a); + mbedtls_mpi_lset(a, n); + } + return a; +#endif +} + +BigInteger +BigIntegerFromBytes(const unsigned char *bytes, int length) +{ +#ifdef OPENSSL + BIGNUM * a = BN_new(); + BN_bin2bn(bytes, length, a); + return a; +#elif defined(CRYPTOLIB) + BigInteger rv, t; + int i, n; + + rv = bigInit(0); + if(rv == NULL) + return rv; + if(length % 4 == 0) + RSA_bufToBig(bytes, length, rv); + else { /* Wouldn't need this if cryptolib behaved better */ + i = length & 0x3; + if(length > i) + RSA_bufToBig(bytes + i, length - i, rv); + for(n = 0; i > 0; --i) + n = (n << 8) | *bytes++; + t = bigInit(n); + bigLeftShift(t, (length & ~0x3) << 3, t); + bigAdd(rv, t, rv); + freeBignum(t); + } + return rv; +#elif defined(GNU_MP) + BigInteger rv = (BigInteger) malloc(sizeof(MP_INT)); + +# ifdef GMP_IMPEXP + if(rv) { + mpz_init(rv); + mpz_import(rv, length, 1, 1, 1, 0, bytes); + } +# else + cstr * hexbuf = cstr_new(); + + if(hexbuf) { + if(rv) + mpz_init_set_str(rv, t_tohexcstr(hexbuf, bytes, length), 16); + cstr_clear_free(hexbuf); + } +# endif /* GMP_IMPEXP */ + + return rv; +#elif defined(GCRYPT) + BigInteger rv; + gcry_mpi_scan(&rv, GCRYMPI_FMT_USG, bytes, length, NULL); + return rv; +#elif defined(MPI) || defined(TOMMATH) + BigInteger rv = (BigInteger) malloc(sizeof(mp_int)); + if(rv) { + mp_init(rv); + mp_read_unsigned_bin(rv, (unsigned char *)bytes, length); + } + return rv; +#elif defined(MBEDTLS) + mbedtls_mpi* a = (mbedtls_mpi*)malloc(sizeof(mbedtls_mpi)); + if (a) { + mbedtls_mpi_init(a); + mbedtls_mpi_read_binary(a, bytes, length); + } + return a; +#endif +} + +int +BigIntegerToBytes(BigInteger src, unsigned char *dest, int destlen) +{ +#ifdef OPENSSL + return BN_bn2bin(src, dest); +#elif defined(CRYPTOLIB) + int i, j; + cstr * rawbuf; + + trim(src); + i = bigBytes(src); + j = (bigBits(src) + 7) / 8; + if(i == j) + RSA_bigToBuf(src, i, dest); + else { /* Wouldn't need this if cryptolib behaved better */ + rawbuf = cstr_new(); + cstr_set_length(rawbuf, i); + RSA_bigToBuf(src, i, rawbuf->data); + memcpy(dest, rawbuf->data + (i-j), j); + cstr_clear_free(rawbuf); + } + return j; +#elif defined(GNU_MP) + size_t r = 0; +# ifdef GMP_IMPEXP + mpz_export(dest, &r, 1, 1, 1, 0, src); +# else + cstr * hexbuf = cstr_new(); + + if(hexbuf) { + cstr_set_length(hexbuf, mpz_sizeinbase(src, 16) + 1); + mpz_get_str(hexbuf->data, 16, src); + r = t_fromhex(dest, hexbuf->data); + cstr_clear_free(hexbuf); + } +# endif + return r; +#elif defined(GCRYPT) + size_t r = 0; + gcry_mpi_print(GCRYMPI_FMT_USG, dest, destlen, &r, src); + return r; +#elif defined(MPI) || defined(TOMMATH) + mp_to_unsigned_bin(src, dest); + return mp_unsigned_bin_size(src); +#elif defined(MBEDTLS) + size_t r = mbedtls_mpi_size(src); + mbedtls_mpi_write_binary(src, dest, r); + return r; +#endif +} + +BigIntegerResult +BigIntegerToCstr(BigInteger x, cstr * out) +{ + int n = BigIntegerByteLen(x); + if(cstr_set_length(out, n) < 0) + return BIG_INTEGER_ERROR; + if(cstr_set_length(out, BigIntegerToBytes(x, (unsigned char*)out->data, n)) < 0) + return BIG_INTEGER_ERROR; + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerToCstrEx(BigInteger x, cstr * out, int len) +{ + int n; + if(cstr_set_length(out, len) < 0) + return BIG_INTEGER_ERROR; +#if defined(MBEDTLS) + /* mbedtls will prefix the output with zeros if the buffer is larger */ + mbedtls_mpi_write_binary(x, (unsigned char*)out->data, len); +#else + n = BigIntegerToBytes(x, (unsigned char*)out->data, len); + if(n < len) { + memmove(out->data + (len - n), out->data, n); + memset(out->data, 0, len - n); + } +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerToHex(BigInteger src, char *dest, int destlen) +{ +#ifdef OPENSSL + strncpy(dest, BN_bn2hex(src), destlen); +#elif defined(CRYPTOLIB) + trim(src); + bigsprint(src, dest); +#elif defined(GNU_MP) + mpz_get_str(dest, 16, src); +#elif defined(GCRYPT) + gcry_mpi_print(GCRYMPI_FMT_HEX, dest, destlen, NULL, src); +#elif defined(MPI) || defined(TOMMATH) + mp_toradix(src, dest, 16); +#elif defined(MBEDTLS) + size_t olen = 0; + mbedtls_mpi_write_string(src, 16, dest, destlen, &olen); +#endif + return BIG_INTEGER_SUCCESS; +} + +static char b64table[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./"; + +BigIntegerResult +BigIntegerToString(BigInteger src, char *dest, int destlen, unsigned int radix) +{ + BigInteger t = BigIntegerFromInt(0); + char * p = dest; + char c; + + *p++ = b64table[BigIntegerModInt(src, radix, NULL)]; + BigIntegerDivInt(t, src, radix, NULL); + while(BigIntegerCmpInt(t, 0) > 0) { + *p++ = b64table[BigIntegerModInt(t, radix, NULL)]; + BigIntegerDivInt(t, t, radix, NULL); + } + BigIntegerFree(t); + *p-- = '\0'; + /* reverse the string */ + while(p > dest) { + c = *p; + *p-- = *dest; + *dest++ = c; + } + return BIG_INTEGER_SUCCESS; +} + +int +BigIntegerBitLen(BigInteger b) +{ +#ifdef OPENSSL + return BN_num_bits(b); +#elif defined(CRYPTOLIB) + return bigBits(b); +#elif defined(GNU_MP) + return mpz_sizeinbase(b, 2); +#elif defined(GCRYPT) + return gcry_mpi_get_nbits(b); +#elif defined(MPI) || defined(TOMMATH) + return mp_count_bits(b); +#elif defined(MBEDTLS) + return (int)mbedtls_mpi_bitlen(b); +#endif +} + +int +BigIntegerCmp(BigInteger c1, BigInteger c2) +{ +#ifdef OPENSSL + return BN_cmp(c1, c2); +#elif defined(CRYPTOLIB) + return bigCompare(c1, c2); +#elif defined(GNU_MP) + return mpz_cmp(c1, c2); +#elif defined(GCRYPT) + return gcry_mpi_cmp(c1, c2); +#elif defined(MPI) || defined(TOMMATH) + return mp_cmp(c1, c2); +#elif defined(MBEDTLS) + return mbedtls_mpi_cmp_mpi(c1, c2); +#endif +} + +int +BigIntegerCmpInt(BigInteger c1, unsigned int c2) +{ +#ifdef OPENSSL + BigInteger bc2 = BigIntegerFromInt(c2); + int rv = BigIntegerCmp(c1, bc2); + BigIntegerFree(bc2); + return rv; +#elif defined(CRYPTOLIB) + BigInteger t; + int rv; + + t = bigInit(c2); + rv = bigCompare(c1, t); + freeBignum(t); + return rv; +#elif defined(GNU_MP) + return mpz_cmp_ui(c1, c2); +#elif defined(TOMMATH) + return mp_cmp_d(c1, c2); +#elif defined(GCRYPT) + return gcry_mpi_cmp_ui(c1, c2); +#elif defined(MPI) + return mp_cmp_int(c1, c2); +#elif defined(MBEDTLS) + return mbedtls_mpi_cmp_int(c1, c2); +#endif +} + +BigIntegerResult +BigIntegerLShift(BigInteger result, BigInteger x, unsigned int bits) +{ +#ifdef OPENSSL + BN_lshift(result, x, bits); +#elif defined(CRYPTOLIB) + bigLeftShift(x, bits, result); +#elif defined(GNU_MP) + mpz_mul_2exp(result, x, bits); +#elif defined(GCRYPT) + gcry_mpi_mul_2exp(result, x, bits); +#elif defined(MPI) || defined(TOMMATH) + mp_mul_2d(x, bits, result); +#elif defined(MBEDTLS) + mbedtls_mpi_copy(result, x); + mbedtls_mpi_shift_l(result, bits); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerAdd(BigInteger result, BigInteger a1, BigInteger a2) +{ +#ifdef OPENSSL + BN_add(result, a1, a2); +#elif defined(CRYPTOLIB) + bigAdd(a1, a2, result); +#elif defined(GNU_MP) + mpz_add(result, a1, a2); +#elif defined(GCRYPT) + gcry_mpi_add(result, a1, a2); +#elif defined(MPI) || defined(TOMMATH) + mp_add(a1, a2, result); +#elif defined(MBEDTLS) + mbedtls_mpi_add_mpi(result, a1, a2); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerAddInt(BigInteger result, BigInteger a1, unsigned int a2) +{ +#ifdef OPENSSL + if(result != a1) + BN_copy(result, a1); + BN_add_word(result, a2); +#elif defined(CRYPTOLIB) + BigInteger t; + + t = bigInit(a2); + bigAdd(a1, t, result); + freeBignum(t); +#elif defined(GNU_MP) + mpz_add_ui(result, a1, a2); +#elif defined(GCRYPT) + gcry_mpi_add_ui(result, a1, a2); +#elif defined(MPI) || defined(TOMMATH) + mp_add_d(a1, a2, result); +#elif defined(MBEDTLS) + mbedtls_mpi_add_int(result, a1, a2); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerSub(BigInteger result, BigInteger s1, BigInteger s2) +{ +#ifdef OPENSSL + BN_sub(result, s1, s2); +#elif defined(CRYPTOLIB) + bigSubtract(s1, s2, result); +#elif defined(GNU_MP) + mpz_sub(result, s1, s2); +#elif defined(GCRYPT) + gcry_mpi_sub(result, s1, s2); +#elif defined(MPI) || defined(TOMMATH) + mp_sub(s1, s2, result); +#elif defined(MBEDTLS) + mbedtls_mpi_sub_mpi(result, s1, s2); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerSubInt(BigInteger result, BigInteger s1, unsigned int s2) +{ +#ifdef OPENSSL + if(result != s1) + BN_copy(result, s1); + BN_sub_word(result, s2); +#elif defined(CRYPTOLIB) + BigInteger t; + + t = bigInit(s2); + bigSubtract(s1, t, result); + freeBignum(t); +#elif defined(GNU_MP) + mpz_sub_ui(result, s1, s2); +#elif defined(GCRYPT) + gcry_mpi_sub_ui(result, s1, s2); +#elif defined(MPI) || defined(TOMMATH) + mp_sub_d(s1, s2, result); +#elif defined(MBEDTLS) + mbedtls_mpi_sub_int(result, s1, s2); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerMul(BigInteger result, BigInteger m1, BigInteger m2, BigIntegerCtx c) +{ +#ifdef OPENSSL + BN_CTX * ctx = NULL; + if(c == NULL) + c = ctx = BN_CTX_new(); + BN_mul(result, m1, m2, c); + if(ctx) + BN_CTX_free(ctx); +#elif defined(CRYPTOLIB) + bigMultiply(m1, m2, result); +#elif defined(GNU_MP) + mpz_mul(result, m1, m2); +#elif defined(GCRYPT) + gcry_mpi_mul(result, m1, m2); +#elif defined(MPI) || defined(TOMMATH) + mp_mul(m1, m2, result); +#elif defined(MBEDTLS) + mbedtls_mpi_mul_mpi(result, m1, m2); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerMulInt(BigInteger result, BigInteger m1, unsigned int m2, BigIntegerCtx c) +{ +#ifdef OPENSSL + if(result != m1) + BN_copy(result, m1); + BN_mul_word(result, m2); +#elif defined(CRYPTOLIB) + BigInteger t; + + t = bigInit(m2); + bigMultiply(m1, t, result); + freeBignum(t); +#elif defined(GNU_MP) + mpz_mul_ui(result, m1, m2); +#elif defined(GCRYPT) + gcry_mpi_mul_ui(result, m1, m2); +#elif defined(MPI) || defined(TOMMATH) + mp_mul_d(m1, m2, result); +#elif defined(MBEDTLS) + mbedtls_mpi_mul_int(result, m1, m2); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerDivInt(BigInteger result, BigInteger d, unsigned int m, BigIntegerCtx c) +{ +#ifdef OPENSSL + if(result != d) + BN_copy(result, d); + BN_div_word(result, m); +#elif defined(CRYPTOLIB) + BigInteger t, u, q; + + t = bigInit(m); + u = bigInit(0); + /* We use a separate variable q because cryptolib breaks if result == d */ + q = bigInit(0); + bigDivide(d, t, q, u); + freeBignum(t); + freeBignum(u); + bigCopy(q, result); + freeBignum(q); +#elif defined(GNU_MP) +# ifdef GMP2 + mpz_fdiv_q_ui(result, d, m); +# else + mpz_div_ui(result, d, m); +# endif +#elif defined(GCRYPT) + BigInteger t = BigIntegerFromInt(m); + gcry_mpi_div(result, NULL, d, t, -1); + BigIntegerFree(t); +#elif defined(MPI) || defined(TOMMATH) + mp_div_d(d, m, result, NULL); +#elif defined(MBEDTLS) + mbedtls_mpi_div_int(result, NULL, d, m); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerMod(BigInteger result, BigInteger d, BigInteger m, BigIntegerCtx c) +{ +#ifdef OPENSSL + BN_CTX * ctx = NULL; + if(c == NULL) + c = ctx = BN_CTX_new(); + BN_mod(result, d, m, c); + if(ctx) + BN_CTX_free(ctx); +#elif defined(CRYPTOLIB) + bigMod(d, m, result); +#elif defined(GNU_MP) + mpz_mod(result, d, m); +#elif defined(GCRYPT) + gcry_mpi_mod(result, d, m); +#elif defined(MPI) || defined(TOMMATH) + mp_mod(d, m, result); +#elif defined(MBEDTLS) + mbedtls_mpi_mod_mpi(result, d, m); +#endif + return BIG_INTEGER_SUCCESS; +} + +unsigned int +BigIntegerModInt(BigInteger d, unsigned int m, BigIntegerCtx c) +{ +#ifdef OPENSSL + return BN_mod_word(d, m); +#elif defined(CRYPTOLIB) + BigInteger t, u; + unsigned char r[4]; + + t = bigInit(m); + u = bigInit(0); + bigMod(d, t, u); + bigToBuf(u, sizeof(r), r); + freeBignum(t); + freeBignum(u); + return r[0] | (r[1] << 8) | (r[2] << 16) | (r[3] << 24); +#elif defined(GNU_MP) + MP_INT result; + unsigned int i; + + mpz_init(&result); + +/* Define GMP2 if you're using an old gmp.h but want to link against a + * newer libgmp.a (e.g. 2.0 or later). */ + +# ifdef GMP2 + mpz_fdiv_r_ui(&result, d, m); +# else + mpz_mod_ui(&result, d, m); +# endif + i = mpz_get_ui(&result); + mpz_clear(&result); + return i; +#elif defined(GCRYPT) + /* TODO: any way to clean this up??? */ + unsigned char r[4]; + size_t len, i; + unsigned int ret = 0; + BigInteger t = BigIntegerFromInt(m); + BigInteger a = BigIntegerFromInt(0); + gcry_mpi_mod(a, d, t); + gcry_mpi_print(GCRYMPI_FMT_USG, r, 4, &len, a); + for(i = 0; i < len; ++i) + ret = (ret << 8) | r[i]; + BigIntegerFree(t); + BigIntegerFree(a); + return ret; +#elif defined(MPI) || defined(TOMMATH) + mp_digit r; + mp_mod_d(d, m, &r); + return r; +#elif defined(MBEDTLS) + mbedtls_mpi_uint r = 0; + mbedtls_mpi_mod_int(&r, d, m); + return r; +#endif +} + +BigIntegerResult +BigIntegerModMul(BigInteger r, BigInteger m1, BigInteger m2, BigInteger modulus, BigIntegerCtx c) +{ +#ifdef OPENSSL + BN_CTX * ctx = NULL; + if(c == NULL) + c = ctx = BN_CTX_new(); + BN_mod_mul(r, m1, m2, modulus, c); + if(ctx) + BN_CTX_free(ctx); +#elif defined(CRYPTOLIB) + bigMultiply(m1, m2, r); + bigMod(r, modulus, r); +#elif defined(GNU_MP) + mpz_mul(r, m1, m2); + mpz_mod(r, r, modulus); +#elif defined(GCRYPT) + gcry_mpi_mulm(r, m1, m2, modulus); +#elif defined(MPI) || defined(TOMMATH) + mp_mulmod(m1, m2, modulus, r); +#elif defined(MBEDTLS) + mbedtls_mpi d; + mbedtls_mpi_init(&d); + mbedtls_mpi_mul_mpi(&d, m1, m2); + mbedtls_mpi_mod_mpi(r, &d, modulus); + mbedtls_mpi_free(&d); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerModExp(BigInteger r, BigInteger b, BigInteger e, BigInteger m, BigIntegerCtx c, BigIntegerModAccel a) +{ +#ifdef OPENSSL +#if OPENSSL_VERSION_NUMBER >= 0x00906000 + BN_ULONG B = BN_get_word(b); +#endif + BN_CTX * ctx = NULL; + if(c == NULL) + c = ctx = BN_CTX_new(); + if(default_modexp) { + (*default_modexp)(r, b, e, m, c, a); + } + else if(a == NULL) { + BN_mod_exp(r, b, e, m, c); + } +/* + * In LibreSSL BN_mod_exp_mont_word() is not a public symbol where BN_mod_exp() + * and BN_mod_exp_mont() will use the word optimization when appropriate. + */ +#if OPENSSL_VERSION_NUMBER >= 0x00906000 && !defined(LIBRESSL_VERSION_NUMBER) + else if(B > 0 && B < ULONG_MAX) { /* 0.9.6 and above has mont_word optimization */ + BN_mod_exp_mont_word(r, B, e, m, c, a); + } +#endif + else + BN_mod_exp_mont(r, b, e, m, c, a); + if(ctx) + BN_CTX_free(ctx); +#elif defined(CRYPTOLIB) + bigPow(b, e, m, r); +#elif defined(GNU_MP) + mpz_powm(r, b, e, m); +#elif defined(GCRYPT) + gcry_mpi_powm(r, b, e, m); +#elif defined(MPI) || defined(TOMMATH) + mp_exptmod(b, e, m, r); +#elif defined(MBEDTLS) + mbedtls_mpi_exp_mod(r, b, e, m, NULL); +#endif + return BIG_INTEGER_SUCCESS; +} + +#if defined(MBEDTLS) +int _mbedtls_f_rng(void* unused, unsigned char *buf, size_t size) +{ + t_random(buf, size); + return 0; +} +#endif + +int +BigIntegerCheckPrime(BigInteger n, BigIntegerCtx c) +{ +#ifdef OPENSSL + int rv; + BN_CTX * ctx = NULL; + if(c == NULL) + c = ctx = BN_CTX_new(); +#if OPENSSL_VERSION_NUMBER >= 0x00908000 + #if OPENSSL_VERSION_NUMBER >= 0x30000000L + rv = BN_check_prime(n, c, NULL); + #else + rv = BN_is_prime_ex(n, 25, c, NULL); + #endif +#else + rv = BN_is_prime(n, 25, NULL, c, NULL); +#endif + if(ctx) + BN_CTX_free(ctx); + return rv; +#elif defined(CRYPTOLIB) +#if 0 + /* + * Ugh. Not only is cryptolib's bigDivide sensitive to inputs + * and outputs being the same, but now the primeTest needs random + * numbers, which it gets by calling cryptolib's broken truerand + * implementation(!) We have to fake it out by doing our own + * seeding explicitly. + */ + static int seeded = 0; + static unsigned char seedbuf[64]; + if(!seeded) { + t_random(seedbuf, sizeof(seedbuf)); + seedDesRandom(seedbuf, sizeof(seedbuf)); + memset(seedbuf, 0, sizeof(seedbuf)); + seeded = 1; + } +#endif /* 0 */ + t_random(NULL, 0); + return primeTest(n); +#elif defined(GNU_MP) + return mpz_probab_prime_p(n, 25); +#elif defined(GCRYPT) + return (gcry_prime_check(n, 0) == GPG_ERR_NO_ERROR); +#elif defined(TOMMATH) + int rv; + mp_prime_is_prime(n, 25, &rv); + return rv; +#elif defined(MPI) + return (mpp_pprime(n, 25) == MP_YES); +#elif defined(MBEDTLS) + return mbedtls_mpi_is_prime_ext(n, 25, _mbedtls_f_rng, NULL); +#endif +} + +BigIntegerResult +BigIntegerFree(BigInteger b) +{ +#ifdef OPENSSL + BN_free(b); +#elif defined(CRYPTOLIB) + freeBignum(b); +#elif defined(GNU_MP) + mpz_clear(b); + free(b); +#elif defined(GCRYPT) + gcry_mpi_release(b); +#elif defined(MPI) || defined(TOMMATH) + mp_clear(b); + free(b); +#elif defined(MBEDTLS) + mbedtls_mpi_free(b); + free(b); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerClearFree(BigInteger b) +{ +#ifdef OPENSSL + BN_clear_free(b); +#elif defined(CRYPTOLIB) + /* TODO */ + freeBignum(b); +#elif defined(GNU_MP) + /* TODO */ + mpz_clear(b); + free(b); +#elif defined(GCRYPT) + /* TODO */ + gcry_mpi_release(b); +#elif defined(MPI) || defined(TOMMATH) + /* TODO */ + mp_clear(b); + free(b); +#elif defined(MBEDTLS) + mbedtls_mpi_free(b); + free(b); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerCtx +BigIntegerCtxNew() +{ +#ifdef OPENSSL + return BN_CTX_new(); +#else + return NULL; +#endif +} + +BigIntegerResult +BigIntegerCtxFree(BigIntegerCtx ctx) +{ +#ifdef OPENSSL + if(ctx) + BN_CTX_free(ctx); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerModAccel +BigIntegerModAccelNew(BigInteger m, BigIntegerCtx c) +{ +#ifdef OPENSSL + BN_CTX * ctx = NULL; + BN_MONT_CTX * mctx; + if(default_modexp) + return NULL; + if(c == NULL) + c = ctx = BN_CTX_new(); + mctx = BN_MONT_CTX_new(); + BN_MONT_CTX_set(mctx, m, c); + if(ctx) + BN_CTX_free(ctx); + return mctx; +#else + return NULL; +#endif +} + +BigIntegerResult +BigIntegerModAccelFree(BigIntegerModAccel accel) +{ +#ifdef OPENSSL + if(accel) + BN_MONT_CTX_free(accel); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerInitialize() +{ +#if OPENSSL_VERSION_NUMBER >= 0x00907000 && defined(OPENSSL_ENGINE) + ENGINE_load_builtin_engines(); +#endif + return BIG_INTEGER_SUCCESS; +} + +BigIntegerResult +BigIntegerFinalize() +{ + return BigIntegerReleaseEngine(); +} + +BigIntegerResult +BigIntegerUseEngine(const char * engine) +{ +#if defined(OPENSSL) && defined(OPENSSL_ENGINE) + ENGINE * e = ENGINE_by_id(engine); + if(e) { + if(ENGINE_init(e) > 0) { +#if OPENSSL_VERSION_NUMBER >= 0x00907000 + /* 0.9.7 loses the BN_mod_exp method. Pity. */ + const RSA_METHOD * rsa = ENGINE_get_RSA(e); + if(rsa) +#if (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x3000000fL) || (!defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100005L) + default_modexp = (modexp_meth)RSA_meth_get_bn_mod_exp(rsa); +#else + default_modexp = (modexp_meth)rsa->bn_mod_exp; +#endif +#else + default_modexp = (modexp_meth)ENGINE_get_BN_mod_exp(e); +#endif + BigIntegerReleaseEngine(); + default_engine = e; + return BIG_INTEGER_SUCCESS; + } + else + ENGINE_free(e); + } +#endif + return BIG_INTEGER_ERROR; +} + +BigIntegerResult +BigIntegerReleaseEngine() +{ +#if defined(OPENSSL) && defined(OPENSSL_ENGINE) + if(default_engine) { + ENGINE_finish(default_engine); + ENGINE_free(default_engine); + default_engine = NULL; + default_modexp = NULL; + } +#endif + return BIG_INTEGER_SUCCESS; +} diff --git a/3rd_party/libsrp6a-sha512/t_misc.c b/3rd_party/libsrp6a-sha512/t_misc.c new file mode 100644 index 0000000..3a2cda1 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_misc.c @@ -0,0 +1,439 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +#include "t_defines.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#ifdef WIN32 +#include <process.h> +#include <io.h> +#endif + +#include "t_sha.h" + +#ifndef NULL +#define NULL 0 +#endif + +#ifdef OPENSSL +#include <openssl/opensslv.h> +#include <openssl/rand.h> +#elif defined(TOMCRYPT) +#include "tomcrypt.h" +static prng_state g_rng; +static unsigned char entropy[32]; +#elif defined(CRYPTOLIB) +# include "libcrypt.h" +static unsigned char crpool[64]; +#else +static unsigned char randpool[SHA_DIGESTSIZE], randout[SHA_DIGESTSIZE]; +static unsigned long randcnt = 0; +static unsigned int outpos = 0; +SHA1_CTX randctxt; +#endif /* OPENSSL */ + +/* + * t_envhash - Generate a 160-bit SHA hash of the environment + * + * This routine performs an SHA hash of all the "name=value" pairs + * in the environment concatenated together and dumps them in the + * output. While it is true that anyone on the system can see + * your environment, someone not on the system will have a very + * difficult time guessing it, especially since some systems play + * tricks with variable ordering and sometimes define quirky + * environment variables like $WINDOWID or $_. + */ +extern char ** environ; + +static void +t_envhash(unsigned char * out) +{ + char ** ptr; + char ebuf[256]; + SHA1_CTX ctxt; + + SHA1Init(&ctxt); + for(ptr = environ; *ptr; ++ptr) { + strncpy(ebuf, *ptr, 255); + ebuf[255] = '\0'; + SHA1Update(&ctxt, ebuf, strlen(ebuf)); + } + SHA1Final(out, &ctxt); +} + +/* + * t_fshash - Generate a 160-bit SHA hash from the file system + * + * This routine climbs up the directory tree from the current + * directory, running stat() on each directory until it hits the + * root directory. This information is sensitive to the last + * access/modification times of all the directories above you, + * so someone who lists one of those directories injects some + * entropy into the system. Obviously, this hash is very sensitive + * to your current directory when the program is run. + * + * For good measure, it also performs an fstat on the standard input, + * usually your tty, throws that into the buffer, creates a file in + * /tmp (the inode is unpredictable on a busy system), and runs stat() + * on that before deleting it. + * + * The entire buffer is run once through SHA to obtain the final result. + */ +static void +t_fshash(unsigned char * out) +{ + char dotpath[128]; + struct stat st; + SHA1_CTX ctxt; + int i, pinode; + dev_t pdev; + + SHA1Init(&ctxt); + if(stat(".", &st) >= 0) { + SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st)); + pinode = st.st_ino; + pdev = st.st_dev; + strcpy(dotpath, ".."); + for(i = 0; i < 40; ++i) { + if(stat(dotpath, &st) < 0) + break; + if(st.st_ino == pinode && st.st_dev == pdev) + break; + SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st)); + pinode = st.st_ino; + pdev = st.st_dev; + strcat(dotpath, "/.."); + } + } + + if(fstat(0, &st) >= 0) + SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st)); + + sprintf(dotpath, "/tmp/rnd.%d", getpid()); + if(creat(dotpath, 0600) >= 0 && stat(dotpath, &st) >= 0) + SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st)); + unlink(dotpath); + + SHA1Final(out, &ctxt); +} + +/* + * Generate a high-entropy seed for the strong random number generator. + * This uses a wide variety of quickly gathered and somewhat unpredictable + * system information. The 'preseed' structure is assembled from: + * + * The system time in seconds + * The system time in microseconds + * The current process ID + * The parent process ID + * A hash of the user's environment + * A hash gathered from the file system + * Input from a random device, if available + * Timings of system interrupts + * + * The entire structure (60 bytes on most systems) is fed to SHA to produce + * a 160-bit seed for the strong random number generator. It is believed + * that in the worst case (on a quiet system with no random device versus + * an attacker who has access to the system already), the seed contains at + * least about 80 bits of entropy. Versus an attacker who does not have + * access to the system, the entropy should be slightly over 128 bits. + */ +static char initialized = 0; + +static struct { + unsigned int trand1; + time_t sec; + time_t subsec; + short pid; + short ppid; + unsigned char envh[SHA_DIGESTSIZE]; + unsigned char fsh[SHA_DIGESTSIZE]; + unsigned char devrand[20]; + unsigned int trand2; +} preseed; + +unsigned long raw_truerand(); + +static void +t_initrand() +{ + SHA1_CTX ctxt; +#ifdef USE_FTIME + struct timeb t; +#else + struct timeval t; +#endif + int i, r=0; + + if(initialized) + return; + + initialized = 1; + +#if defined(OPENSSL) /* OpenSSL has nifty win32 entropy-gathering code */ +#if OPENSSL_VERSION_NUMBER >= 0x00905100 + r = RAND_status(); +#if defined(WINDOWS) || defined(WIN32) + if(r) /* Don't do the Unix-y stuff on Windows if possible */ + return; +#else +#endif +#endif + +#elif defined(TOMCRYPT) + yarrow_start(&g_rng); + r = rng_get_bytes(entropy, sizeof(entropy), NULL); + if(r > 0) { + yarrow_add_entropy(entropy, r, &g_rng); + memset(entropy, 0, sizeof(entropy)); +# if defined(WINDOWS) || defined(WIN32) + /* Don't do the Unix-y stuff on Windows if possible */ + yarrow_ready(&g_rng); + return; +# endif + } +#endif + +#if !defined(WINDOWS) && !defined(WIN32) + i = open("/dev/urandom", O_RDONLY); + if(i > 0) { + r += read(i, preseed.devrand, sizeof(preseed.devrand)); + close(i); + } +#endif /* !WINDOWS && !WIN32 */ + + /* Resort to truerand only if desperate for some Real entropy */ + if(r == 0) + preseed.trand1 = raw_truerand(); + +#ifdef USE_FTIME + ftime(&t); + preseed.sec = t.time; + preseed.subsec = t.millitm; +#else + gettimeofday(&t, NULL); + preseed.sec = t.tv_sec; + preseed.subsec = t.tv_usec; +#endif + preseed.pid = getpid(); +#ifndef WIN32 + preseed.ppid = getppid(); +#endif + t_envhash(preseed.envh); + t_fshash(preseed.fsh); + + if(r == 0) + preseed.trand2 = raw_truerand(); + +#ifdef OPENSSL + RAND_seed((unsigned char *)&preseed, sizeof(preseed)); +#elif defined(TOMCRYPT) + yarrow_add_entropy((unsigned char *)&preseed, sizeof(preseed), &g_rng); + yarrow_ready(&g_rng); +#elif defined(CRYPTOLIB) + t_mgf1(crpool, sizeof(crpool), (unsigned char *) &preseed, sizeof(preseed)); + seedDesRandom(crpool, sizeof(crpool)); + memset(crpool, 0, sizeof(crpool)); +#elif defined(GCRYPT) + gcry_random_add_bytes((unsigned char *)&preseed, sizeof(preseed), -1); +#else + SHA1Init(&ctxt); + SHA1Update(&ctxt, (unsigned char *) &preseed, sizeof(preseed)); + SHA1Final(randpool, &ctxt); + memset((unsigned char *) &ctxt, 0, sizeof(ctxt)); + outpos = 0; +#endif /* OPENSSL */ + memset((unsigned char *) &preseed, 0, sizeof(preseed)); +} + +#define NUM_RANDOMS 12 + +_TYPE( void ) +t_stronginitrand() +{ +#if 1 /* t_initrand() has been improved enough to make this unnecessary */ + t_initrand(); +#else + SHA1_CTX ctxt; + unsigned int rawrand[NUM_RANDOMS]; + int i; + + if(!initialized) + t_initrand(); + for(i = 0; i < NUM_RANDOMS; ++i) + rawrand[i] = raw_truerand(); + SHA1Init(&ctxt); + SHA1Update(&ctxt, (unsigned char *) rawrand, sizeof(rawrand)); + SHA1Final(randkey2, &ctxt); + memset(rawrand, 0, sizeof(rawrand)); +#endif +} + +/* + * The strong random number generator. This uses a 160-bit seed + * and uses SHA-1 in a feedback configuration to generate successive + * outputs. If S[0] is set to the initial seed, then: + * + * S[i+1] = SHA-1(i || S[i]) + * A[i] = SHA-1(S[i]) + * + * where the A[i] are the output blocks starting with i=0. + * Each cycle generates 20 bytes of new output. + */ +_TYPE( void ) +t_random(unsigned char * data, unsigned size) +{ + if(!initialized) + t_initrand(); + + if(size <= 0) /* t_random(NULL, 0) forces seed initialization */ + return; + +#ifdef OPENSSL + RAND_bytes(data, size); +#elif defined(TOMCRYPT) + yarrow_read(data, size, &g_rng); +#elif defined(GCRYPT) + gcry_randomize(data, size, GCRY_STRONG_RANDOM); +#elif defined(CRYPTOLIB) + randomBytes(data, size, PSEUDO); +#else + while(size > outpos) { + if(outpos > 0) { + memcpy(data, randout + (sizeof(randout) - outpos), outpos); + data += outpos; + size -= outpos; + } + + /* Recycle */ + SHA1Init(&randctxt); + SHA1Update(&randctxt, randpool, sizeof(randpool)); + SHA1Final(randout, &randctxt); + SHA1Init(&randctxt); + SHA1Update(&randctxt, (unsigned char *) &randcnt, sizeof(randcnt)); + SHA1Update(&randctxt, randpool, sizeof(randpool)); + SHA1Final(randpool, &randctxt); + ++randcnt; + outpos = sizeof(randout); + } + + if(size > 0) { + memcpy(data, randout + (sizeof(randout) - outpos), size); + outpos -= size; + } +#endif +} + +/* + * The interleaved session-key hash. This separates the even and the odd + * bytes of the input (ignoring the first byte if the input length is odd), + * hashes them separately, and re-interleaves the two outputs to form a + * single 320-bit value. + */ +_TYPE( unsigned char * ) +t_sessionkey(unsigned char * key, unsigned char * sk, unsigned sklen) +{ + unsigned i, klen; + unsigned char * hbuf; + unsigned char hout[SHA_DIGESTSIZE]; + SHA1_CTX ctxt; + + while(sklen > 0 && *sk == 0) { /* Skip leading 0's */ + --sklen; + ++sk; + } + + klen = sklen / 2; + if((hbuf = malloc(klen * sizeof(char))) == 0) + return 0; + + for(i = 0; i < klen; ++i) + hbuf[i] = sk[sklen - 2 * i - 1]; + SHA1Init(&ctxt); + SHA1Update(&ctxt, hbuf, klen); + SHA1Final(hout, &ctxt); + for(i = 0; i < sizeof(hout); ++i) + key[2 * i] = hout[i]; + + for(i = 0; i < klen; ++i) + hbuf[i] = sk[sklen - 2 * i - 2]; + SHA1Init(&ctxt); + SHA1Update(&ctxt, hbuf, klen); + SHA1Final(hout, &ctxt); + for(i = 0; i < sizeof(hout); ++i) + key[2 * i + 1] = hout[i]; + + memset(hout, 0, sizeof(hout)); + memset(hbuf, 0, klen); + free(hbuf); + return key; +} + +_TYPE( void ) +t_mgf1(unsigned char * mask, unsigned masklen, const unsigned char * seed, unsigned seedlen) +{ + SHA1_CTX ctxt; + unsigned i = 0; + unsigned pos = 0; + unsigned char cnt[4]; + unsigned char hout[SHA_DIGESTSIZE]; + + while(pos < masklen) { + cnt[0] = (i >> 24) & 0xFF; + cnt[1] = (i >> 16) & 0xFF; + cnt[2] = (i >> 8) & 0xFF; + cnt[3] = i & 0xFF; + SHA1Init(&ctxt); + SHA1Update(&ctxt, seed, seedlen); + SHA1Update(&ctxt, cnt, 4); + + if(pos + SHA_DIGESTSIZE > masklen) { + SHA1Final(hout, &ctxt); + memcpy(mask + pos, hout, masklen - pos); + pos = masklen; + } + else { + SHA1Final(mask + pos, &ctxt); + pos += SHA_DIGESTSIZE; + } + + ++i; + } + + memset(hout, 0, sizeof(hout)); + memset((unsigned char *)&ctxt, 0, sizeof(ctxt)); +} diff --git a/3rd_party/libsrp6a-sha512/t_pwd.h b/3rd_party/libsrp6a-sha512/t_pwd.h new file mode 100644 index 0000000..a90a364 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_pwd.h @@ -0,0 +1,246 @@ +/* + * Copyright (c) 1997-2007 The Stanford SRP Authentication Project + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Redistributions in source or binary form must retain an intact copy + * of this copyright notice. + */ + +#ifndef T_PWD_H +#define T_PWD_H + +#include <stdio.h> +#include "cstr.h" + +#define MAXPARAMBITS 2048 +#define MAXPARAMLEN ((MAXPARAMBITS + 7) / 8) +#define MAXB64PARAMLEN ((MAXPARAMBITS + 5) / 6 + 1) +#define MAXHEXPARAMLEN ((MAXPARAMBITS + 3) / 4 + 1) +#define MAXOCTPARAMLEN ((MAXPARAMBITS + 2) / 3 + 1) + +#define MAXUSERLEN 32 +#define MAXSALTLEN 32 +#define MAXB64SALTLEN 44 /* 256 bits in b64 + null */ +#define SALTLEN 10 /* Normally 80 bits */ + +#define RESPONSE_LEN 20 /* 160-bit proof hashes */ +#define SESSION_KEY_LEN (2 * RESPONSE_LEN) /* 320-bit session key */ + +#define DEFAULT_PASSWD "/etc/tpasswd" +#define DEFAULT_CONF "/etc/tpasswd.conf" + +struct t_num { /* Standard byte-oriented integer representation */ + int len; + unsigned char * data; +}; + +struct t_preconf { /* Structure returned by t_getpreparam() */ + char * mod_b64; + char * gen_b64; + char * comment; + + struct t_num modulus; + struct t_num generator; +}; + +/* + * The built-in (known good) parameters access routines + * + * "t_getprecount" returns the number of precompiled parameter sets. + * "t_getpreparam" returns the indicated parameter set. + * Memory is statically allocated - callers need not perform any memory mgmt. + */ +_TYPE( int ) t_getprecount(); +_TYPE( struct t_preconf * ) t_getpreparam P((int)); + +struct t_confent { /* One configuration file entry (index, N, g) */ + int index; + struct t_num modulus; + struct t_num generator; +}; + +struct t_conf { /* An open configuration file */ + FILE * instream; + char close_on_exit; + cstr * modbuf; + cstr * genbuf; + struct t_confent tcbuf; +}; + +/* + * The configuration file routines are designed along the lines of the + * "getpw" functions in the standard C library. + * + * "t_openconf" accepts a stdio stream and interprets it as a config file. + * "t_openconfbyname" accepts a filename and does the same thing. + * "t_closeconf" closes the config file. + * "t_getconfent" fetches the next sequential configuration entry. + * "t_getconfbyindex" fetches the configuration entry whose index + * matches the one supplied, or NULL if one can't be found. + * "t_getconflast" fetches the last configuration entry in the file. + * "t_makeconfent" generates a set of configuration entry parameters + * randomly. + * "t_newconfent" returns an empty configuration entry. + * "t_cmpconfent" compares two configuration entries a la strcmp. + * "t_checkconfent" verifies that a set of configuration parameters + * are suitable. N must be prime and should be a safe prime. + * "t_putconfent" writes a configuration entry to a stream. + */ +_TYPE( struct t_conf * ) t_openconf P((FILE *)); +_TYPE( struct t_conf * ) t_openconfbyname P((const char *)); +_TYPE( void ) t_closeconf P((struct t_conf *)); +_TYPE( void ) t_rewindconf P((struct t_conf *)); +_TYPE( struct t_confent * ) t_getconfent P((struct t_conf *)); +_TYPE( struct t_confent * ) t_getconfbyindex P((struct t_conf *, int)); +_TYPE( struct t_confent * ) t_getconflast P((struct t_conf *)); +_TYPE( struct t_confent * ) t_makeconfent P((struct t_conf *, int)); +_TYPE( struct t_confent * ) t_makeconfent_c P((struct t_conf *, int)); +_TYPE( struct t_confent * ) t_newconfent P((struct t_conf *)); +_TYPE( int ) t_cmpconfent P((const struct t_confent *, const struct t_confent *)); +_TYPE( int ) t_checkconfent P((const struct t_confent *)); +_TYPE( void ) t_putconfent P((const struct t_confent *, FILE *)); + +/* libc-style system conf file access */ +_TYPE( struct t_confent *) gettcent(); +_TYPE( struct t_confent *) gettcid P((int)); +_TYPE( void ) settcent(); +_TYPE( void ) endtcent(); + +#ifdef ENABLE_NSW +extern struct t_confent * _gettcent(); +extern struct t_confent * _gettcid P((int)); +extern void _settcent(); +extern void _endtcent(); +#endif + +/* A hack to support '+'-style entries in the passwd file */ + +typedef enum fstate { + FILE_ONLY, /* Ordinary file, don't consult NIS ever */ + FILE_NIS, /* Currently accessing file, use NIS if encountered */ + IN_NIS, /* Currently in a '+' entry; use NIS for getXXent */ +} FILE_STATE; + +struct t_pwent { /* A single password file entry */ + char * name; + struct t_num password; + struct t_num salt; + int index; +}; + +struct t_pw { /* An open password file */ + FILE * instream; + char close_on_exit; + FILE_STATE state; + char userbuf[MAXUSERLEN]; + cstr * pwbuf; + unsigned char saltbuf[SALTLEN]; + struct t_pwent pebuf; +}; + +/* + * The password manipulation routines are patterned after the getpw* + * standard C library function calls. + * + * "t_openpw" reads a stream as if it were a password file. + * "t_openpwbyname" opens the named file as a password file. + * "t_closepw" closes an open password file. + * "t_rewindpw" starts the internal file pointer from the beginning + * of the password file. + * "t_getpwent" retrieves the next sequential password entry. + * "t_getpwbyname" looks up the password entry corresponding to the + * specified user. + * "t_makepwent" constructs a password entry from a username, password, + * numeric salt, and configuration entry. + * "t_putpwent" writes a password entry to a stream. + */ +_TYPE( struct t_pw * ) t_newpw(); +_TYPE( struct t_pw * ) t_openpw P((FILE *)); +_TYPE( struct t_pw * ) t_openpwbyname P((const char *)); +_TYPE( void ) t_closepw P((struct t_pw *)); +_TYPE( void ) t_rewindpw P((struct t_pw *)); +_TYPE( struct t_pwent * ) t_getpwent P((struct t_pw *)); +_TYPE( struct t_pwent * ) t_getpwbyname P((struct t_pw *, const char *)); +_TYPE( struct t_pwent * ) t_makepwent P((struct t_pw *, const char *, + const char *, const struct t_num *, + const struct t_confent *)); +_TYPE( void ) t_putpwent P((const struct t_pwent *, FILE *)); + +struct t_passwd { + struct t_pwent tp; + struct t_confent tc; +}; + +/* libc-style system password file access */ +_TYPE( struct t_passwd * ) gettpent(); +_TYPE( struct t_passwd * ) gettpnam P((const char *)); +_TYPE( void ) settpent(); +_TYPE( void ) endtpent(); + +#ifdef ENABLE_NSW +extern struct t_passwd * _gettpent(); +extern struct t_passwd * _gettpnam P((const char *)); +extern void _settpent(); +extern void _endtpent(); +#endif + +/* + * Utility functions + * + * "t_verifypw" accepts a username and password, and checks against the + * system password file to see if the password for that user is correct. + * Returns > 0 if it is correct, 0 if not, and -1 if some error occurred + * (i.e. the user doesn't exist on the system). This is intended ONLY + * for local authentication; for remote authentication, look at the + * t_client and t_server source. (That's the whole point of SRP!) + * "t_changepw" modifies the specified file, substituting the given password + * entry for the one already in the file. If no matching entry is found, + * the new entry is simply appended to the file. + * "t_deletepw" removes the specified user from the specified file. + */ +_TYPE( int ) t_verifypw P((const char *, const char *)); +_TYPE( int ) t_changepw P((const char *, const struct t_pwent *)); +_TYPE( int ) t_deletepw P((const char *, const char *)); + +/* Conversion utilities */ + +/* + * All these calls accept output as the first parameter. In the case of + * t_tohex and t_tob64, the last argument is the length of the byte-string + * input. + */ +_TYPE( char * ) t_tohex P((char *, const char *, unsigned)); +_TYPE( int ) t_fromhex P((char *, const char *)); +_TYPE( char * ) t_tob64 P((char *, const char *, unsigned)); +_TYPE( int ) t_fromb64 P((char *, const char *)); + +/* These functions put their output in a cstr object */ +_TYPE( char * ) t_tohexcstr P((cstr *, const char *, unsigned)); +_TYPE( int ) t_cstrfromhex P((cstr *, const char *)); +_TYPE( char * ) t_tob64cstr P((cstr *, const char *, unsigned)); +_TYPE( int ) t_cstrfromb64 P((cstr *, const char *)); + +/* Miscellaneous utilities (moved to t_defines.h) */ + +#endif diff --git a/3rd_party/libsrp6a-sha512/t_sha.c b/3rd_party/libsrp6a-sha512/t_sha.c new file mode 100644 index 0000000..8e54cb6 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_sha.c @@ -0,0 +1,314 @@ +#include "t_defines.h" +#include "t_sha.h" + +#ifdef CRYPTOLIB_SHA + +/* A wrapper around CryptoLib's shsFinal that delivers output in octets */ +void +shsFinalBytes(unsigned char digest[20], SHS_CTX* context) +{ + int i; + unsigned long r; + unsigned char *p = digest; + + shsFinal(context); + for(i = 0; i < 5; ++i) { + r = context->h[i]; + *p++ = (unsigned char)((r >> 24) & 0xff); + *p++ = (unsigned char)((r >> 16) & 0xff); + *p++ = (unsigned char)((r >> 8) & 0xff); + *p++ = (unsigned char)(r & 0xff); + } +} + +#elif defined(GCRYPT_SHA) +/* Wrappers for gcrypt's md interface */ + +void +SHA1Init_gcry(SHA1_CTX * ctx) +{ + gcry_md_open(ctx, GCRY_MD_SHA1, 0); +} + +void +SHA1Update_gcry(SHA1_CTX * ctx, const void *data, unsigned int len) +{ + gcry_md_write(*ctx, data, len); +} + +void +SHA1Final_gcry(unsigned char digest[20], SHA1_CTX * ctx) +{ + memcpy(digest, gcry_md_read(*ctx, GCRY_MD_SHA1), 20); + gcry_md_close(*ctx); +} + +void +SHA512Init_gcry(SHA512_CTX * ctx) +{ + gcry_md_open(ctx, GCRY_MD_SHA512, 0); +} + +void +SHA512Update_gcry(SHA512_CTX * ctx, const void *data, unsigned int len) +{ + gcry_md_write(*ctx, data, len); +} + +void +SHA512Final_gcry(unsigned char digest[64], SHA512_CTX * ctx) +{ + memcpy(digest, gcry_md_read(*ctx, GCRY_MD_SHA512), 64); + gcry_md_close(*ctx); +} + +#elif defined(MBEDTLS_SHA) +/* Wrappers for mbedtls's md interface */ + +void +SHA1Init_mbed(SHA1_CTX * ctx) +{ + mbedtls_md_init(ctx); + mbedtls_md_setup(ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 0); + mbedtls_md_starts(ctx); +} + +void +SHA1Update_mbed(SHA1_CTX * ctx, const void *data, unsigned int len) +{ + mbedtls_md_update(ctx, data, len); +} + +void +SHA1Final_mbed(unsigned char digest[20], SHA1_CTX * ctx) +{ + mbedtls_md_finish(ctx, digest); + mbedtls_md_free(ctx); +} + +void +SHA512Init_mbed(SHA512_CTX * ctx) +{ + mbedtls_md_init(ctx); + mbedtls_md_setup(ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA512), 0); + mbedtls_md_starts(ctx); +} + +void +SHA512Update_mbed(SHA512_CTX * ctx, const void *data, unsigned int len) +{ + mbedtls_md_update(ctx, data, len); +} + +void +SHA512Final_mbed(unsigned char digest[64], SHA512_CTX * ctx) +{ + mbedtls_md_finish(ctx, digest); + mbedtls_md_free(ctx); +} + +#elif defined(OPENSSL_SHA) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +void +SHA1Init_openssl(SHA1_CTX *ctx) +{ + *ctx = EVP_MD_CTX_new(); + EVP_DigestInit(*ctx, EVP_sha1()); +} + +void SHA1Update_openssl(SHA1_CTX *ctx, const void *data, unsigned int len) +{ + EVP_DigestUpdate(*ctx, data, (size_t)len); +} + +void SHA1Final_openssl(unsigned char digest[20], SHA1_CTX *ctx) +{ + EVP_DigestFinal(*ctx, digest, NULL); + EVP_MD_CTX_destroy(*ctx); +} + +void +SHA512Init_openssl(SHA512_CTX *ctx) +{ + *ctx = EVP_MD_CTX_new(); + EVP_DigestInit(*ctx, EVP_sha512()); +} + +void SHA512Update_openssl(SHA512_CTX *ctx, const void *data, unsigned int len) +{ + EVP_DigestUpdate(*ctx, data, (size_t)len); +} + +void SHA512Final_openssl(unsigned char digest[64], SHA512_CTX *ctx) +{ + EVP_DigestFinal(*ctx, digest, NULL); + EVP_MD_CTX_destroy(*ctx); +} +#endif +#elif !defined(OPENSSL_SHA) && !defined(TOMCRYPT_SHA) +/* Use the free SHA1 if the library doesn't have it */ + +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#include <stdio.h> +#include <string.h> + +static void SHA1Transform(uint32 state[5], const unsigned char buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifndef WORDS_BIGENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static void SHA1Transform(uint32 state[5], const unsigned char buffer[64]) +{ +uint32 a, b, c, d, e; +typedef union { + unsigned char c[64]; + uint32 l[16]; +} CHAR64LONG16; +CHAR64LONG16* block; +#ifdef SHA1HANDSOFF +static unsigned char workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, unsigned int len) +{ +unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ +uint32 i, j; +unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, (unsigned char *)"\0", 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} +#endif diff --git a/3rd_party/libsrp6a-sha512/t_sha.h b/3rd_party/libsrp6a-sha512/t_sha.h new file mode 100644 index 0000000..2e38067 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_sha.h @@ -0,0 +1,147 @@ +#ifndef T_SHA_H +#define T_SHA_H + +#if !defined(P) +#ifdef __STDC__ +#define P(x) x +#else +#define P(x) () +#endif +#endif + +#define SHA_DIGESTSIZE 20 + +#ifdef OPENSSL +#define OPENSSL_SHA 1 +#endif + +#ifdef TOMCRYPT +# include <tomcrypt.h> +# ifdef SHA1 +# define TOMCRYPT_SHA 1 +# endif +#endif + +#ifdef CRYPTOLIB +/* The SHA (shs) implementation in CryptoLib 1.x breaks when Update + * is called multiple times, so we still use our own code. + * Uncomment below if you think your copy of CryptoLib is fixed. */ +/*#define CRYPTOLIB_SHA 1*/ +#endif + +#ifdef GCRYPT +# define GCRYPT_SHA 1 +#endif + +#ifdef MBEDTLS +# define MBEDTLS_SHA 1 +#endif + +#ifdef OPENSSL_SHA +#include <openssl/err.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/evp.h> + +typedef EVP_MD_CTX* SHA1_CTX; +#define SHA1Init SHA1Init_openssl +#define SHA1Update SHA1Update_openssl +#define SHA1Final SHA1Final_openssl + +typedef EVP_MD_CTX* SHA512_CTX; +#define SHA512Init SHA512Init_openssl +#define SHA512Update SHA512Update_openssl +#define SHA512Final SHA512Final_openssl + +void SHA1Init_openssl(SHA1_CTX *ctx); +void SHA1Update_openssl(SHA1_CTX *ctx, const void *data, unsigned int len); +void SHA1Final_openssl(unsigned char digest[20], SHA1_CTX *ctx); + +void SHA512Init_openssl(SHA512_CTX *ctx); +void SHA512Update_openssl(SHA512_CTX *ctx, const void *data, unsigned int len); +void SHA512Final_openssl(unsigned char digest[64], SHA1_CTX *ctx); +#else /* for OpenSSL < 3.0 */ +#include <openssl/sha.h> + +typedef SHA_CTX SHA1_CTX; +#define SHA1Init SHA1_Init +#define SHA1Update SHA1_Update +#define SHA1Final SHA1_Final + +#define SHA512Init SHA512_Init +#define SHA512Update SHA512_Update +#define SHA512Final SHA512_Final +#endif /* for OpenSSL < 3.0 */ +#elif defined(TOMCRYPT_SHA) +/* mycrypt.h already included above */ + +typedef hash_state SHA1_CTX; +#define SHA1Init sha1_init +#define SHA1Update sha1_process +#define SHA1Final(D,C) sha1_done(C,D) + +#elif defined(GCRYPT_SHA) +#include "gcrypt.h" + +typedef gcry_md_hd_t SHA1_CTX; +#define SHA1Init SHA1Init_gcry +#define SHA1Update SHA1Update_gcry +#define SHA1Final SHA1Final_gcry +typedef gcry_md_hd_t SHA512_CTX; +#define SHA512Init SHA512Init_gcry +#define SHA512Update SHA512Update_gcry +#define SHA512Final SHA512Final_gcry + +void SHA1Init_gcry(SHA1_CTX * ctx); +void SHA1Update_gcry(SHA1_CTX * ctx, const void *data, unsigned int len); +void SHA1Final_gcry(unsigned char digest[20], SHA1_CTX * ctx); + +void SHA512Init_gcry(SHA512_CTX * ctx); +void SHA512Update_gcry(SHA512_CTX * ctx, const void *data, unsigned int len); +void SHA512Final_gcry(unsigned char digest[64], SHA512_CTX * ctx); + +#elif defined(MBEDTLS_SHA) +#include <mbedtls/md.h> + +typedef mbedtls_md_context_t SHA1_CTX; +#define SHA1Init SHA1Init_mbed +#define SHA1Update SHA1Update_mbed +#define SHA1Final SHA1Final_mbed + +typedef mbedtls_md_context_t SHA512_CTX; +#define SHA512Init SHA512Init_mbed +#define SHA512Update SHA512Update_mbed +#define SHA512Final SHA512Final_mbed + +void SHA1Init_mbed(SHA1_CTX * ctx); +void SHA1Update_mbed(SHA1_CTX * ctx, const void *data, unsigned int len); +void SHA1Final_mbed(unsigned char digest[20], SHA1_CTX * ctx); + +void SHA512Init_mbed(SHA512_CTX * ctx); +void SHA512Update_mbed(SHA512_CTX * ctx, const void *data, unsigned int len); +void SHA512Final_mbed(unsigned char digest[64], SHA512_CTX * ctx); + +#elif defined(CRYPTOLIB_SHA) +#include "libcrypt.h" + +typedef SHS_CTX SHA1_CTX; +#define SHA1Init shsInit +#define SHA1Update shsUpdate +#define SHA1Final shsFinalBytes + +void shsFinalBytes P((unsigned char digest[20], SHS_CTX* context)); + +#else +typedef unsigned int uint32; + +typedef struct { + uint32 state[5]; + uint32 count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Init P((SHA1_CTX* context)); +void SHA1Update P((SHA1_CTX* context, const unsigned char* data, unsigned int len)); +void SHA1Final P((unsigned char digest[20], SHA1_CTX* context)); +#endif /* !OPENSSL && !CRYPTOLIB */ + +#endif /* T_SHA_H */ diff --git a/3rd_party/libsrp6a-sha512/t_truerand.c b/3rd_party/libsrp6a-sha512/t_truerand.c new file mode 100644 index 0000000..f995ed7 --- /dev/null +++ b/3rd_party/libsrp6a-sha512/t_truerand.c @@ -0,0 +1,241 @@ +/* + * Physically random numbers (very nearly uniform) + * D. P. Mitchell + * Modified by Matt Blaze 7/95 + */ +/* + * The authors of this software are Don Mitchell and Matt Blaze. + * Copyright (c) 1995 by AT&T. + * Permission to use, copy, and modify this software without fee + * is hereby granted, provided that this entire notice is included in + * all copies of any software which is or includes a copy or + * modification of this software and in all copies of the supporting + * documentation for such software. + * + * This software may be subject to United States export controls. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + */ + +/* + * WARNING: depending on the particular platform, raw_truerand() + * output may be biased or correlated. In general, you can expect + * about 16 bits of "pseudo-entropy" out of each 32 bit word returned + * by truerand(), but it may not be uniformly diffused. You should + * raw_therefore run the output through some post-whitening function + * (like MD5 or DES or whatever) before using it to generate key + * material. (RSAREF's random package does this for you when you feed + * raw_truerand() bits to the seed input function.) + * + * The application interface, for 8, 16, and 32 bit properly "whitened" + * random numbers, can be found in trand8(), trand16(), and trand32(). + * Use those instead of calling raw_truerand() directly. + * + * The basic idea here is that between clock "skew" and various + * hard-to-predict OS event arrivals, counting a tight loop will yield + * a little (maybe a third of a bit or so) of "good" randomness per + * interval clock tick. This seems to work well even on unloaded + * machines. If there is a human operator at the machine, you should + * augment truerand with other measure, like keyboard event timing. + * On server machines (e.g., when you need to generate a + * Diffie-Hellman secret) truerand alone may be good enough. + * + * Test these assumptions on your own platform before fielding a + * system based on this software or these techniques. + * + * This software seems to work well (at 10 or so bits per + * raw_truerand() call) on a Sun Sparc-20 under SunOS 4.1.3 and on a + * P100 under BSDI 2.0. You're on your own elsewhere. + * + */ + +#include "t_defines.h" + +#ifdef WIN32 + +# ifdef CRYPTOLIB + +/* Cryptolib contains its own truerand() on both UNIX and Windows. */ +/* Only use cryptolib's truerand under Windows */ + +# include "libcrypt.h" + +unsigned long +raw_truerand() +{ + return truerand(); +} + +# else /* !CRYPTOLIB && WIN32 */ + +#include <windows.h> +#include <wtypes.h> +#include <winbase.h> +#include <windef.h> +#include <winnt.h> +#include <winuser.h> +#include <process.h> + +volatile unsigned long count, ocount, randbuf; +volatile int dontstop; +char outbuf[1024], *bufp; + +static void counter() { + while (dontstop) + count++; + _endthread(); +} + + +static unsigned long roulette() { + unsigned long thread; + + count = 0; + dontstop= 1; + while ((thread = _beginthread((void *)counter, 1024, NULL)) < 0) + ; + + Sleep(16); + dontstop = 0; + Sleep(1); + + count ^= (count>>3) ^ (count>>6) ^ (ocount); + count &= 0x7; + ocount = count; + randbuf = (randbuf<<3) ^ count; + return randbuf; +} + + +unsigned long +raw_truerand() { + + roulette(); + roulette(); + roulette(); + roulette(); + roulette(); + roulette(); + roulette(); + roulette(); + roulette(); + roulette(); + return roulette(); +} + +# endif /* CRYPTOLIB */ + +#else /* !WIN32 */ + +#include <signal.h> +#include <setjmp.h> +#include <sys/time.h> +#include <math.h> +#include <stdio.h> + +#ifdef OLD_TRUERAND +static jmp_buf env; +#endif +static unsigned volatile count +#ifndef OLD_TRUERAND + , done = 0 +#endif +; + +static unsigned ocount; +static unsigned buffer; + +static void +tick() +{ + struct itimerval it, oit; + + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 0; + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 16665; + if (setitimer(ITIMER_REAL, &it, &oit) < 0) + perror("tick"); +} + +static void +interrupt() +{ + if (count) { +#ifdef OLD_TRUERAND + longjmp(env, 1); +#else + ++done; + return; +#endif + } + + (void) signal(SIGALRM, interrupt); + tick(); +} + +static unsigned long +roulette() +{ +#ifdef OLD_TRUERAND + if (setjmp(env)) { + count ^= (count>>3) ^ (count>>6) ^ ocount; + count &= 0x7; + ocount=count; + buffer = (buffer<<3) ^ count; + return buffer; + } +#else + done = 0; +#endif + (void) signal(SIGALRM, interrupt); + count = 0; + tick(); +#ifdef OLD_TRUERAND + for (;;) +#else + while(done == 0) +#endif + count++; /* about 1 MHz on VAX 11/780 */ +#ifndef OLD_TRUERAND + count ^= (count>>3) ^ (count>>6) ^ ocount; + count &= 0x7; + ocount=count; + buffer = (buffer<<3) ^ count; + return buffer; +#endif +} + +unsigned long +raw_truerand() +{ + count=0; + (void) roulette(); + (void) roulette(); + (void) roulette(); + (void) roulette(); + (void) roulette(); + (void) roulette(); + (void) roulette(); + (void) roulette(); + (void) roulette(); + (void) roulette(); + return roulette(); +} + +int +raw_n_truerand(int n) +{ + int slop, v; + + slop = 0x7FFFFFFF % n; + do { + v = raw_truerand() >> 1; + } while (v <= slop); + return v % n; +} + +#endif /* !CRYPTOLIB || !WIN32 */ @@ -1,15 +1,21 @@ Bastien Nocera Bryan Forbes Christophe Fergeau +Geoff Paul Ingmar Vanhassel +John Maguire Jonathan Beck Joshua Hill +Julien Lavergne Martin Aumueller Martin Szulecki +Marty Rosenberg Matt Colyer Nikias Bassen Patrick Walton Paul Sladen +Peter Hoepfner +Petr Uzel Todd Zullinger Zach C Zoltan Balaton diff --git a/Makefile.am b/Makefile.am index 39840d6..352b28f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,13 +1,15 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = src include swig dev tools docs +SUBDIRS = 3rd_party common src include $(CYTHON_SUB) tools docs -DISTCHECK_CONFIGURE_FLAGS = --enable-dev-tools +EXTRA_DIST = \ + docs \ + README.md \ + git-version-gen -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libimobiledevice-1.0.pc - -EXTRA_DIST = docs +dist-hook: + @if ! git diff --quiet; then echo "Uncommitted changes present; not releasing"; exit 1; fi + echo $(VERSION) > $(distdir)/.tarball-version docs/html: $(top_builddir)/doxygen.cfg $(top_srcdir)/src/*.c $(top_srcdir)/src/*.h $(top_srcdir)/include/libimobiledevice/*.h rm -rf docs/html @@ -16,5 +18,5 @@ docs/html: $(top_builddir)/doxygen.cfg $(top_srcdir)/src/*.c $(top_srcdir)/src/* docs: doxygen.cfg docs/html indent: - indent -kr -ut -ts4 -l120 src/*.c src/*.h dev/*.c + indent -kr -ut -ts4 -l120 src/*.c src/*.h @@ -1,3 +1,338 @@ +Version 1.3.0 +~~~~~~~~~~~~~ + +* Development release "Let's get the party started." +* Changes: + - Fix Python 3 support + - Add more lockdown error codes + - Add new lockdownd_pair_with_options() function + - Fix GnuTLS support with iOS 10 + - Make sure sockets only listen locally due to security reasons + - Plug various memory leaks + - Fix SSL version negotiation for newer versions of OpenSSL + - Optimize lockdown pair record handling + - Return proper error code when a lockdown pair record is missing + - Fix building with MingGW + - Store application information in Info.plist using idevicebackup2 + - Fix application backup handling to allow the device to restore applications + that were installed using idevicebackup2 + - Make idevicebackup2 reboot after restore the default to allow the device to + migrate data correctly and thus improve the restored device data state + - Improve console frontend information output in idevicebackup2 + - Extend ideviceprovision tool to allow retrieving and removing all + provisioning profiles + - Fix parsing large provisioning profile using ideviceprovision + - Fix receiving large property lists in property list service + - Propagate lower level errors to callers instead of returning + IDEVICE_E_UNKNOWN_ERROR + - API: Add IDEVICE_DEVICE_PAIRED event type + - Detect screenshot format to support png, tiff and dat formats using + idevicescreenshot tool + - API: Add mobileactivation service implementation + - Wait for passcode entry if required using idevicesyslog + - Add HDMI option to diagnostics command for idevicediagnostics + - Fix IORegistry command for iOS 11+ devices in idevicediagnostics + - Remove 40-digit character limit for UDID in tools to support newer devices + - Fix broken validate command in idevicepair with iOS 11+ + - Fix OpenSSL version checks for configure target when using LibreSSL + - Migrate latest improved common code from libusbmuxd + - Convert README file to markdown format + - Fix idevicecrashreport tool to work with iOS 13+ + - Fix various errors in SSL communication logic + - API: Add preboard service implementation + - Output hint to user to enter passcode when changing password using + idevicebackup2 + - Cython: Fix and improve debugserver and diagnostics service bindings + - API: Add WiFi device support via new idevice_new_with_options() function + - API: Add idevice_get_device_list_extended() to also list network devices + - API: Add lockdown_strerror() helper to get error representation as string + - Add network device support to idevicesyslog and ideviceinfo tools + - Make debug output consistently output to stderr + - Add new idevicesetlocation tool (requires mounted developer image) + - Add option to exit if device disconnects in idevicesyslog + - API: Add syslog_relay_start_capture_raw() for raw syslog capture + - Add color output and process filter support to idevicesyslog + - API: Add companion_proxy service implementation + - Bump dependency to libusbmuxd 2.0.2 + - Bump dependency to libplist 2.2.0 + - Improve error handling and reporting in library and tools + - Fix various memory leaks in library and tools + - Add "--network" and "--version" options to all tools + - Fix socket_connect_addr() not connecting to network devices using IPv6 + in some cases. + - Improve IPv6 "scope id" detection to fix connecting to network devices with + link-local adresses. + - Update man pages + - Fix various inconsistent declarations in public headers + - Allow OpenSSL >= 1.1.0 to use older/disallowed TLS versions fixing issues + where pairing records were getting removed repeatingly + - Fixed memory leaks + - Cython: Rewrite version detection logic in configure.ac + - Rename "--enable-debug-code" configure option to "--enable-debug" + - Improve README.md with project description, installation, contributing and + usage sections + - Rename library and all related files by adding an API version resulting + in "libimobiledevice-1.0" + - Bump soname version +* API is UNSTABLE + +Version 1.2.0 +~~~~~~~~~~~~~ + +* Stable release "It took you so long baby!" +* Changes: + - Require autoconf 2.64 or later + - Remove dev tools, will return either as proper tools or website examples + - Refactor installation proxy service implementation and normalize code + - API: Added instproxy_lookup() to efficiently lookup app information + - API: Added instproxy_check_capabilities_match() to check device capabilities + - API: Added various instproxy command and status plist getters + - API: Make debugserver_client_set_ack_mode() public + - Fix handling of clients reconnecting in idevicedebugserverproxy which + previously didn't work properly + - Flush stdout for every line in idevicesyslog + - Fix shutdown of idevicedebugserverproxy tool which could hang + - Notify user when erroneously using idevicebackup with iOS 4 or later + - Enable build of idevicecrashreport on WIN32 + - Fix thread handle leaks on WIN32 adding thread_new and thread_free + - cython: Add receive/receive_timeout methods for iDeviceConnection to + receive raw data from a connection + - cython: Add new FILE_RELAY_E_PERMISSION_DENIED(-6) error + - API: Refactor lockdown service internal error checking and add a bunch of + new native errors + - Convert int16_t macro error types into enum within common module, too + - Add new "idevicenotificationproxy" tool to post or observe notifications + - Fix overlong blocking in np_client_free() + - Improve maintainability and Requires of pkg-config file + - API: Add new LOCKDOWN_E_SERVICE_LIMIT error to detect service limit states + - API: Remove const argv requirement for debugserver_command_new + - cython: Add get_path_for_bundle_identifier() method to + InstallationProxyClient + - cython: Add DebugServerClient class to communicate with debugserver + - Comply to strict function prototypes by using (void) instead of just () + - Fix notification proxy shutdown process which was incorrectly implemented + - Fix linking problems on OS X + - Fix missing debug output which broke with the last release + - Unify and improve various debug messages + - Fix re-pairing if pairing with existing pair record failed initially + - Skip printing long plist (16kb+) files to prevent excessive debug output + - Move a few common helpers from backup tools to common utility helper code + - Remove incorrect flags from afc_file_open() documentation + - Fix various memory leaks + +Version 1.1.7 +~~~~~~~~~~~~~ + +* Development release "Cleaning up the yard." +* Changes: + - Fix broken app args, environment handling and memory leaks in idevicedebug + - Make all tools print an error if lockdown connection fails + - Convert int16_t macro error types into enum for better type-checking and + for various debugging benefits + - Avoid exporting non-public symbols for better ABI stability + - Fix failing backup process for devices having a passcode set and entering + lock state during the process in idevicebackup2 + - API: Added lockdownd_start_service_with_escrow_bag() + - API: Added afc_remove_path_and_contents() for recursive deletion + - Fix last memory leak with OpenSSL through proper library deinitialization + - Add new idevicedebug tool to interact with debugserver on a device + - API: Add debugserver service implementation + - Handle new PermissionDenied error of file_relay due new security in iOS 8+ + - Fix retry loop problem when device requests 0 files in idevicebackup2 + - Add trust dialog related error codes to Cython bindings + - Fix various memory leaks in AFC implementation + - Fix disk image upload with latest iOS 8 in ideviceimagemounter + - Add new "dump" command to print information about a provisioning profile in + ideviceprovision + - Refactor plist print helper code and move it into common module for better + reuse accross the tools + - Do not crash if retrieving the system buid fails + - API: Make generic "propery_list_service_client" public + - Moved doc comments from private to public headers + - Fix possible segfault when using lockdownd_get_value() due to always + returning success + - Do not read files entirely into memory during restore in idevicebackup + - Plug a few memory leaks and fix invalid password check in idevicebackup2 + - Add support for file sizes > 4GB on Win32 in idevicebackup2 + - Fix declaration for DllMain on Win32 + - Silence various compiler warnings + - Fix assert within pairing logic +* API is UNSTABLE + +Version 1.1.6 +~~~~~~~~~~~~~ + +* Development release "Way too overdue." +* Changes: + - Remove segmentation code from afc_file_read() to provide raw interface and + more control to API consumer I/O logic + - Implement global thread safe library initialization, especially to control + SSL backend lifecycle + - Major refactoring of pair record code and logic to use new usbmuxd pair + record management interface + - Replace user level with system wide pair record file handling + - Bump dependency to libplist 1.11 and remove use of "plist_dict_insert_item" + - Bump dependency to libusbmuxd 1.0.9 + - Finish pair record and trust dialog handling for iOS 7+ + - Improve AFC write performance and memory usage + - Add support for custom output filename to idevicescreenshot + - Fix detection and compilation for Python 3.x + - API: Added file_relay_request_sources_timeout() + - Fix broken HouseArrestClient class in cython bindings + - Add new idevicecrashreport tool to retrieve crash reports and logs from a + device + - Prevent "Failed to restart/shutdown device" messages in idevicediagnostics + - Link against ws2_32.dll on Win32 + - Add support for iOS 7+ disk image mounting to ideviceimagemounter + - Add new idevicename tool to get or set the device name + - Allow unbacking of encrypted backups with a given password to idevicebackup2 + - Remove sending "Goodbye" request on lockdown + - Add support for newer PLIST_REAL based time type to idevicedate + - Add note about setting time not working on iOS 6+ to idevicedate + - Handle partial SSL reads correctly now to prevent random crashes + - Fix duplicated output in ideviceinfo output + - Remove a bunch of dead code + - Fix deprecated OpenSSL "RSA_generate_key" with "RSA_generate_key_ex" which + is available since OpenSSL 0.9.8 (July 2005) + - Improve debug messages + - Enforce "-fsigned-char" to fix issues on embedded platforms + - Fix compilation with Clang/LLVM + - Avoid versioning for shared library on Win32 + - Add experimental support for controlling cloud backup mode to idevicebackup2 + - Save EscrowBag when starting service for automatic unlocking in pair record + - Remove pairing logic which is obsoleted by usbmuxd's preflight handler + - Fix shutdown of SSL connection to be correct and no longer generate errors + on device + - Add support for GnuTLS 3.x and fix broken GnuTLS backend + - Add extensions to generated certificates to match native ones + - Add "systembuid" command to idevicepair + - Allow starting service without the need for a running SSL session + - Refactor more code into common module + - Add option to filerelaytest to specify a source to request + - Fix support for partial messages in webinspector implementation + - Implement support for encrypted backups in idevicebackup2 + - API: Export SSL control functions for idevice_connection_t + - API: Make generic service client public to allow external service + implementations + - Implement *_start_service() helper for easier creation of service clients + - Add public *_SERVICE_NAME defines for each service + - Fix a great bunch of memory leaks after intensive valgrind session + - Security: Fix insecure use of the /tmp directory (CVE-2013-2142) + - A bunch of memory leak fixes + - Python: Various fixes and support for "with" statement for AfcFile class + - Python: Add Afc2Client class to allow jailbroken filesystem access + - Fix linking issue with newer libtool as reported for Ubuntu + - Fix stuck thread in idevicesyslog which broke quit from within the tool + - Add syslog_relay service implementation and use it in idevicesyslog + - API: Add instproxy_client_get_path_for_bundle_identifier() helper + - API: Add afc_dictionary_free() helper + - Move thread, socket, debug and userpref code to "common" source directory + in order to improve code reuse + - Fix broken byte order detection in configure.ac which could lead to broken + AFC protocol communication on platforms without endian.h (Raspberry PI) +* API is UNSTABLE + +Version 1.1.5 +~~~~~~~~~~~~~ + +* Development release +* Changes: + - Implement automatic reconnecting in idevicesyslog + - Refactor all services to use new base service + - Add new generic service_client_factory_start_service() helper + - Implement a base service that all services inherit from + - API: Refactor use of "port numbers" into a "service descriptor" which is + a needed change as all services must now transparently support SSL. + Fortunately, only minor changes are needed to migrate your code properly. + - Add experimental ideviceheartbeat to allow service checkin over the network + - Add heartbeat service implementation to keep alive network connections + - Add webinspector service implementation for WebKit remote debugging + - Fix idevicebackup2 failing due to integer overflow in free disk space + calculation on 32 bit architectures and large disk capacities + - Add support for encrypted and password protected backups to idevicebackup2 + - Fix major "too long filename received" bug in idevicebackup2 + - Various fixes for proper and tested WIN32 support including MinGW building + - Fix various crashers and improve quality of idevicebackup2 tool + - Add endianness helpers for systems lacking support + - Fix idevicedate to work on iOS 6+ + - Add idevicediagnostics tool + - Add diagnostics_relay service implementation + - Add idevicedebugserverproxy tool for remote lldb debugging + - Add ideviceprovision tool + - Add misagent service implementation to manage provisioning profiles + - Fix crash if $HOME is empty or not defined + - Fix non-ASCII characters being stripped when using plist communication + - Improve compile support for cython and check it at configure time + - Bump cython requirement to 0.17.0+ + - Fix compilation of cython bindings + - Python bindings now cover all C APIs + - Fix iOS 6 compatibility for mobilesync, mobilebackup, mobilebackup2 and + screenshotr by bumping device link protocol version number + - Do not strip non_ASCII characters from XML plists + - Fix possible crash when using OpenSSL +* API is UNSTABLE + +Version 1.1.4 +~~~~~~~~~~~~~ + +* Development release +* Changes: + - Fix a bug in idevicesyslog causing the connection to close after timeout + - Bump soname revision +* API is UNSTABLE + +Version 1.1.3 +~~~~~~~~~~~~~ + +* Development release +* Changes: + - Bump libusbmuxd dependency to 1.0.8 + - Fix reading from syslog_relay and remove null characters + - Relicense ideviceimagemounter and idevicescreenshot to LGPL + - Fix a crash when using restored_client_free() + - API: Add sbservices_get_interface_orientation() + - Update man pages and code comments for documentation + - Minor cleanup +* API is UNSTABLE + +Version 1.1.2 +~~~~~~~~~~~~~ + +* Development release +* Changes: + - Add Python bindings generated by Cython + - Bump libplist requirement to latest 1.8 + - Add support for OpenSSL with fallback to GNUTLS + - Improvements and various fixes for Win32 and OS X build + - Remove glib dependency + - Improve restored implementation + - Fix various memory leaks + - Fix support for iOS 5 and later +* SWIG Python Bindings are removed +* API is UNSTABLE + +Version 1.1.1 +~~~~~~~~~~~~~ + +* Development release +* Changes: + - Add new idevicebackup2 tool for full backup and restore support on iOS 4+ + - Add a workaround for a bug in iOS 4.3 affecting lockdown_get_value() which + most prominently affected libgpod, gvfs, ideviceinfo and some other tools + - Read ProxyDeath message to preventing obsolete messages in device syslog + - Rework SWIG detection and includes + - Add new idevicedate tool to get or set the clock on iDevices + - API: Add mobilesync_clear_all_records_on_device() + - API: Change device_link_service_disconnect() to accept a message + - Add manpages for ideviceenterrecovery, idevicepair, idevicebackup2 and + idevicedate + - Add missing libgen.h include to silence compiler warnings + - Fix a segfault that might occour if locally stored certs could not be read + - Fix various memory leaks + - Update documentation +* Python Bindings will get refactored completely +* API is UNSTABLE + Version 1.1.0 ~~~~~~~~~~~~~ @@ -22,6 +357,38 @@ Version 1.1.0 * Python Bindings will get refactored completely * API is UNSTABLE +Version 1.0.7 +~~~~~~~~~~~~~ + +* Maintenance release of stable series +* Changes: + - Fix SWIG 2.x detection + - Fix support for iOS 5 and later + - Flush output of idevicesyslog immediately + - Replace deprecated GNUTLS functions properly + - Fix segfaults in library and some tools + - Fix memory leaks + - Build fixes + +Version 1.0.6 +~~~~~~~~~~~~~ + +* Quick follow up release +* Changes: + - Add ideviceenterrecovery which was missing in last release by accident + +Version 1.0.5 +~~~~~~~~~~~~~ + +* Maintenance release of stable series +* Changes: + - Add a workaround for a bug in iOS 4.3 affecting lockdown_get_value() which + most prominently affected libgpod, gvfs, ideviceinfo and some other tools + - Read ProxyDeath message to preventing obsolete messages in device syslog + - Rework SWIG detection and includes + - Add manpages for ideviceenterrecovery and idevicepair + - Add missing libgen.h include to silence compiler warnings + Version 1.0.4 ~~~~~~~~~~~~~ @@ -1,62 +0,0 @@ -About -===== - -A library to communicate with services running on Apple iPhone/iPod Touch -devices. - -Requirements -============ - -Development Packages of: - libgnutls - libgcrypt - glib2.0 - libplist - libusbmuxd (within usbmuxd package, see below) - -Software: - usbmuxd (git clone git://git.marcansoft.com/usbmuxd.git) - make - autoheader - automake - autoconf - libtool - gcc - -Installation -============ - -To compile run: - ./configure - make - sudo make install - -On Ubuntu/Debian, you can do: - sudo apt-get install build-essential automake autoconf libtool\ - libgnutls-dev libglib2.0-dev libxml2-dev libreadline5-dev - -Who/What/Where? -=============== - -Home: - http://www.libimobiledevice.org/ - -Code: - git clone git://git.sukimashita.com/libimobiledevice.git - -Tickets: - http://libiphone.lighthouseapp.com/ - -Mailing List: - http://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel - -Credits -======= - -Apple, iPhone, iPod, and iPod Touch are trademarks of Apple Inc. -libimobiledevice is an independent software library and has not been -authorized, sponsored, or otherwise approved by Apple Inc. - -README Updated on: - 2010-03-31 - diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec057a6 --- /dev/null +++ b/README.md @@ -0,0 +1,201 @@ +# libimobiledevice + +*A library to communicate with services on iOS devices using native protocols.* + + + +## Features + +libimobiledevice is a cross-platform software library that talks the protocols +to interact with iOS devices. + +Unlike other projects, it does not depend on using any existing proprietary +libraries and does not require jailbreaking. + +Some key features are: + +- **Interface**: Implements many high-level interfaces for device services +- **Implementation**: Object oriented architecture and service abstraction layer +- **Cross-Platform:** Tested on Linux, macOS, Windows and Android platforms +- **Utilities**: Provides various command-line utilities for device services +- **SSL**: Allows choosing between OpenSSL, GnuTLS, or MbedTLS to handle SSL communication +- **Network**: Supports network connections with "WiFi sync" enabled devices +- **Python:** Provides Cython based bindings for Python + +The implemented interfaces of many device service protocols allow applications +to: + +* Access filesystem of a device +* Access documents of file sharing apps +* Retrieve information about a device and modify various settings +* Backup and restore the device in a native way compatible with iTunes +* Manage app icons arrangement on the device +* Install, remove, list and basically manage apps +* Activate a device using official servers +* Manage contacts, calendars, notes and bookmarks +* Retrieve and remove crashreports +* Retrieve various diagnostics information +* Establish a debug connection for app debugging +* Mount filesystem images +* Forward device notifications +* Manage device provisioning +* Take screenshots from the device screen (requires mounted developer image) +* Simulate changed geolocation of the device (requires mounted developer image) +* Relay the syslog of the device +* Expose a connection for WebKit remote debugging + +... and much more. + +The library is in development since August 2007 with the goal to bring support +for these devices to the Linux Desktop. + +## Installation / Getting started + +### Debian / Ubuntu Linux + +First install all required dependencies and build tools: +```shell +sudo apt-get install \ + build-essential \ + pkg-config \ + checkinstall \ + git \ + autoconf \ + automake \ + libtool-bin \ + libplist-dev \ + libusbmuxd-dev \ + libimobiledevice-glue-dev \ + libtatsu-dev \ + libssl-dev \ + usbmuxd +``` +NOTE: [libtatsu](https://github.com/libimobiledevice/libtatsu) (and thus `libtatsu-dev`) +is a new library that was just published recently, you have to +[build it from source](https://github.com/libimobiledevice/libtatsu?tab=readme-ov-file#building). + +If you want to optionally build the documentation or Python bindings use: +```shell +sudo apt-get install \ + doxygen \ + cython +``` + +Then clone the actual project repository: +```shell +git clone https://github.com/libimobiledevice/libimobiledevice.git +cd libimobiledevice +``` + +Now you can build and install it: +```shell +./autogen.sh +make +sudo make install +``` + +If you require a custom prefix or other option being passed to `./configure` +you can pass them directly to `./autogen.sh` like this: +```bash +./autogen.sh --prefix=/opt/local --enable-debug +make +sudo make install +``` + +By default, OpenSSL will be used as TLS/SSL library. If you prefer GnuTLS, +configure with `--with-gnutls` like this: +```bash +./autogen.sh --with-gnutls +``` + +MbedTLS is also supported and can be enabled by passing `--with-mbedtls` to +configure. If mbedTLS is not installed in a default location, you need to set +the environment variables `mbedtls_INCLUDES` to the path that contains the +MbedTLS headers and `mbedtls_LIBDIR` to set the library path. Optionally, +`mbedtls_LIBS` can be used to set the library names directly. Example: +```bash +./autogen.sh --with-mbedtls mbedtls_INCLUDES=/opt/local/include mbedtls_LIBDIR=/opt/local/lib +``` + +## Usage + +Documentation about using the library in your application is not available yet. +The "hacker way" for now is to look at the implementation of the included +utilities. + +### Utilities + +The library bundles the following command-line utilities in the tools directory: + +| Utility | Description | +| -------------------------- | ------------------------------------------------------------------ | +| `idevice_id` | List attached devices or print device name of given device | +| `idevicebackup` | Create or restore backup for devices (legacy) | +| `idevicebackup2` | Create or restore backups for devices running iOS 4 or later | +| `idevicebtlogger` | Capture Bluetooth HCI traffic from a device (requires log profile) | +| `idevicecrashreport` | Retrieve crash reports from a device | +| `idevicedate` | Display the current date or set it on a device | +| `idevicedebug` | Interact with the debugserver service of a device | +| `idevicedebugserverproxy` | Proxy a debugserver connection from a device for remote debugging | +| `idevicediagnostics` | Interact with the diagnostics interface of a device | +| `ideviceenterrecovery` | Make a device enter recovery mode | +| `ideviceimagemounter` | Mount disk images on the device | +| `ideviceinfo` | Show information about a connected device | +| `idevicename` | Display or set the device name | +| `idevicenotificationproxy` | Post or observe notifications on a device | +| `idevicepair` | Manage host pairings with devices and usbmuxd | +| `ideviceprovision` | Manage provisioning profiles on a device | +| `idevicescreenshot` | Gets a screenshot from the connected device | +| `idevicesetlocation` | Simulate location on device | +| `idevicesyslog` | Relay syslog of a connected device | +| `afcclient` | Interact with device filesystem via AFC/HouseArrest | + +Please consult the usage information or manual pages of each utility for a +documentation of available command line options and usage examples like this: +```shell +ideviceinfo --help +man ideviceinfo +``` + +## Contributing + +We welcome contributions from anyone and are grateful for every pull request! + +If you'd like to contribute, please fork the `master` branch, change, commit and +send a pull request for review. Once approved it can be merged into the main +code base. + +If you plan to contribute larger changes or a major refactoring, please create a +ticket first to discuss the idea upfront to ensure less effort for everyone. + +Please make sure your contribution adheres to: +* Try to follow the code style of the project +* Commit messages should describe the change well without being too short +* Try to split larger changes into individual commits of a common domain +* Use your real name and a valid email address for your commits + +We are still working on the guidelines so bear with us! + +## Links + +* Homepage: https://libimobiledevice.org/ +* Repository: https://git.libimobiledevice.org/libimobiledevice.git +* Repository (Mirror): https://github.com/libimobiledevice/libimobiledevice.git +* Issue Tracker: https://github.com/libimobiledevice/libimobiledevice/issues +* Mailing List: https://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel +* Twitter: https://twitter.com/libimobiledev + +## License + +This library and utilities are licensed under the [GNU Lesser General Public License v2.1](https://www.gnu.org/licenses/lgpl-2.1.en.html), +also included in the repository in the `COPYING` file. + +## Credits + +Apple, iPhone, iPad, iPod, iPod Touch, Apple TV, Apple Watch, Mac, iOS, +iPadOS, tvOS, watchOS, and macOS are trademarks of Apple Inc. + +This project is an independent software and has not been authorized, sponsored, +or otherwise approved by Apple Inc. + +README Updated on: 2024-06-27 @@ -1,15 +1,26 @@ #!/bin/sh -gprefix=`which glibtoolize 2>&1 >/dev/null` -if [ $? -eq 0 ]; then - glibtoolize --force -else - libtoolize --force -fi -aclocal -I m4 -autoheader -automake --add-missing -autoconf + +olddir=`pwd` +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +( + cd "$srcdir" + + gprefix=`which glibtoolize 2>&1 >/dev/null` + if [ $? -eq 0 ]; then + glibtoolize --force + else + libtoolize --force + fi + aclocal -I m4 + autoheader + automake --add-missing + autoconf + + cd "$olddir" +) if [ -z "$NOCONFIGURE" ]; then - ./configure "$@" + $srcdir/configure "$@" fi diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..ba7ed9c --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,29 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) + +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + $(ssl_lib_CFLAGS) \ + $(LFS_CFLAGS) \ + $(libusbmuxd_CFLAGS) \ + $(limd_glue_CFLAGS) \ + $(libplist_CFLAGS) + +AM_LDFLAGS = \ + $(ssl_lib_LIBS) \ + ${libpthread_LIBS} \ + $(libusbmuxd_LIBS) \ + $(limd_glue_LIBS) \ + $(libplist_LIBS) + +noinst_LTLIBRARIES = libinternalcommon.la +libinternalcommon_la_LIBADD = +libinternalcommon_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined +libinternalcommon_la_SOURCES = \ + debug.c debug.h \ + userpref.c userpref.h + +if WIN32 +libinternalcommon_la_LIBADD += -lole32 -lws2_32 +endif diff --git a/src/debug.c b/common/debug.c index 26a9678..cf1bc2f 100644 --- a/src/debug.c +++ b/common/debug.c @@ -9,15 +9,15 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H @@ -29,23 +29,25 @@ #include <stdio.h> #include <stdint.h> #include <stdlib.h> +#include <time.h> +#include "src/idevice.h" #include "debug.h" #include "libimobiledevice/libimobiledevice.h" -int debug_level = 0; +#ifndef STRIP_DEBUG_CODE +#include "asprintf.h" +#endif -/** - * Sets the level of debugging. Currently the only acceptable values are 0 and - * 1. - * - * @param level Set to 0 for no debugging or 1 for debugging. - */ -void idevice_set_debug_level(int level) +static int debug_level; + +void internal_set_debug_level(int level) { debug_level = level; } +#define MAX_PRINT_LEN (16*1024) + #ifndef STRIP_DEBUG_CODE static void debug_print_line(const char *func, const char *file, int line, const char *buffer) { @@ -54,29 +56,26 @@ static void debug_print_line(const char *func, const char *file, int line, const time_t the_time; time(&the_time); - str_time = g_new0 (gchar, 255); + str_time = (char*)malloc(255); strftime(str_time, 254, "%H:%M:%S", localtime (&the_time)); /* generate header text */ - (void)asprintf(&header, "%s %s:%d %s()", str_time, file, line, func); + if(asprintf(&header, "%s %s:%d %s()", str_time, file, line, func)<0){} free (str_time); /* trim ending newlines */ /* print header */ - printf ("%s: ", header); + fprintf(stderr, "%s: ", header); /* print actual debug content */ - printf ("%s\n", buffer); - - /* flush this output, as we need to debug */ - fflush (stdout); + fprintf(stderr, "%s\n", buffer); free (header); } #endif -inline void debug_info_real(const char *func, const char *file, int line, const char *format, ...) +void debug_info_real(const char *func, const char *file, int line, const char *format, ...) { #ifndef STRIP_DEBUG_CODE va_list args; @@ -87,7 +86,7 @@ inline void debug_info_real(const char *func, const char *file, int line, const /* run the real fprintf */ va_start(args, format); - (void)vasprintf(&buffer, format, args); + if(vasprintf(&buffer, format, args)<0){} va_end(args); debug_print_line(func, file, line, buffer); @@ -96,7 +95,7 @@ inline void debug_info_real(const char *func, const char *file, int line, const #endif } -inline void debug_buffer(const char *data, const int length) +void debug_buffer(const char *data, const int length) { #ifndef STRIP_DEBUG_CODE int i; @@ -111,7 +110,7 @@ inline void debug_buffer(const char *data, const int length) fprintf(stderr, " "); continue; } - fprintf(stderr, "%02hhx ", *(data + i + j)); + fprintf(stderr, "%02x ", *(data + i + j) & 0xff); } fprintf(stderr, " | "); for (j = 0; j < 16; j++) { @@ -131,11 +130,11 @@ inline void debug_buffer(const char *data, const int length) #endif } -inline void debug_buffer_to_file(const char *file, const char *data, const int length) +void debug_buffer_to_file(const char *file, const char *data, const int length) { #ifndef STRIP_DEBUG_CODE if (debug_level) { - FILE *f = fopen(file, "w+"); + FILE *f = fopen(file, "wb"); fwrite(data, 1, length, f); fflush(f); fclose(f); @@ -143,7 +142,7 @@ inline void debug_buffer_to_file(const char *file, const char *data, const int l #endif } -inline void debug_plist_real(const char *func, const char *file, int line, plist_t plist) +void debug_plist_real(const char *func, const char *file, int line, plist_t plist) { #ifndef STRIP_DEBUG_CODE if (!plist) @@ -157,7 +156,11 @@ inline void debug_plist_real(const char *func, const char *file, int line, plist if (buffer[length-1] == '\n') buffer[length-1] = '\0'; - debug_info_real(func, file, line, "printing %i bytes plist:\n%s", length, buffer); + if (length <= MAX_PRINT_LEN) + debug_info_real(func, file, line, "printing %i bytes plist:\n%s", length, buffer); + else + debug_info_real(func, file, line, "supress printing %i bytes plist...\n", length); + free(buffer); #endif } diff --git a/src/debug.h b/common/debug.h index 2fd0960..882072d 100644 --- a/src/debug.h +++ b/common/debug.h @@ -9,22 +9,21 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef DEBUG_H -#define DEBUG_H +#ifndef __DEBUG_H +#define __DEBUG_H #include <plist/plist.h> -#include <glib.h> #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L && !defined(STRIP_DEBUG_CODE) #define debug_info(...) debug_info_real (__func__, __FILE__, __LINE__, __VA_ARGS__) @@ -37,16 +36,18 @@ #define debug_plist(a) #endif -G_GNUC_INTERNAL inline void debug_info_real(const char *func, +void debug_info_real(const char *func, const char *file, int line, const char *format, ...); -G_GNUC_INTERNAL inline void debug_buffer(const char *data, const int length); -G_GNUC_INTERNAL inline void debug_buffer_to_file(const char *file, const char *data, const int length); -G_GNUC_INTERNAL inline void debug_plist_real(const char *func, +void debug_buffer(const char *data, const int length); +void debug_buffer_to_file(const char *file, const char *data, const int length); +void debug_plist_real(const char *func, const char *file, int line, plist_t plist); +void internal_set_debug_level(int level); + #endif diff --git a/common/userpref.c b/common/userpref.c new file mode 100644 index 0000000..48bcfcb --- /dev/null +++ b/common/userpref.c @@ -0,0 +1,1171 @@ +/* + * userpref.c + * contains methods to access user specific certificates IDs and more. + * + * Copyright (c) 2013-2021 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2013-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2008 Jonathan Beck All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifndef WIN32 +#include <pwd.h> +#endif +#include <unistd.h> +#include <usbmuxd.h> +#if defined(HAVE_OPENSSL) +#include <openssl/bn.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#if OPENSSL_VERSION_NUMBER < 0x1010000fL || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x20700000L)) +#define X509_set1_notBefore X509_set_notBefore +#define X509_set1_notAfter X509_set_notAfter +#endif +#elif defined(HAVE_GNUTLS) +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <gnutls/x509.h> +#include <gcrypt.h> +#include <libtasn1.h> +#elif defined(HAVE_MBEDTLS) +#include <mbedtls/ssl.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/asn1write.h> +#include <mbedtls/oid.h> +#else +#error No supported TLS/SSL library enabled +#endif + +#include <dirent.h> +#include <libgen.h> +#include <sys/stat.h> +#include <errno.h> + +#ifdef WIN32 +#include <shlobj.h> +#endif + +#ifndef ETIMEDOUT +#define ETIMEDOUT 138 +#endif + +#include <libimobiledevice-glue/utils.h> + +#include "userpref.h" +#include "debug.h" + +#if defined(HAVE_GNUTLS) +const ASN1_ARRAY_TYPE pkcs1_asn1_tab[] = { + {"PKCS1", 536872976, 0}, + {0, 1073741836, 0}, + {"RSAPublicKey", 536870917, 0}, + {"modulus", 1073741827, 0}, + {"publicExponent", 3, 0}, + {0, 0, 0} +}; +#endif + +#ifdef WIN32 +#define DIR_SEP '\\' +#define DIR_SEP_S "\\" +#else +#define DIR_SEP '/' +#define DIR_SEP_S "/" +#endif + +#define USERPREF_CONFIG_EXTENSION ".plist" + +#ifdef WIN32 +#define USERPREF_CONFIG_DIR "Apple"DIR_SEP_S"Lockdown" +#else +#define USERPREF_CONFIG_DIR "lockdown" +#endif + +#define USERPREF_CONFIG_FILE "SystemConfiguration"USERPREF_CONFIG_EXTENSION + +static char *__config_dir = NULL; + +#ifdef WIN32 +static char *userpref_utf16_to_utf8(wchar_t *unistr, long len, long *items_read, long *items_written) +{ + if (!unistr || (len <= 0)) return NULL; + char *outbuf = (char*)malloc(3*(len+1)); + int p = 0; + int i = 0; + + wchar_t wc; + + while (i < len) { + wc = unistr[i++]; + if (wc >= 0x800) { + outbuf[p++] = (char)(0xE0 + ((wc >> 12) & 0xF)); + outbuf[p++] = (char)(0x80 + ((wc >> 6) & 0x3F)); + outbuf[p++] = (char)(0x80 + (wc & 0x3F)); + } else if (wc >= 0x80) { + outbuf[p++] = (char)(0xC0 + ((wc >> 6) & 0x1F)); + outbuf[p++] = (char)(0x80 + (wc & 0x3F)); + } else { + outbuf[p++] = (char)(wc & 0x7F); + } + } + if (items_read) { + *items_read = i; + } + if (items_written) { + *items_written = p; + } + outbuf[p] = 0; + + return outbuf; +} +#endif + +const char *userpref_get_config_dir() +{ + char *base_config_dir = NULL; + + if (__config_dir) + return __config_dir; + +#ifdef WIN32 + wchar_t path[MAX_PATH+1]; + HRESULT hr; + LPITEMIDLIST pidl = NULL; + BOOL b = FALSE; + + hr = SHGetSpecialFolderLocation (NULL, CSIDL_COMMON_APPDATA, &pidl); + if (hr == S_OK) { + b = SHGetPathFromIDListW (pidl, path); + if (b) { + base_config_dir = userpref_utf16_to_utf8 (path, wcslen(path), NULL, NULL); + CoTaskMemFree (pidl); + } + } +#else +#ifdef __APPLE__ + base_config_dir = strdup("/var/db"); +#else + base_config_dir = strdup("/var/lib"); +#endif +#endif + __config_dir = string_concat(base_config_dir, DIR_SEP_S, USERPREF_CONFIG_DIR, NULL); + + if (__config_dir) { + int i = strlen(__config_dir)-1; + while ((i > 0) && (__config_dir[i] == DIR_SEP)) { + __config_dir[i--] = '\0'; + } + } + + free(base_config_dir); + + debug_info("initialized config_dir to %s", __config_dir); + + return __config_dir; +} + +/** + * Reads the SystemBUID from a previously generated configuration file. + * + * @note It is the responsibility of the calling function to free the returned system_buid. + * @param system_buid A pointer that will be set to a newly allocated string containing the + * SystemBUID upon successful return. + * @return 0 if the SystemBUID has been successfully retrieved or < 0 otherwise. + */ +int userpref_read_system_buid(char **system_buid) +{ + int res = usbmuxd_read_buid(system_buid); + if (res == 0) { + debug_info("using %s as %s", *system_buid, USERPREF_SYSTEM_BUID_KEY); + } else { + debug_info("could not read system buid, error %d", res); + } + return res; +} + +/** + * Fills a list with UDIDs of devices that have been connected to this + * system before, i.e. for which a public key file exists. + * + * @param list A pointer to a char** initially pointing to NULL that will + * hold a newly allocated list of UDIDs upon successful return. + * The caller is responsible for freeing the memory. Note that if + * no public key file was found the list has to be freed too as it + * points to a terminating NULL element. + * @param count The number of UDIDs found. This parameter can be NULL if it + * is not required. + * + * @return USERPREF_E_SUCCESS on success, or USERPREF_E_INVALID_ARG if the + * list parameter is not pointing to NULL. + */ +userpref_error_t userpref_get_paired_udids(char ***list, unsigned int *count) +{ + DIR *config_dir; + const char *config_path = NULL; + unsigned int found = 0; + + if (!list || (list && *list)) { + debug_info("ERROR: The list parameter needs to point to NULL!"); + return USERPREF_E_INVALID_ARG; + } + + if (count) { + *count = 0; + } + *list = (char**)malloc(sizeof(char*)); + + config_path = userpref_get_config_dir(); + config_dir = opendir(config_path); + if (config_dir) { + struct dirent *entry; + while ((entry = readdir(config_dir))) { + if (strcmp(entry->d_name, USERPREF_CONFIG_FILE) == 0) { + /* ignore SystemConfiguration.plist */ + continue; + } + char *ext = strrchr(entry->d_name, '.'); + if (ext && (strcmp(ext, USERPREF_CONFIG_EXTENSION) == 0)) { + size_t len = strlen(entry->d_name) - strlen(USERPREF_CONFIG_EXTENSION); + char **newlist = (char**)realloc(*list, sizeof(char*) * (found+2)); + if (!newlist) { + fprintf(stderr, "ERROR: Out of memory\n"); + break; + } + *list = newlist; + char *tmp = (char*)malloc(len+1); + if (tmp) { + strncpy(tmp, entry->d_name, len); + tmp[len] = '\0'; + } + (*list)[found] = tmp; + if (!tmp) { + fprintf(stderr, "ERROR: Out of memory\n"); + break; + } + found++; + } + } + closedir(config_dir); + } + (*list)[found] = NULL; + + if (count) { + *count = found; + } + + return USERPREF_E_SUCCESS; +} + +/** + * Save a pair record for a device. + * + * @param udid The device UDID as given by the device + * @param device_id The usbmux device id (handle) of the connected device, or 0 + * @param pair_record The pair record to save + * + * @return 1 on success and 0 if no device record is given or if it has already + * been saved previously. + */ +userpref_error_t userpref_save_pair_record(const char *udid, uint32_t device_id, plist_t pair_record) +{ + char* record_data = NULL; + uint32_t record_size = 0; + + plist_to_bin(pair_record, &record_data, &record_size); + + int res = usbmuxd_save_pair_record_with_device_id(udid, device_id, record_data, record_size); + + free(record_data); + + return res == 0 ? USERPREF_E_SUCCESS: USERPREF_E_UNKNOWN_ERROR; +} + +/** + * Read a pair record for a device. + * + * @param udid The device UDID as given by the device + * @param pair_record The pair record to read + * + * @return USERPREF_E_SUCCESS on success, + * USERPREF_E_NOENT if no pairing record was found, + * USERPREF_E_READ_ERROR if retrieving the pairing record from usbmuxd failed, + * or USERPREF_E_INVALID_CONF otherwise. + */ +userpref_error_t userpref_read_pair_record(const char *udid, plist_t *pair_record) +{ + char* record_data = NULL; + uint32_t record_size = 0; + + int res = usbmuxd_read_pair_record(udid, &record_data, &record_size); + if (res < 0) { + free(record_data); + switch (-res) { + case ENOENT: + return USERPREF_E_NOENT; + case ETIMEDOUT: + return USERPREF_E_READ_ERROR; + default: + return USERPREF_E_INVALID_CONF; + } + } + + *pair_record = NULL; + plist_from_memory(record_data, record_size, pair_record, NULL); + free(record_data); + + if (!*pair_record) { + debug_info("Failed to parse pairing record"); + return USERPREF_E_INVALID_CONF; + } + return USERPREF_E_SUCCESS; +} + +/** + * Remove the pairing record stored for a device from this host. + * + * @param udid The udid of the device + * + * @return USERPREF_E_SUCCESS on success. + */ +userpref_error_t userpref_delete_pair_record(const char *udid) +{ + int res = usbmuxd_delete_pair_record(udid); + + return res == 0 ? USERPREF_E_SUCCESS: USERPREF_E_UNKNOWN_ERROR; +} + +#if defined(HAVE_OPENSSL) +static int X509_add_ext_helper(X509 *cert, int nid, char *value) +{ + X509_EXTENSION *ex; + X509V3_CTX ctx; + + /* No configuration database */ + X509V3_set_ctx_nodb(&ctx); + + X509V3_set_ctx(&ctx, NULL, cert, NULL, NULL, 0); + ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value); + if (!ex) { + debug_info("ERROR: X509V3_EXT_conf_nid(%d, %s) failed", nid, value); + return 0; + } + + X509_add_ext(cert, ex, -1); + X509_EXTENSION_free(ex); + + return 1; +} +#elif defined(HAVE_MBEDTLS) +static int _mbedtls_x509write_crt_set_basic_constraints_critical(mbedtls_x509write_cert *ctx, int is_ca, int max_pathlen) +{ + int ret; + unsigned char buf[9]; + unsigned char *c = buf + sizeof(buf); + size_t len = 0; + + memset( buf, 0, sizeof(buf) ); + + if (is_ca && max_pathlen > 127) + return( MBEDTLS_ERR_X509_BAD_INPUT_DATA ); + + if (is_ca) { + if (max_pathlen >= 0) { + MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_int( &c, buf, max_pathlen ) ); + } + MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_bool( &c, buf, 1 ) ); + } + + MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_len( &c, buf, len ) ); + MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_tag( &c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ); + + return mbedtls_x509write_crt_set_extension( ctx, MBEDTLS_OID_BASIC_CONSTRAINTS, MBEDTLS_OID_SIZE( MBEDTLS_OID_BASIC_CONSTRAINTS ), 1, buf + sizeof(buf) - len, len ); +} +#endif + +/** + * Private function to generate required private keys and certificates. + * + * @param pair_record a #PLIST_DICT that will be filled with the keys + * and certificates + * @param public_key the public key to use (device public key) + * + * @return 1 if keys were successfully generated, 0 otherwise + */ +userpref_error_t pair_record_generate_keys_and_certs(plist_t pair_record, key_data_t public_key) +{ + userpref_error_t ret = USERPREF_E_SSL_ERROR; + + key_data_t dev_cert_pem = { NULL, 0 }; + key_data_t root_key_pem = { NULL, 0 }; + key_data_t root_cert_pem = { NULL, 0 }; + key_data_t host_key_pem = { NULL, 0 }; + key_data_t host_cert_pem = { NULL, 0 }; + + if (!pair_record || !public_key.data) + return USERPREF_E_INVALID_ARG; + + debug_info("Generating keys and certificates..."); + +#if defined(HAVE_OPENSSL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY* root_pkey = EVP_RSA_gen(2048); + EVP_PKEY* host_pkey = EVP_RSA_gen(2048); +#else + BIGNUM *e = BN_new(); + RSA* root_keypair = RSA_new(); + RSA* host_keypair = RSA_new(); + + BN_set_word(e, 65537); + + RSA_generate_key_ex(root_keypair, 2048, e, NULL); + RSA_generate_key_ex(host_keypair, 2048, e, NULL); + + BN_free(e); + + EVP_PKEY* root_pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(root_pkey, root_keypair); + + EVP_PKEY* host_pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(host_pkey, host_keypair); +#endif + + /* generate root certificate */ + X509* root_cert = X509_new(); + { + /* set serial number */ + ASN1_INTEGER* sn = ASN1_INTEGER_new(); + ASN1_INTEGER_set(sn, 0); + X509_set_serialNumber(root_cert, sn); + ASN1_INTEGER_free(sn); + + /* set version */ + X509_set_version(root_cert, 2); + + /* set x509v3 basic constraints */ + X509_add_ext_helper(root_cert, NID_basic_constraints, (char*)"critical,CA:TRUE"); + + /* set key validity */ + ASN1_TIME* asn1time = ASN1_TIME_new(); + ASN1_TIME_set(asn1time, time(NULL)); + X509_set1_notBefore(root_cert, asn1time); + ASN1_TIME_set(asn1time, time(NULL) + (60 * 60 * 24 * 365 * 10)); + X509_set1_notAfter(root_cert, asn1time); + ASN1_TIME_free(asn1time); + + /* use root public key for root cert */ + X509_set_pubkey(root_cert, root_pkey); + + /* sign root cert with root private key */ + X509_sign(root_cert, root_pkey, EVP_sha1()); + } + + /* create host certificate */ + X509* host_cert = X509_new(); + { + /* set serial number */ + ASN1_INTEGER* sn = ASN1_INTEGER_new(); + ASN1_INTEGER_set(sn, 0); + X509_set_serialNumber(host_cert, sn); + ASN1_INTEGER_free(sn); + + /* set version */ + X509_set_version(host_cert, 2); + + /* set x509v3 basic constraints */ + X509_add_ext_helper(host_cert, NID_basic_constraints, (char*)"critical,CA:FALSE"); + + /* set x509v3 key usage */ + X509_add_ext_helper(host_cert, NID_key_usage, (char*)"critical,digitalSignature,keyEncipherment"); + + /* set key validity */ + ASN1_TIME* asn1time = ASN1_TIME_new(); + ASN1_TIME_set(asn1time, time(NULL)); + X509_set1_notBefore(host_cert, asn1time); + ASN1_TIME_set(asn1time, time(NULL) + (60 * 60 * 24 * 365 * 10)); + X509_set1_notAfter(host_cert, asn1time); + ASN1_TIME_free(asn1time); + + /* use host public key for host cert */ + X509_set_pubkey(host_cert, host_pkey); + + /* sign host cert with root private key */ + X509_sign(host_cert, root_pkey, EVP_sha1()); + } + + if (root_cert && root_pkey && host_cert && host_pkey) { + BIO* membp; + char *bdata; + + membp = BIO_new(BIO_s_mem()); + if (PEM_write_bio_X509(membp, root_cert) > 0) { + root_cert_pem.size = BIO_get_mem_data(membp, &bdata); + root_cert_pem.data = (unsigned char*)malloc(root_cert_pem.size); + if (root_cert_pem.data) { + memcpy(root_cert_pem.data, bdata, root_cert_pem.size); + } + BIO_free(membp); + membp = NULL; + } + membp = BIO_new(BIO_s_mem()); + if (PEM_write_bio_PrivateKey(membp, root_pkey, NULL, NULL, 0, 0, NULL) > 0) { + root_key_pem.size = BIO_get_mem_data(membp, &bdata); + root_key_pem.data = (unsigned char*)malloc(root_key_pem.size); + if (root_key_pem.data) { + memcpy(root_key_pem.data, bdata, root_key_pem.size); + } + BIO_free(membp); + membp = NULL; + } + membp = BIO_new(BIO_s_mem()); + if (PEM_write_bio_X509(membp, host_cert) > 0) { + host_cert_pem.size = BIO_get_mem_data(membp, &bdata); + host_cert_pem.data = (unsigned char*)malloc(host_cert_pem.size); + if (host_cert_pem.data) { + memcpy(host_cert_pem.data, bdata, host_cert_pem.size); + } + BIO_free(membp); + membp = NULL; + } + membp = BIO_new(BIO_s_mem()); + if (PEM_write_bio_PrivateKey(membp, host_pkey, NULL, NULL, 0, 0, NULL) > 0) { + host_key_pem.size = BIO_get_mem_data(membp, &bdata); + host_key_pem.data = (unsigned char*)malloc(host_key_pem.size); + if (host_key_pem.data) { + memcpy(host_key_pem.data, bdata, host_key_pem.size); + } + BIO_free(membp); + membp = NULL; + } + } + + EVP_PKEY *pubkey = NULL; + { + BIO *membp = BIO_new_mem_buf(public_key.data, public_key.size); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (!PEM_read_bio_PUBKEY(membp, &pubkey, NULL, NULL)) { + debug_info("WARNING: Could not read public key"); + } +#else + RSA *rsa_pubkey = NULL; + if (!PEM_read_bio_RSAPublicKey(membp, &rsa_pubkey, NULL, NULL)) { + debug_info("WARNING: Could not read public key"); + } else { + pubkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(pubkey, rsa_pubkey); + } +#endif + BIO_free(membp); + } + + X509* dev_cert = X509_new(); + if (pubkey && dev_cert) { + /* generate device certificate */ + ASN1_INTEGER* sn = ASN1_INTEGER_new(); + ASN1_INTEGER_set(sn, 0); + X509_set_serialNumber(dev_cert, sn); + ASN1_INTEGER_free(sn); + X509_set_version(dev_cert, 2); + + X509_add_ext_helper(dev_cert, NID_basic_constraints, (char*)"critical,CA:FALSE"); + + ASN1_TIME* asn1time = ASN1_TIME_new(); + ASN1_TIME_set(asn1time, time(NULL)); + X509_set1_notBefore(dev_cert, asn1time); + ASN1_TIME_set(asn1time, time(NULL) + (60 * 60 * 24 * 365 * 10)); + X509_set1_notAfter(dev_cert, asn1time); + ASN1_TIME_free(asn1time); + + X509_set_pubkey(dev_cert, pubkey); + + X509_add_ext_helper(dev_cert, NID_subject_key_identifier, (char*)"hash"); + X509_add_ext_helper(dev_cert, NID_key_usage, (char*)"critical,digitalSignature,keyEncipherment"); + + /* sign device certificate with root private key */ + if (X509_sign(dev_cert, root_pkey, EVP_sha1())) { + /* if signing succeeded, export in PEM format */ + BIO* membp = BIO_new(BIO_s_mem()); + if (PEM_write_bio_X509(membp, dev_cert) > 0) { + char *bdata = NULL; + dev_cert_pem.size = BIO_get_mem_data(membp, &bdata); + dev_cert_pem.data = (unsigned char*)malloc(dev_cert_pem.size); + if (dev_cert_pem.data) { + memcpy(dev_cert_pem.data, bdata, dev_cert_pem.size); + } + BIO_free(membp); + membp = NULL; + } + } else { + debug_info("ERROR: Signing device certificate with root private key failed!"); + } + } + + X509_free(dev_cert); + + EVP_PKEY_free(pubkey); + EVP_PKEY_free(root_pkey); + EVP_PKEY_free(host_pkey); + + X509_free(host_cert); + X509_free(root_cert); +#elif defined(HAVE_GNUTLS) + gnutls_x509_privkey_t root_privkey; + gnutls_x509_crt_t root_cert; + gnutls_x509_privkey_t host_privkey; + gnutls_x509_crt_t host_cert; + + /* use less secure random to speed up key generation */ + gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM); + + gnutls_x509_privkey_init(&root_privkey); + gnutls_x509_privkey_init(&host_privkey); + + gnutls_x509_crt_init(&root_cert); + gnutls_x509_crt_init(&host_cert); + + /* generate root key */ + gnutls_x509_privkey_generate(root_privkey, GNUTLS_PK_RSA, 2048, 0); + gnutls_x509_privkey_generate(host_privkey, GNUTLS_PK_RSA, 2048, 0); + + /* generate certificates */ + gnutls_x509_crt_set_key(root_cert, root_privkey); + gnutls_x509_crt_set_serial(root_cert, "\x01", 1); + gnutls_x509_crt_set_version(root_cert, 3); + gnutls_x509_crt_set_ca_status(root_cert, 1); + gnutls_x509_crt_set_activation_time(root_cert, time(NULL)); + gnutls_x509_crt_set_expiration_time(root_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); + gnutls_x509_crt_sign2(root_cert, root_cert, root_privkey, GNUTLS_DIG_SHA1, 0); + + gnutls_x509_crt_set_key(host_cert, host_privkey); + gnutls_x509_crt_set_serial(host_cert, "\x01", 1); + gnutls_x509_crt_set_version(host_cert, 3); + gnutls_x509_crt_set_ca_status(host_cert, 0); + gnutls_x509_crt_set_key_usage(host_cert, GNUTLS_KEY_KEY_ENCIPHERMENT | GNUTLS_KEY_DIGITAL_SIGNATURE); + gnutls_x509_crt_set_activation_time(host_cert, time(NULL)); + gnutls_x509_crt_set_expiration_time(host_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); + gnutls_x509_crt_sign2(host_cert, root_cert, root_privkey, GNUTLS_DIG_SHA1, 0); + + /* export to PEM format */ + size_t root_key_export_size = 0; + size_t host_key_export_size = 0; + + gnutls_x509_privkey_export(root_privkey, GNUTLS_X509_FMT_PEM, NULL, &root_key_export_size); + gnutls_x509_privkey_export(host_privkey, GNUTLS_X509_FMT_PEM, NULL, &host_key_export_size); + + root_key_pem.data = gnutls_malloc(root_key_export_size); + host_key_pem.data = gnutls_malloc(host_key_export_size); + + gnutls_x509_privkey_export(root_privkey, GNUTLS_X509_FMT_PEM, root_key_pem.data, &root_key_export_size); + root_key_pem.size = root_key_export_size; + gnutls_x509_privkey_export(host_privkey, GNUTLS_X509_FMT_PEM, host_key_pem.data, &host_key_export_size); + host_key_pem.size = host_key_export_size; + + size_t root_cert_export_size = 0; + size_t host_cert_export_size = 0; + + gnutls_x509_crt_export(root_cert, GNUTLS_X509_FMT_PEM, NULL, &root_cert_export_size); + gnutls_x509_crt_export(host_cert, GNUTLS_X509_FMT_PEM, NULL, &host_cert_export_size); + + root_cert_pem.data = gnutls_malloc(root_cert_export_size); + host_cert_pem.data = gnutls_malloc(host_cert_export_size); + + gnutls_x509_crt_export(root_cert, GNUTLS_X509_FMT_PEM, root_cert_pem.data, &root_cert_export_size); + root_cert_pem.size = root_cert_export_size; + gnutls_x509_crt_export(host_cert, GNUTLS_X509_FMT_PEM, host_cert_pem.data, &host_cert_export_size); + host_cert_pem.size = host_cert_export_size; + + gnutls_datum_t modulus = { NULL, 0 }; + gnutls_datum_t exponent = { NULL, 0 }; + + /* now decode the PEM encoded key */ + gnutls_datum_t der_pub_key = { NULL, 0 }; + int gnutls_error = gnutls_pem_base64_decode_alloc("RSA PUBLIC KEY", &public_key, &der_pub_key); + if (GNUTLS_E_SUCCESS == gnutls_error) { + /* initalize asn.1 parser */ + ASN1_TYPE pkcs1 = ASN1_TYPE_EMPTY; + if (ASN1_SUCCESS == asn1_array2tree(pkcs1_asn1_tab, &pkcs1, NULL)) { + + ASN1_TYPE asn1_pub_key = ASN1_TYPE_EMPTY; + asn1_create_element(pkcs1, "PKCS1.RSAPublicKey", &asn1_pub_key); + + if (ASN1_SUCCESS == asn1_der_decoding(&asn1_pub_key, der_pub_key.data, der_pub_key.size, NULL)) { + + /* get size to read */ + int ret1 = asn1_read_value(asn1_pub_key, "modulus", NULL, (int*)&modulus.size); + int ret2 = asn1_read_value(asn1_pub_key, "publicExponent", NULL, (int*)&exponent.size); + + modulus.data = gnutls_malloc(modulus.size); + exponent.data = gnutls_malloc(exponent.size); + + ret1 = asn1_read_value(asn1_pub_key, "modulus", modulus.data, (int*)&modulus.size); + ret2 = asn1_read_value(asn1_pub_key, "publicExponent", exponent.data, (int*)&exponent.size); + if (ret1 != ASN1_SUCCESS || ret2 != ASN1_SUCCESS) { + gnutls_free(modulus.data); + modulus.data = NULL; + modulus.size = 0; + gnutls_free(exponent.data); + exponent.data = NULL; + exponent.size = 0; + } + } + if (asn1_pub_key) + asn1_delete_structure(&asn1_pub_key); + } + if (pkcs1) + asn1_delete_structure(&pkcs1); + } else { + debug_info("ERROR: Could not parse public key: %s", gnutls_strerror(gnutls_error)); + } + + /* generate device certificate */ + if (modulus.data && 0 != modulus.size && exponent.data && 0 != exponent.size) { + + gnutls_datum_t prime_p = { (unsigned char*)"\x00\xca\x4a\x03\x13\xdf\x9d\x7a\xfd", 9 }; + gnutls_datum_t prime_q = { (unsigned char*)"\x00\xf2\xff\xe0\x15\xd1\x60\x37\x63", 9 }; + gnutls_datum_t coeff = { (unsigned char*)"\x32\x07\xf1\x68\x57\xdf\x9a\xf4", 8 }; + + gnutls_x509_privkey_t fake_privkey; + gnutls_x509_crt_t dev_cert; + + gnutls_x509_privkey_init(&fake_privkey); + gnutls_x509_crt_init(&dev_cert); + + gnutls_error = gnutls_x509_privkey_import_rsa_raw(fake_privkey, &modulus, &exponent, &exponent, &prime_p, &prime_q, &coeff); + if (GNUTLS_E_SUCCESS == gnutls_error) { + /* now generate device certificate */ + gnutls_x509_crt_set_key(dev_cert, fake_privkey); + gnutls_x509_crt_set_serial(dev_cert, "\x01", 1); + gnutls_x509_crt_set_version(dev_cert, 3); + gnutls_x509_crt_set_ca_status(dev_cert, 0); + gnutls_x509_crt_set_activation_time(dev_cert, time(NULL)); + gnutls_x509_crt_set_expiration_time(dev_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); + + /* use custom hash generation for compatibility with the "Apple ecosystem" */ + const gnutls_digest_algorithm_t dig_sha1 = GNUTLS_DIG_SHA1; + size_t hash_size = gnutls_hash_get_len(dig_sha1); + unsigned char hash[hash_size]; + if (gnutls_hash_fast(dig_sha1, der_pub_key.data, der_pub_key.size, (unsigned char*)&hash) < 0) { + debug_info("ERROR: Failed to generate SHA1 for public key"); + } else { + gnutls_x509_crt_set_subject_key_id(dev_cert, hash, hash_size); + } + + gnutls_x509_crt_set_key_usage(dev_cert, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT); + gnutls_error = gnutls_x509_crt_sign2(dev_cert, root_cert, root_privkey, GNUTLS_DIG_SHA1, 0); + if (GNUTLS_E_SUCCESS == gnutls_error) { + /* if everything went well, export in PEM format */ + size_t export_size = 0; + gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, NULL, &export_size); + dev_cert_pem.data = gnutls_malloc(export_size); + gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, dev_cert_pem.data, &export_size); + dev_cert_pem.size = export_size; + } else { + debug_info("ERROR: Signing device certificate with root private key failed: %s", gnutls_strerror(gnutls_error)); + } + } else { + debug_info("ERROR: Failed to import RSA key data: %s", gnutls_strerror(gnutls_error)); + } + gnutls_x509_crt_deinit(dev_cert); + gnutls_x509_privkey_deinit(fake_privkey); + } + + gnutls_x509_crt_deinit(root_cert); + gnutls_x509_crt_deinit(host_cert); + gnutls_x509_privkey_deinit(root_privkey); + gnutls_x509_privkey_deinit(host_privkey); + + gnutls_free(modulus.data); + gnutls_free(exponent.data); + + gnutls_free(der_pub_key.data); +#elif defined(HAVE_MBEDTLS) + time_t now = time(NULL); + struct tm* timestamp = gmtime(&now); + char notbefore[16]; + strftime(notbefore, sizeof(notbefore), "%Y%m%d%H%M%S", timestamp); + time_t then = now + 60 * 60 * 24 * 365 * 10; + char notafter[16]; + timestamp = gmtime(&then); + strftime(notafter, sizeof(notafter), "%Y%m%d%H%M%S", timestamp); + + mbedtls_mpi sn; + mbedtls_mpi_init(&sn); + mbedtls_mpi_lset(&sn, 1); /* 0 doesn't work, so we have to use 1 (like GnuTLS) */ + + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + + mbedtls_pk_context root_pkey; + mbedtls_pk_init(&root_pkey); + + mbedtls_pk_context host_pkey; + mbedtls_pk_init(&host_pkey); + + mbedtls_pk_context dev_public_key; + mbedtls_pk_init(&dev_public_key); + + mbedtls_entropy_context entropy; + mbedtls_entropy_init(&entropy); + + mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)"limd", 4); + + /* ----- root key & cert ----- */ + ret = mbedtls_pk_setup(&root_pkey, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)); + if (ret != 0) { + debug_info("mbedtls_pk_setup returned -0x%04x", -ret); + goto cleanup; + } + + ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(root_pkey), mbedtls_ctr_drbg_random, &ctr_drbg, 2048, 65537); + if (ret != 0) { + debug_info("mbedtls_rsa_gen_key returned -0x%04x", -ret); + goto cleanup; + } + + mbedtls_x509write_cert cert; + mbedtls_x509write_crt_init(&cert); + + /* set serial number */ + mbedtls_x509write_crt_set_serial(&cert, &sn); + + /* set version */ + mbedtls_x509write_crt_set_version(&cert, 2); + + /* set x509v3 basic constraints */ + _mbedtls_x509write_crt_set_basic_constraints_critical(&cert, 1, -1); + + /* use root public key for root cert */ + mbedtls_x509write_crt_set_subject_key(&cert, &root_pkey); + + /* set x509v3 subject key identifier */ + mbedtls_x509write_crt_set_subject_key_identifier(&cert); + + /* set key validity */ + mbedtls_x509write_crt_set_validity(&cert, notbefore, notafter); + + /* sign root cert with root private key */ + mbedtls_x509write_crt_set_issuer_key(&cert, &root_pkey); + mbedtls_x509write_crt_set_md_alg(&cert, MBEDTLS_MD_SHA1); + + unsigned char outbuf[16384]; + + /* write root private key */ + mbedtls_pk_write_key_pem(&root_pkey, outbuf, sizeof(outbuf)); + root_key_pem.size = strlen((const char*)outbuf); + root_key_pem.data = malloc(root_key_pem.size+1); + memcpy(root_key_pem.data, outbuf, root_key_pem.size); + root_key_pem.data[root_key_pem.size] = '\0'; + + /* write root certificate */ + mbedtls_x509write_crt_pem(&cert, outbuf, sizeof(outbuf), mbedtls_ctr_drbg_random, &ctr_drbg); + root_cert_pem.size = strlen((const char*)outbuf); + root_cert_pem.data = malloc(root_cert_pem.size+1); + memcpy(root_cert_pem.data, outbuf, root_cert_pem.size); + root_cert_pem.data[root_cert_pem.size] = '\0'; + + mbedtls_x509write_crt_free(&cert); + + + /* ----- host key & cert ----- */ + ret = mbedtls_pk_setup(&host_pkey, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)); + if (ret != 0) { + debug_info("mbedtls_pk_setup returned -0x%04x", -ret); + goto cleanup; + } + + ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(host_pkey), mbedtls_ctr_drbg_random, &ctr_drbg, 2048, 65537); + if (ret != 0) { + debug_info("mbedtls_rsa_gen_key returned -0x%04x", -ret); + goto cleanup; + } + + mbedtls_x509write_crt_init(&cert); + + /* set serial number */ + mbedtls_x509write_crt_set_serial(&cert, &sn); + + /* set version */ + mbedtls_x509write_crt_set_version(&cert, 2); + + /* set x509v3 basic constraints */ + _mbedtls_x509write_crt_set_basic_constraints_critical(&cert, 0, -1); + + /* use host public key for host cert */ + mbedtls_x509write_crt_set_subject_key(&cert, &host_pkey); + + /* set x509v3 subject key identifier */ + mbedtls_x509write_crt_set_subject_key_identifier(&cert); + + /* set x509v3 key usage */ + mbedtls_x509write_crt_set_key_usage(&cert, MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_KEY_ENCIPHERMENT); + + /* set key validity */ + mbedtls_x509write_crt_set_validity(&cert, notbefore, notafter); + + /* sign host cert with root private key */ + mbedtls_x509write_crt_set_issuer_key(&cert, &root_pkey); + mbedtls_x509write_crt_set_md_alg(&cert, MBEDTLS_MD_SHA1); + + /* write host private key */ + mbedtls_pk_write_key_pem(&host_pkey, outbuf, sizeof(outbuf)); + host_key_pem.size = strlen((const char*)outbuf); + host_key_pem.data = malloc(host_key_pem.size+1); + memcpy(host_key_pem.data, outbuf, host_key_pem.size); + host_key_pem.data[host_key_pem.size] = '\0'; + + /* write host certificate */ + mbedtls_x509write_crt_pem(&cert, outbuf, sizeof(outbuf), mbedtls_ctr_drbg_random, &ctr_drbg); + host_cert_pem.size = strlen((const char*)outbuf); + host_cert_pem.data = malloc(host_cert_pem.size+1); + memcpy(host_cert_pem.data, outbuf, host_cert_pem.size); + host_cert_pem.data[host_cert_pem.size] = '\0'; + + mbedtls_x509write_crt_free(&cert); + + + /* ----- device certificate ----- */ + unsigned char* pubkey_data = malloc(public_key.size+1); + if (!pubkey_data) { + debug_info("malloc() failed\n"); + goto cleanup; + } + memcpy(pubkey_data, public_key.data, public_key.size); + pubkey_data[public_key.size] = '\0'; + + int pr = mbedtls_pk_parse_public_key(&dev_public_key, pubkey_data, public_key.size+1); + free(pubkey_data); + if (pr != 0) { + debug_info("Failed to read device public key: -0x%x\n", -pr); + goto cleanup; + } + + mbedtls_x509write_crt_init(&cert); + + /* set serial number */ + mbedtls_x509write_crt_set_serial(&cert, &sn); + + /* set version */ + mbedtls_x509write_crt_set_version(&cert, 2); + + /* set x509v3 basic constraints */ + _mbedtls_x509write_crt_set_basic_constraints_critical(&cert, 0, -1); + + /* use root public key for dev cert subject key */ + mbedtls_x509write_crt_set_subject_key(&cert, &dev_public_key); + + /* set x509v3 subject key identifier */ + mbedtls_x509write_crt_set_subject_key_identifier(&cert); + + /* set x509v3 key usage */ + mbedtls_x509write_crt_set_key_usage(&cert, MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_KEY_ENCIPHERMENT); + + /* set key validity */ + mbedtls_x509write_crt_set_validity(&cert, notbefore, notafter); + + /* sign device certificate with root private key */ + mbedtls_x509write_crt_set_issuer_key(&cert, &root_pkey); + mbedtls_x509write_crt_set_md_alg(&cert, MBEDTLS_MD_SHA1); + + /* write device certificate */ + mbedtls_x509write_crt_pem(&cert, outbuf, sizeof(outbuf), mbedtls_ctr_drbg_random, &ctr_drbg); + dev_cert_pem.size = strlen((const char*)outbuf); + dev_cert_pem.data = malloc(dev_cert_pem.size+1); + memcpy(dev_cert_pem.data, outbuf, dev_cert_pem.size); + dev_cert_pem.data[dev_cert_pem.size] = '\0'; + + mbedtls_x509write_crt_free(&cert); + + /* cleanup */ +cleanup: + mbedtls_mpi_free(&sn); + mbedtls_pk_free(&dev_public_key); + mbedtls_entropy_free(&entropy); + mbedtls_pk_free(&host_pkey); + mbedtls_pk_free(&root_pkey); + mbedtls_ctr_drbg_free(&ctr_drbg); +#endif + + /* make sure that we have all we need */ + if (root_cert_pem.data && 0 != root_cert_pem.size + && root_key_pem.data && 0 != root_key_pem.size + && host_cert_pem.data && 0 != host_cert_pem.size + && host_key_pem.data && 0 != host_key_pem.size + && dev_cert_pem.data && 0 != dev_cert_pem.size) { + /* now set keys and certificates */ + pair_record_set_item_from_key_data(pair_record, USERPREF_DEVICE_CERTIFICATE_KEY, &dev_cert_pem); + pair_record_set_item_from_key_data(pair_record, USERPREF_HOST_PRIVATE_KEY_KEY, &host_key_pem); + pair_record_set_item_from_key_data(pair_record, USERPREF_HOST_CERTIFICATE_KEY, &host_cert_pem); + pair_record_set_item_from_key_data(pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY, &root_key_pem); + pair_record_set_item_from_key_data(pair_record, USERPREF_ROOT_CERTIFICATE_KEY, &root_cert_pem); + ret = USERPREF_E_SUCCESS; + } + + free(dev_cert_pem.data); + free(root_key_pem.data); + free(root_cert_pem.data); + free(host_key_pem.data); + free(host_cert_pem.data); + + return ret; +} + +/** + * Private function which import the given key into a gnutls structure. + * + * @param name The name of the private key to import. + * @param key the gnutls key structure. + * + * @return 1 if the key was successfully imported. + */ +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) +userpref_error_t pair_record_import_key_with_name(plist_t pair_record, const char* name, key_data_t* key) +#elif defined(HAVE_GNUTLS) +userpref_error_t pair_record_import_key_with_name(plist_t pair_record, const char* name, gnutls_x509_privkey_t key) +#endif +{ +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) + if (!key) + return USERPREF_E_SUCCESS; +#endif + userpref_error_t ret = USERPREF_E_INVALID_CONF; + +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) + ret = pair_record_get_item_as_key_data(pair_record, name, key); +#elif defined(HAVE_GNUTLS) + key_data_t pem = { NULL, 0 }; + ret = pair_record_get_item_as_key_data(pair_record, name, &pem); + if (ret == USERPREF_E_SUCCESS && GNUTLS_E_SUCCESS == gnutls_x509_privkey_import(key, &pem, GNUTLS_X509_FMT_PEM)) + ret = USERPREF_E_SUCCESS; + else + ret = USERPREF_E_SSL_ERROR; + + if (pem.data) + free(pem.data); +#endif + return ret; +} + +/** + * Private function which import the given certificate into a gnutls structure. + * + * @param name The name of the certificate to import. + * @param cert the gnutls certificate structure. + * + * @return IDEVICE_E_SUCCESS if the certificate was successfully imported. + */ +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) +userpref_error_t pair_record_import_crt_with_name(plist_t pair_record, const char* name, key_data_t* cert) +#else +userpref_error_t pair_record_import_crt_with_name(plist_t pair_record, const char* name, gnutls_x509_crt_t cert) +#endif +{ +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) + if (!cert) + return USERPREF_E_SUCCESS; +#endif + userpref_error_t ret = USERPREF_E_INVALID_CONF; + +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) + ret = pair_record_get_item_as_key_data(pair_record, name, cert); +#elif defined(HAVE_GNUTLS) + key_data_t pem = { NULL, 0 }; + ret = pair_record_get_item_as_key_data(pair_record, name, &pem); + if (ret == USERPREF_E_SUCCESS && GNUTLS_E_SUCCESS == gnutls_x509_crt_import(cert, &pem, GNUTLS_X509_FMT_PEM)) + ret = USERPREF_E_SUCCESS; + else + ret = USERPREF_E_SSL_ERROR; + + if (pem.data) + free(pem.data); +#endif + return ret; +} + +userpref_error_t pair_record_get_host_id(plist_t pair_record, char** host_id) +{ + plist_t node = plist_dict_get_item(pair_record, USERPREF_HOST_ID_KEY); + + if (node && plist_get_node_type(node) == PLIST_STRING) { + plist_get_string_val(node, host_id); + } + + return USERPREF_E_SUCCESS; +} + +userpref_error_t pair_record_set_host_id(plist_t pair_record, const char* host_id) +{ + plist_dict_set_item(pair_record, USERPREF_HOST_ID_KEY, plist_new_string(host_id)); + + return USERPREF_E_SUCCESS; +} + +userpref_error_t pair_record_get_item_as_key_data(plist_t pair_record, const char* name, key_data_t *value) +{ + if (!pair_record || !value) + return USERPREF_E_INVALID_ARG; + + userpref_error_t ret = USERPREF_E_SUCCESS; + char* buffer = NULL; + uint64_t length = 0; + + plist_t node = plist_dict_get_item(pair_record, name); + + if (node && plist_get_node_type(node) == PLIST_DATA) { + plist_get_data_val(node, &buffer, &length); + value->data = (unsigned char*)malloc(length+1); + memcpy(value->data, buffer, length); + value->data[length] = '\0'; + value->size = length+1; + free(buffer); + buffer = NULL; + } else { + ret = USERPREF_E_INVALID_CONF; + } + + if (buffer) + free(buffer); + + return ret; +} + +userpref_error_t pair_record_set_item_from_key_data(plist_t pair_record, const char* name, key_data_t *value) +{ + userpref_error_t ret = USERPREF_E_SUCCESS; + + if (!pair_record || !value) { + return USERPREF_E_INVALID_ARG; + } + + /* set new item */ + plist_dict_set_item(pair_record, name, plist_new_data((char*)value->data, value->size)); + + return ret; +} + diff --git a/common/userpref.h b/common/userpref.h new file mode 100644 index 0000000..75bb8b7 --- /dev/null +++ b/common/userpref.h @@ -0,0 +1,89 @@ +/* + * userpref.h + * contains methods to access user specific certificates IDs and more. + * + * Copyright (c) 2013-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2008 Jonathan Beck All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __USERPREF_H +#define __USERPREF_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) +typedef struct { + unsigned char *data; + unsigned int size; +} key_data_t; +#else +#include <gnutls/gnutls.h> +typedef gnutls_datum_t key_data_t; +#endif + +#include <stdint.h> +#include <plist/plist.h> + +#define USERPREF_DEVICE_CERTIFICATE_KEY "DeviceCertificate" +#define USERPREF_ESCROW_BAG_KEY "EscrowBag" +#define USERPREF_HOST_CERTIFICATE_KEY "HostCertificate" +#define USERPREF_ROOT_CERTIFICATE_KEY "RootCertificate" +#define USERPREF_HOST_PRIVATE_KEY_KEY "HostPrivateKey" +#define USERPREF_ROOT_PRIVATE_KEY_KEY "RootPrivateKey" +#define USERPREF_HOST_ID_KEY "HostID" +#define USERPREF_SYSTEM_BUID_KEY "SystemBUID" +#define USERPREF_WIFI_MAC_ADDRESS_KEY "WiFiMACAddress" + +/** Error Codes */ +typedef enum { + USERPREF_E_SUCCESS = 0, + USERPREF_E_INVALID_ARG = -1, + USERPREF_E_NOENT = -2, + USERPREF_E_INVALID_CONF = -3, + USERPREF_E_SSL_ERROR = -4, + USERPREF_E_READ_ERROR = -5, + USERPREF_E_WRITE_ERROR = -6, + USERPREF_E_UNKNOWN_ERROR = -256 +} userpref_error_t; + +const char *userpref_get_config_dir(void); +int userpref_read_system_buid(char **system_buid); +userpref_error_t userpref_read_pair_record(const char *udid, plist_t *pair_record); +userpref_error_t userpref_save_pair_record(const char *udid, uint32_t device_id, plist_t pair_record); +userpref_error_t userpref_delete_pair_record(const char *udid); + +userpref_error_t pair_record_generate_keys_and_certs(plist_t pair_record, key_data_t public_key); +#if defined(HAVE_OPENSSL) || defined(HAVE_MBEDTLS) +userpref_error_t pair_record_import_key_with_name(plist_t pair_record, const char* name, key_data_t* key); +userpref_error_t pair_record_import_crt_with_name(plist_t pair_record, const char* name, key_data_t* cert); +#else +userpref_error_t pair_record_import_key_with_name(plist_t pair_record, const char* name, gnutls_x509_privkey_t key); +userpref_error_t pair_record_import_crt_with_name(plist_t pair_record, const char* name, gnutls_x509_crt_t cert); +#endif + +userpref_error_t pair_record_get_host_id(plist_t pair_record, char** host_id); +userpref_error_t pair_record_set_host_id(plist_t pair_record, const char* host_id); +userpref_error_t pair_record_get_item_as_key_data(plist_t pair_record, const char* name, key_data_t *value); +userpref_error_t pair_record_set_item_from_key_data(plist_t pair_record, const char* name, key_data_t *value); + +/* deprecated */ +userpref_error_t userpref_get_paired_udids(char ***list, unsigned int *count); +int userpref_has_pair_record(const char *udid); + +#endif diff --git a/configure.ac b/configure.ac index a982b7b..c67e896 100644 --- a/configure.ac +++ b/configure.ac @@ -1,9 +1,9 @@ # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. -AC_PREREQ(2.61) -AC_INIT(libimobiledevice, 1.1.0, nospam@nowhere.com) -AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip]) +AC_PREREQ([2.68]) +AC_INIT([libimobiledevice], [m4_esyscmd(./git-version-gen $RELEASE_VERSION)], [https://github.com/libimobiledevice/libimobiledevice/issues], [], [https://libimobiledevice.org]) +AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip check-news]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES]) AC_CONFIG_SRCDIR([src/]) AC_CONFIG_HEADERS([config.h]) @@ -15,29 +15,51 @@ dnl libtool versioning # changes to the signature and the semantic) # ? :+1 : ? == just internal changes # CURRENT : REVISION : AGE -LIBIMOBILEDEVICE_SO_VERSION=2:0:0 +LIBIMOBILEDEVICE_SO_VERSION=6:0:0 AC_SUBST(LIBIMOBILEDEVICE_SO_VERSION) +# Check if we have a version defined +if test -z $PACKAGE_VERSION; then + AC_MSG_ERROR([PACKAGE_VERSION is not defined. Make sure to configure a source tree checked out from git or that .tarball-version is present.]) +fi + +dnl Minimum package versions +LIBUSBMUXD_VERSION=2.0.2 +LIBPLIST_VERSION=2.3.0 +LIMD_GLUE_VERSION=1.3.0 +LIBTATSU_VERSION=1.0.3 + +AC_SUBST(LIBUSBMUXD_VERSION) +AC_SUBST(LIBPLIST_VERSION) +AC_SUBST(LIMD_GLUE_VERSION) + # Checks for programs. AC_PROG_CC AC_PROG_CXX AM_PROG_CC_C_O -AC_PROG_LIBTOOL +LT_INIT # Checks for libraries. -PKG_CHECK_MODULES(libusbmuxd, libusbmuxd >= 0.1.4) -PKG_CHECK_MODULES(libglib2, glib-2.0 >= 2.14.1) -PKG_CHECK_MODULES(libgthread2, gthread-2.0 >= 2.14.1) -PKG_CHECK_MODULES(libgnutls, gnutls >= 1.6.3 ) -PKG_CHECK_MODULES(libtasn1, libtasn1 >= 1.1) -PKG_CHECK_MODULES(libplist, libplist >= 0.15) -PKG_CHECK_MODULES(libplistmm, libplist++ >= 0.15) -AC_CHECK_LIB(gcrypt, gcry_control, [AC_SUBST(libgcrypt_LIBS,[-lgcrypt])], [AC_MSG_ERROR([libgcrypt is required to build libimobiledevice])]) +PKG_CHECK_MODULES(libusbmuxd, libusbmuxd-2.0 >= $LIBUSBMUXD_VERSION) +PKG_CHECK_MODULES(libplist, libplist-2.0 >= $LIBPLIST_VERSION) +PKG_CHECK_MODULES(limd_glue, libimobiledevice-glue-1.0 >= $LIMD_GLUE_VERSION) +PKG_CHECK_MODULES(libtatsu, libtatsu-1.0 >= $LIBTATSU_VERSION) +AC_ARG_WITH([readline], + [AS_HELP_STRING([--without-readline], + [build without support for libreadline (default is yes)])], + [check_libreadline=false], + [check_libreadline=true]) +if test "$check_libreadline" = "true"; then + PKG_CHECK_MODULES(readline, readline >= 1.0, have_readline=yes, have_readline=no) + if test "x$have_readline" = "xyes"; then + AC_DEFINE(HAVE_READLINE, 1, [Define if readline library is available]) + fi +fi +AM_CONDITIONAL([HAVE_READLINE],[test "x$have_readline" = "xyes"]) # Checks for header files. -AC_HEADER_STDC -AC_CHECK_HEADERS([stdint.h stdlib.h string.h gcrypt.h]) +AC_CHECK_HEADERS([stdint.h stdlib.h string.h sys/time.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST @@ -48,111 +70,278 @@ AC_TYPE_UINT32_T AC_TYPE_UINT8_T # Checks for library functions. -AC_FUNC_MALLOC -AC_FUNC_REALLOC -AC_CHECK_FUNCS([strcasecmp strdup strerror strndup]) - -AC_ARG_WITH([swig], - [AS_HELP_STRING([--without-swig], - [build Python bindings using swig (default is yes)])], - [build_swig=false], - [build_swig=true]) -if test "$build_swig" = "true"; then - AM_PATH_PYTHON(2.3) - AC_PROG_SWIG(1.3.21) - AX_SWIG_ENABLE_CXX - SWIG_PYTHON -else - SWIG=false -fi +AC_CHECK_FUNCS([asprintf strcasecmp strdup strerror strndup stpcpy vasprintf getifaddrs]) -if [test "x$SWIG" != "xfalse"]; then - python_bindings=yes -else - python_bindings=no +AC_CHECK_HEADER(endian.h, [ac_cv_have_endian_h="yes"], [ac_cv_have_endian_h="no"]) +if test "x$ac_cv_have_endian_h" = "xno"; then + AC_DEFINE(__LITTLE_ENDIAN,1234,[little endian]) + AC_DEFINE(__BIG_ENDIAN,4321,[big endian]) + AC_C_BIGENDIAN([ac_cv_c_bigendian="yes"], [ac_cv_c_bigendian="no"], [], []) + if test "x$ac_cv_c_bigendian" = "xyes"; then + AC_DEFINE(__BYTE_ORDER,4321,[big endian byte order]) + else + AC_DEFINE(__BYTE_ORDER,1234,[little endian byte order]) + fi fi -AM_CONDITIONAL([HAVE_SWIG],[test "x$SWIG" != "xfalse"]) +AC_CHECK_DECL([plist_from_json], [AC_DEFINE([HAVE_PLIST_JSON], [1], [Define if libplist has JSON support])], [], [[#include <plist/plist.h>]]) -AC_SUBST([DEV_SUB]) +# Check for operating system +AC_MSG_CHECKING([for platform-specific build settings]) +case ${host_os} in + *mingw32*|*cygwin*) + AC_MSG_RESULT([${host_os}]) + win32=true + AC_DEFINE(WINVER, 0x0501, [minimum Windows version]) + ;; + darwin*) + AC_MSG_RESULT([${host_os}]) + darwin=true + ;; + *) + AC_MSG_RESULT([${host_os}]) + AX_PTHREAD([], [AC_MSG_ERROR([pthread is required to build $PACKAGE_NAME])]) + AC_CHECK_LIB(pthread, [pthread_once], [], [AC_MSG_ERROR([pthread with pthread_once required to build $PACKAGE_NAME])]) + ;; +esac +AM_CONDITIONAL(WIN32, test x$win32 = xtrue) +AM_CONDITIONAL(DARWIN, test x$darwin = xtrue) +# Check if the C compiler supports __attribute__((constructor)) +AC_CACHE_CHECK([wether the C compiler supports constructor/destructor attributes], + ac_cv_attribute_constructor, [ + ac_cv_attribute_constructor=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[ + static void __attribute__((constructor)) test_constructor(void) { + } + static void __attribute__((destructor)) test_destructor(void) { + } + ]], [])], + [ac_cv_attribute_constructor=yes] + )] +) +if test "$ac_cv_attribute_constructor" = "yes"; then + AC_DEFINE(HAVE_ATTRIBUTE_CONSTRUCTOR, 1, [Define if the C compiler supports constructor/destructor attributes]) +fi + +AC_CHECK_MEMBER(struct dirent.d_type, AC_DEFINE(HAVE_DIRENT_D_TYPE, 1, [define if struct dirent has member d_type]),, [#include <dirent.h>]) -AC_ARG_ENABLE([dev-tools], - [AS_HELP_STRING([--enable-dev-tools], - [build development helper tools (default is no)])], - [build_dev_tools=true], - [build_dev_tools=false]) -if test "$build_dev_tools" = true; then - DEV_SUB=dev - AC_CHECK_HEADERS([readline/readline.h], - [], - [AC_MSG_ERROR([Please install readline development headers])] - ) - building_dev_tools=yes +# Cython Python Bindings +AC_ARG_WITH([cython], + [AS_HELP_STRING([--without-cython], + [build Python bindings using Cython (default is yes)])], + [build_cython=false], + [build_cython=true]) +if test "$build_cython" = "true"; then + AC_PROG_CYTHON([3.0.0]) + if [test "x$CYTHON" != "xfalse"]; then + AM_PATH_PYTHON([3.0], [ + CYTHON_PYTHON + AS_COMPILER_FLAG([-Wno-cast-function-type -Werror], [ + CYTHON_CFLAGS+=" -Wno-cast-function-type" + AC_SUBST([CYTHON_CFLAGS]) + ], []) + ]) + else + AC_MSG_WARN([Use the "--without-cython" option to avoid this warning.]) + fi +else + CYTHON=false +fi +if [test "x$CYTHON" != "xfalse"]; then + PKG_PROG_PKG_CONFIG + AC_MSG_CHECKING([for libplist Cython bindings]) + CYTHON_PLIST_INCLUDE_DIR=$($PKG_CONFIG --variable=includedir libplist-2.0)/plist/cython + if [test ! -d "$CYTHON_PLIST_INCLUDE_DIR"]; then + CYTHON=false + CYTHON_SUB= + cython_python_bindings=no + AC_MSG_RESULT([no]) + AC_MSG_WARN([Unable to find libplist Cython bindings. You should install your distribution specific libplist Cython bindings package.]) + else + AC_SUBST([CYTHON_PLIST_INCLUDE_DIR]) + AC_MSG_RESULT([$CYTHON_PLIST_INCLUDE_DIR]) + CYTHON_SUB=cython + cython_python_bindings=yes + fi else - DEV_SUB= - building_dev_tools=no + CYTHON_SUB= + cython_python_bindings=no fi +AM_CONDITIONAL([HAVE_CYTHON],[test "x$CYTHON_SUB" = "xcython"]) +AC_SUBST([CYTHON_SUB]) -AM_CONDITIONAL([ENABLE_DEVTOOLS],[test "x$DEV_SUB" = "xdev"]) +default_openssl=yes -AC_SUBST([DEV_SUB]) +AC_ARG_WITH([mbedtls], + [AS_HELP_STRING([--without-mbedtls], + [Do not look for mbedtls])], + [use_mbedtls=$withval], + [use_mbedtls=no]) +if test "x$use_mbedtls" = "xyes"; then + default_openssl=no +fi +AC_ARG_WITH([gnutls], + [AS_HELP_STRING([--without-gnutls], + [Do not look for GnuTLS])], + [use_gnutls=$withval], + [use_gnutls=no]) +if test "x$use_gnutls" = "xyes"; then + default_openssl=no +fi +AC_ARG_WITH([openssl], + [AS_HELP_STRING([--without-openssl], + [Do not look for OpenSSL])], + [use_openssl=$withval], + [use_openssl=$default_openssl]) -AC_ARG_ENABLE([debug-code], - [AS_HELP_STRING([--disable-debug-code], - [disable debug message reporting in library (default is yes)])], +if test "x$use_mbedtls" = "xyes"; then + CACHED_CFLAGS="$CFLAGS" + conf_mbedtls_CFLAGS="" + if test -n "$mbedtls_INCLUDES"; then + CFLAGS=" -I$mbedtls_INCLUDES" + conf_mbedtls_CFLAGS="-I$mbedtls_INCLUDES" + fi + conf_mbedtls_LIBS="" + if test -n "$mbedtls_LIBDIR"; then + conf_mbedtls_LIBS+=" -L$mbedtls_LIBDIR" + fi + if test -n "$mbedtls_LIBS"; then + conf_mbedtls_LIBS+=" $mbedtls_LIBS" + else + conf_mbedtls_LIBS+=" -lmbedtls -lmbedx509 -lmbedcrypto" + fi + AC_CHECK_HEADER(mbedtls/ssl.h, [break], [AC_MSG_ERROR([MbedTLS support explicitly requested, but includes could not be found. Try setting mbedtls_INCLUDES=/path/to/mbedtls/include])]) + CFLAGS="$CACHED_CFLAGS" + AC_DEFINE(HAVE_MBEDTLS, 1, [Define if you have MbedTLS support]) + ssl_lib_CFLAGS="$conf_mbedtls_CFLAGS" + ssl_lib_LIBS="$conf_mbedtls_LIBS" + AC_SUBST(ssl_lib_CFLAGS) + AC_SUBST(ssl_lib_LIBS) + ssl_provider="MbedTLS"; + ssl_requires="" + AC_SUBST(ssl_requires) +else + if test "x$use_openssl" = "xyes"; then + pkg_req_openssl="openssl >= 0.9.8" + PKG_CHECK_MODULES(openssl, $pkg_req_openssl, have_openssl=yes, have_openssl=no) + if test "x$have_openssl" != "xyes"; then + AC_MSG_ERROR([OpenSSL support explicitly requested but OpenSSL could not be found]) + else + AC_DEFINE(HAVE_OPENSSL, 1, [Define if you have OpenSSL support]) + ssl_lib_CFLAGS="$openssl_CFLAGS" + ssl_lib_LIBS="$openssl_LIBS" + AC_SUBST(ssl_lib_CFLAGS) + AC_SUBST(ssl_lib_LIBS) + ssl_provider="OpenSSL"; + ssl_requires="$pkg_req_openssl" + # test if we have LibreSSL + CACHED_CFLAGS="$CFLAGS" + CFLAGS="$openssl_CFLAGS" + ac_cv_is_libressl=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[ + #include <openssl/opensslv.h> + ]], [ + #ifndef LIBRESSL_VERSION_NUMBER + #error No LibreSSL + #endif + ])], + [ + ac_cv_is_libressl=yes + ], + ) + CFLAGS="$CACHED_CFLAGS" + if test "x$ac_cv_is_libressl" = "xyes"; then + ssl_provider="LibreSSL" + case ${host_os} in + darwin*) + case ${openssl_LIBS} in + *.tbd*) + # using system LibreSSL on Darwin + ssl_requires="" + ;; + esac + ;; + esac + fi + AC_SUBST(ssl_requires) + fi + else + if test "x$use_gnutls" = "xyes"; then + pkg_req_gnutls="gnutls >= 2.2.0" + pkg_req_libtasn1="libtasn1 >= 1.1" + PKG_CHECK_MODULES(libgnutls, $pkg_req_gnutls) + AC_CHECK_HEADERS([gcrypt.h]) + AC_CHECK_LIB(gcrypt, gcry_control, [AC_SUBST(libgcrypt_LIBS,[-lgcrypt])], [AC_MSG_ERROR([libgcrypt is required to build libimobiledevice with GnuTLS])]) + PKG_CHECK_MODULES(libtasn1, $pkg_req_libtasn1) + AC_DEFINE(HAVE_GCRYPT, 1, [Define if you have libgcrypt support]) + AC_DEFINE(HAVE_GNUTLS, 1, [Define if you have GnuTLS support]) + ssl_lib_CFLAGS="$libgnutls_CFLAGS $libtasn1_CFLAGS $libgcrypt_CFLAGS" + ssl_lib_LIBS="$libgnutls_LIBS $libtasn1_LIBS $libgcrypt_LIBS" + AC_SUBST(ssl_lib_CFLAGS) + AC_SUBST(ssl_lib_LIBS) + ssl_provider="GnuTLS" + ssl_requires="$pkg_req_gnutls $pkg_req_libtasn1" + AC_SUBST(ssl_requires) + else + AC_MSG_ERROR([No SSL library configured. $PACKAGE cannot be built without a supported SSL library.]) + fi + fi +fi +AM_CONDITIONAL(HAVE_MBEDTLS, test "x$use_mbedtls" = "xyes") +AM_CONDITIONAL(HAVE_OPENSSL, test "x$use_openssl" = "xyes") +AM_CONDITIONAL(HAVE_GCRYPT, test "x$use_gnutls" = "xyes") + +AC_ARG_ENABLE([wireless-pairing], + [AS_HELP_STRING([--disable-wireless-pairing], + [Do not build with wirless pairing support (default is yes)])]) +if test "$enable_wireless_pairing" != "no"; then + AC_DEFINE(HAVE_WIRELESS_PAIRING,1,[Define if building with wireless pairing support]) +fi +AM_CONDITIONAL(HAVE_WIRELESS_PAIRING, test "$enable_wireless_pairing" != "no") + +AC_ARG_ENABLE([debug], + [AS_HELP_STRING([--enable-debug], + [build debug message output code (default is no)])], [no_debug_code=false], [no_debug_code=true]) if test "$no_debug_code" = true; then building_debug_code=no - AC_DEFINE(STRIP_DEBUG_CODE,1,[Strip debug reporting code]) + AC_DEFINE(STRIP_DEBUG_CODE,1,[Define if debug message output code should not be built.]) else building_debug_code=yes fi -AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wmissing-declarations -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter") +AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wmissing-declarations -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -fsigned-char -fvisibility=hidden") + +if test "x$enable_static" = "xyes" -a "x$enable_shared" = "xno"; then + GLOBAL_CFLAGS+=" -DLIBIMOBILEDEVICE_STATIC" +fi + AC_SUBST(GLOBAL_CFLAGS) # check for large file support AC_SYS_LARGEFILE -LFS_CFLAGS='' -if test "$enable_largefile" != no; then - if test "$ac_cv_sys_file_offset_bits" != 'no'; then - LFS_CFLAGS="$LFS_CFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits" - else - AC_MSG_CHECKING(for native large file support) - AC_RUN_IFELSE([#include <unistd.h> - int main (int argc, char **argv) - { - exit(!(sizeof(off_t) == 8)); - }], - [ac_cv_sys_file_offset_bits=64; AC_DEFINE(_FILE_OFFSET_BITS,64) - AC_MSG_RESULT(yes)], - [AC_MSG_RESULT(no)]) - fi - if test "$ac_cv_sys_large_files" != 'no'; then - LFS_CFLAGS="$LFS_CFLAGS -D_LARGE_FILES=1" - fi - AC_FUNC_FSEEKO - if test "$ac_cv_sys_largefile_source" != 'no'; then - LFS_CFLAGS="$LFS_CFLAGS -D_LARGEFILE_SOURCE=1" - fi -fi -AC_SUBST(LFS_CFLAGS) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) -AC_OUTPUT([ +AC_CONFIG_FILES([ Makefile +3rd_party/Makefile +3rd_party/ed25519/Makefile +3rd_party/libsrp6a-sha512/Makefile +common/Makefile src/Makefile +src/libimobiledevice-1.0.pc include/Makefile -dev/Makefile tools/Makefile -swig/Makefile +cython/Makefile docs/Makefile -libimobiledevice-1.0.pc doxygen.cfg ]) +AC_OUTPUT echo " Configuration for $PACKAGE $VERSION: @@ -160,8 +349,8 @@ Configuration for $PACKAGE $VERSION: Install prefix: .........: $prefix Debug code ..............: $building_debug_code - Dev tools ...............: $building_dev_tools - Python bindings .........: $python_bindings + Python bindings .........: $cython_python_bindings + SSL support backend .....: $ssl_provider Now type 'make' to build $PACKAGE $VERSION, and then 'make install' for installation. diff --git a/cython/Makefile.am b/cython/Makefile.am new file mode 100644 index 0000000..93ea6ed --- /dev/null +++ b/cython/Makefile.am @@ -0,0 +1,84 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/include + +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + $(ssl_lib_CFLAGS) \ + $(LFS_CFLAGS) \ + $(PTHREAD_CFLAGS) \ + $(libplist_CFLAGS) + +AM_LIBS = \ + $(ssl_lib_LIBS) \ + $(PTHREAD_LIBS) \ + $(libplist_LIBS) + +if HAVE_CYTHON + +BUILT_SOURCES = imobiledevice.c +PXDINCLUDES = \ + imobiledevice.pxd \ + $(CYTHON_PLIST_INCLUDE_DIR)/plist.pxd + +PXIINCLUDES = \ + lockdown.pxi \ + mobilesync.pxi \ + notification_proxy.pxi \ + sbservices.pxi \ + mobilebackup.pxi \ + mobilebackup2.pxi \ + afc.pxi \ + file_relay.pxi \ + screenshotr.pxi \ + installation_proxy.pxi \ + webinspector.pxi \ + heartbeat.pxi \ + diagnostics_relay.pxi \ + misagent.pxi \ + house_arrest.pxi \ + restore.pxi \ + mobile_image_mounter.pxi \ + debugserver.pxi + +CLEANFILES = \ + *.pyc \ + *.pyo \ + imobiledevice.c + +EXTRA_DIST = \ + imobiledevice.pyx \ + imobiledevice.pxd \ + $(PXIINCLUDES) + +imobiledevicedir = $(pyexecdir) +imobiledevice_LTLIBRARIES = imobiledevice.la +imobiledevice_la_SOURCES = imobiledevice.pyx +imobiledevice_la_CFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/src \ + $(PYTHON_CPPFLAGS) \ + $(AM_CFLAGS) \ + -Wno-shadow \ + -Wno-redundant-decls \ + -Wno-switch-default \ + -Wno-strict-aliasing \ + -Wno-implicit-function-declaration \ + -fvisibility=default \ + $(CYTHON_CFLAGS) + +imobiledevice_la_LDFLAGS = \ + -module \ + -avoid-version \ + -L$(libdir) \ + $(PYTHON_LIBS) \ + $(AM_LIBS) \ + -no-undefined + +imobiledevice_la_LIBADD = $(top_builddir)/src/libimobiledevice-1.0.la + +imobiledevice.c: imobiledevice.pyx $(PXDINCLUDES) $(PXIINCLUDES) + +.pyx.c: + $(CYTHON) -I$(CYTHON_PLIST_INCLUDE_DIR) -I$(top_srcdir)/src -o $@ $< + +endif diff --git a/cython/afc.pxi b/cython/afc.pxi new file mode 100644 index 0000000..6bd8182 --- /dev/null +++ b/cython/afc.pxi @@ -0,0 +1,337 @@ +cdef extern from "libimobiledevice/afc.h": + cdef struct afc_client_private: + pass + ctypedef afc_client_private *afc_client_t + ctypedef enum afc_error_t: + AFC_E_SUCCESS = 0 + AFC_E_UNKNOWN_ERROR = 1 + AFC_E_OP_HEADER_INVALID = 2 + AFC_E_NO_RESOURCES = 3 + AFC_E_READ_ERROR = 4 + AFC_E_WRITE_ERROR = 5 + AFC_E_UNKNOWN_PACKET_TYPE = 6 + AFC_E_INVALID_ARG = 7 + AFC_E_OBJECT_NOT_FOUND = 8 + AFC_E_OBJECT_IS_DIR = 9 + AFC_E_PERM_DENIED = 10 + AFC_E_SERVICE_NOT_CONNECTED = 11 + AFC_E_OP_TIMEOUT = 12 + AFC_E_TOO_MUCH_DATA = 13 + AFC_E_END_OF_DATA = 14 + AFC_E_OP_NOT_SUPPORTED = 15 + AFC_E_OBJECT_EXISTS = 16 + AFC_E_OBJECT_BUSY = 17 + AFC_E_NO_SPACE_LEFT = 18 + AFC_E_OP_WOULD_BLOCK = 19 + AFC_E_IO_ERROR = 20 + AFC_E_OP_INTERRUPTED = 21 + AFC_E_OP_IN_PROGRESS = 22 + AFC_E_INTERNAL_ERROR = 23 + AFC_E_MUX_ERROR = 30 + AFC_E_NO_MEM = 31 + AFC_E_NOT_ENOUGH_DATA = 32 + AFC_E_DIR_NOT_EMPTY = 33 + ctypedef enum afc_file_mode_t: + AFC_FOPEN_RDONLY = 0x00000001 + AFC_FOPEN_RW = 0x00000002 + AFC_FOPEN_WRONLY = 0x00000003 + AFC_FOPEN_WR = 0x00000004 + AFC_FOPEN_APPEND = 0x00000005 + AFC_FOPEN_RDAPPEND = 0x00000006 + ctypedef enum afc_link_type_t: + AFC_HARDLINK = 1 + AFC_SYMLINK = 2 + ctypedef enum afc_lock_op_t: + AFC_LOCK_SH = 1 | 4 + AFC_LOCK_EX = 2 | 4 + AFC_LOCK_UN = 8 | 4 + + afc_error_t afc_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, afc_client_t *client) + afc_error_t afc_client_free(afc_client_t client) + afc_error_t afc_get_device_info(afc_client_t client, char ***infos) + afc_error_t afc_read_directory(afc_client_t client, char *dir, char ***list) + afc_error_t afc_get_file_info(afc_client_t client, char *filename, char ***infolist) + afc_error_t afc_remove_path(afc_client_t client, char *path) + afc_error_t afc_remove_path_and_contents(afc_client_t client, char *path) + afc_error_t afc_rename_path(afc_client_t client, char *f, char *to) + afc_error_t afc_make_directory(afc_client_t client, char *dir) + afc_error_t afc_truncate(afc_client_t client, char *path, uint64_t newsize) + afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, char *target, char *linkname) + afc_error_t afc_set_file_time(afc_client_t client, char *path, uint64_t mtime) + + afc_error_t afc_file_open(afc_client_t client, char *filename, afc_file_mode_t file_mode, uint64_t *handle) + afc_error_t afc_file_close(afc_client_t client, uint64_t handle) + afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation) + afc_error_t afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read) + afc_error_t afc_file_write(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_written) + afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence) + afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position) + afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize) + +LOCK_SH = AFC_LOCK_SH +LOCK_EX = AFC_LOCK_EX +LOCK_UN = AFC_LOCK_UN + +cdef class AfcError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + AFC_E_SUCCESS: "Success", + AFC_E_UNKNOWN_ERROR: "Unknown error", + AFC_E_OP_HEADER_INVALID: "OP header invalid", + AFC_E_NO_RESOURCES: "No resources", + AFC_E_READ_ERROR: "Read error", + AFC_E_WRITE_ERROR: "Write error", + AFC_E_UNKNOWN_PACKET_TYPE: "Unknown packet type", + AFC_E_INVALID_ARG: "Invalid argument", + AFC_E_OBJECT_NOT_FOUND: "Object not found", + AFC_E_OBJECT_IS_DIR: "Object is directory", + AFC_E_PERM_DENIED: "Permission denied", + AFC_E_SERVICE_NOT_CONNECTED: "Service not connected", + AFC_E_OP_TIMEOUT: "OP timeout", + AFC_E_TOO_MUCH_DATA: "Too much data", + AFC_E_END_OF_DATA: "End of data", + AFC_E_OP_NOT_SUPPORTED: "OP not supported", + AFC_E_OBJECT_EXISTS: "Object exists", + AFC_E_OBJECT_BUSY: "Object busy", + AFC_E_NO_SPACE_LEFT: "No space left", + AFC_E_OP_WOULD_BLOCK: "OP would block", + AFC_E_IO_ERROR: "IO error", + AFC_E_OP_INTERRUPTED: "OP interrupted", + AFC_E_OP_IN_PROGRESS: "OP in progress", + AFC_E_INTERNAL_ERROR: "Internal error", + AFC_E_MUX_ERROR: "MUX error", + AFC_E_NO_MEM: "No memory", + AFC_E_NOT_ENOUGH_DATA: "Not enough data", + AFC_E_DIR_NOT_EMPTY: "Directory not empty" + } + BaseError.__init__(self, *args, **kwargs) + +# forward declaration of AfcClient +cdef class AfcClient(BaseService) + +cdef class AfcFile(Base): + cdef uint64_t _c_handle + cdef AfcClient _client + cdef bytes _filename + + def __init__(self, *args, **kwargs): + raise TypeError("AfcFile cannot be instantiated") + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + cpdef close(self): + self.handle_error(afc_file_close(self._client._c_client, self._c_handle)) + + cpdef lock(self, int operation): + self.handle_error(afc_file_lock(self._client._c_client, self._c_handle, <afc_lock_op_t>operation)) + + cpdef seek(self, int64_t offset, int whence): + self.handle_error(afc_file_seek(self._client._c_client, self._c_handle, offset, whence)) + + cpdef uint64_t tell(self): + cdef uint64_t position + self.handle_error(afc_file_tell(self._client._c_client, self._c_handle, &position)) + return position + + cpdef truncate(self, uint64_t newsize): + self.handle_error(afc_file_truncate(self._client._c_client, self._c_handle, newsize)) + + cpdef bytes read(self, uint32_t size): + cdef: + uint32_t bytes_read + char* c_data = <char *>malloc(size) + bytes result + try: + self.handle_error(afc_file_read(self._client._c_client, self._c_handle, c_data, size, &bytes_read)) + result = c_data[:bytes_read] + return result + except BaseError, e: + raise + finally: + free(c_data) + + cpdef uint32_t write(self, bytes data): + cdef: + uint32_t bytes_written + char* c_data = data + try: + self.handle_error(afc_file_write(self._client._c_client, self._c_handle, c_data, len(data), &bytes_written)) + except BaseError, e: + raise + + return bytes_written + + cdef inline BaseError _error(self, int16_t ret): + return AfcError(ret) + +cdef class AfcClient(BaseService): + __service_name__ = "com.apple.afc" + cdef afc_client_t _c_client + + def __cinit__(self, iDevice device = None, LockdownServiceDescriptor descriptor = None, *args, **kwargs): + if (device is not None and descriptor is not None): + self.handle_error(afc_client_new(device._c_dev, descriptor._c_service_descriptor, &(self._c_client))) + + def __dealloc__(self): + cdef afc_error_t err + if self._c_client is not NULL: + err = afc_client_free(self._c_client) + self.handle_error(err) + + cdef BaseError _error(self, int16_t ret): + return AfcError(ret) + + cpdef list get_device_info(self): + cdef: + afc_error_t err + char** infos = NULL + bytes info + int i = 0 + list result = [] + err = afc_get_device_info(self._c_client, &infos) + try: + self.handle_error(err) + except BaseError, e: + raise + finally: + if infos != NULL: + while infos[i]: + info = infos[i] + result.append(info) + free(infos[i]) + i = i + 1 + free(infos) + + return result + + cpdef list read_directory(self, bytes directory): + cdef: + afc_error_t err + char** dir_list = NULL + bytes f + int i = 0 + list result = [] + err = afc_read_directory(self._c_client, directory, &dir_list) + try: + self.handle_error(err) + except BaseError, e: + raise + finally: + if dir_list != NULL: + while dir_list[i]: + f = dir_list[i] + result.append(f) + free(dir_list[i]) + i = i + 1 + free(dir_list) + + return result + + cpdef AfcFile open(self, bytes filename, bytes mode=b'r'): + cdef: + afc_file_mode_t c_mode + uint64_t handle + AfcFile f + if mode == b'r': + c_mode = AFC_FOPEN_RDONLY + elif mode == b'r+': + c_mode = AFC_FOPEN_RW + elif mode == b'w': + c_mode = AFC_FOPEN_WRONLY + elif mode == b'w+': + c_mode = AFC_FOPEN_WR + elif mode == b'a': + c_mode = AFC_FOPEN_APPEND + elif mode == b'a+': + c_mode = AFC_FOPEN_RDAPPEND + else: + raise ValueError("mode string must be 'r', 'r+', 'w', 'w+', 'a', or 'a+'") + + self.handle_error(afc_file_open(self._c_client, filename, c_mode, &handle)) + f = AfcFile.__new__(AfcFile) + f._c_handle = handle + f._client = self + f._filename = filename + + return f + + cpdef list get_file_info(self, bytes path): + cdef: + list result = [] + char** c_result = NULL + int i = 0 + bytes info + try: + self.handle_error(afc_get_file_info(self._c_client, path, &c_result)) + except BaseError, e: + raise + finally: + if c_result != NULL: + while c_result[i]: + info = c_result[i] + result.append(info) + free(c_result[i]) + i = i + 1 + free(c_result) + + return result + + cpdef remove_path(self, bytes path): + self.handle_error(afc_remove_path(self._c_client, path)) + + cpdef remove_path_and_contents(self, bytes path): + self.handle_error(afc_remove_path_and_contents(self._c_client, path)) + + cpdef rename_path(self, bytes f, bytes t): + self.handle_error(afc_rename_path(self._c_client, f, t)) + + cpdef make_directory(self, bytes d): + self.handle_error(afc_make_directory(self._c_client, d)) + + cpdef truncate(self, bytes path, uint64_t newsize): + self.handle_error(afc_truncate(self._c_client, path, newsize)) + + cpdef link(self, bytes source, bytes link_name): + self.handle_error(afc_make_link(self._c_client, AFC_HARDLINK, source, link_name)) + + cpdef symlink(self, bytes source, bytes link_name): + self.handle_error(afc_make_link(self._c_client, AFC_SYMLINK, source, link_name)) + + cpdef set_file_time(self, bytes path, uint64_t mtime): + self.handle_error(afc_set_file_time(self._c_client, path, mtime)) + +cdef class Afc2Client(AfcClient): + __service_name__ = "com.apple.afc2" + + cpdef AfcFile open(self, bytes filename, bytes mode=b'r'): + cdef: + afc_file_mode_t c_mode + uint64_t handle + AfcFile f + if mode == b'r': + c_mode = AFC_FOPEN_RDONLY + elif mode == b'r+': + c_mode = AFC_FOPEN_RW + elif mode == b'w': + c_mode = AFC_FOPEN_WRONLY + elif mode == b'w+': + c_mode = AFC_FOPEN_WR + elif mode == b'a': + c_mode = AFC_FOPEN_APPEND + elif mode == b'a+': + c_mode = AFC_FOPEN_RDAPPEND + else: + raise ValueError("mode string must be 'r', 'r+', 'w', 'w+', 'a', or 'a+'") + + self.handle_error(afc_file_open(self._c_client, filename, c_mode, &handle)) + f = AfcFile.__new__(AfcFile) + f._c_handle = handle + f._client = <AfcClient>self + f._filename = filename + + return f + diff --git a/cython/debugserver.pxi b/cython/debugserver.pxi new file mode 100644 index 0000000..a3b7d1e --- /dev/null +++ b/cython/debugserver.pxi @@ -0,0 +1,252 @@ +cdef extern from "libimobiledevice/debugserver.h": + cdef struct debugserver_client_private: + pass + ctypedef debugserver_client_private *debugserver_client_t + cdef struct debugserver_command_private: + pass + ctypedef debugserver_command_private *debugserver_command_t + ctypedef enum debugserver_error_t: + DEBUGSERVER_E_SUCCESS = 0 + DEBUGSERVER_E_INVALID_ARG = -1 + DEBUGSERVER_E_MUX_ERROR = -2 + DEBUGSERVER_E_SSL_ERROR = -3 + DEBUGSERVER_E_RESPONSE_ERROR = -4 + DEBUGSERVER_E_UNKNOWN_ERROR = -256 + + debugserver_error_t debugserver_client_new(idevice_t device, lockdownd_service_descriptor_t service, debugserver_client_t * client) + debugserver_error_t debugserver_client_free(debugserver_client_t client) + + debugserver_error_t debugserver_client_send(debugserver_client_t client, const char* data, uint32_t size, uint32_t *sent) + debugserver_error_t debugserver_client_send_command(debugserver_client_t client, debugserver_command_t command, char** response, size_t* response_size) + debugserver_error_t debugserver_client_receive(debugserver_client_t client, char *data, uint32_t size, uint32_t *received) + debugserver_error_t debugserver_client_receive_with_timeout(debugserver_client_t client, char *data, uint32_t size, uint32_t *received, unsigned int timeout) + debugserver_error_t debugserver_client_receive_response(debugserver_client_t client, char** response, size_t* response_size) + debugserver_error_t debugserver_client_set_argv(debugserver_client_t client, int argc, char* argv[], char** response) + debugserver_error_t debugserver_client_set_environment_hex_encoded(debugserver_client_t client, const char* env, char** response) + + debugserver_error_t debugserver_command_new(const char* name, int argc, const char* argv[], debugserver_command_t* command) + debugserver_error_t debugserver_command_free(debugserver_command_t command) + void debugserver_encode_string(const char* buffer, char** encoded_buffer, uint32_t* encoded_length) + void debugserver_decode_string(const char *encoded_buffer, size_t encoded_length, char** buffer) + + +cdef class DebugServerError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + DEBUGSERVER_E_SUCCESS: "Success", + DEBUGSERVER_E_INVALID_ARG: "Invalid argument", + DEBUGSERVER_E_MUX_ERROR: "MUX error", + DEBUGSERVER_E_SSL_ERROR: "SSL error", + DEBUGSERVER_E_RESPONSE_ERROR: "Response error", + DEBUGSERVER_E_UNKNOWN_ERROR: "Unknown error", + } + BaseError.__init__(self, *args, **kwargs) + + +# from http://stackoverflow.com/a/17511714 +# https://github.com/libimobiledevice/libimobiledevice/pull/198 +from cpython cimport PY_MAJOR_VERSION +if PY_MAJOR_VERSION <= 2: + from cpython.string cimport PyString_AsString +else: + from cpython.bytes cimport PyBytes_AsString as PyString_AsString +cdef char ** to_cstring_array(list_str): + if not list_str: + return NULL + cdef char **ret = <char **>malloc(len(list_str) * sizeof(char *)) + for i in xrange(len(list_str)): + ret[i] = PyString_AsString(list_str[i]) + return ret + + +cdef class DebugServerCommand(Base): + cdef debugserver_command_t _c_command + + def __init__(self, bytes name, int argc = 0, argv = None, *args, **kwargs): + cdef: + char* c_name = name + char** c_argv = to_cstring_array(argv) + + try: + self.handle_error(debugserver_command_new(c_name, argc, c_argv, &self._c_command)) + except BaseError, e: + raise + finally: + free(c_argv) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.free() + + cdef free(self): + cdef debugserver_error_t err + if self._c_command is not NULL: + err = debugserver_command_free(self._c_command) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return DebugServerError(ret) + + +cdef class DebugServerClient(BaseService): + __service_name__ = "com.apple.debugserver" + cdef debugserver_client_t _c_client + + def __cinit__(self, iDevice device = None, LockdownServiceDescriptor descriptor = None, *args, **kwargs): + if (device is not None and descriptor is not None): + self.handle_error(debugserver_client_new(device._c_dev, descriptor._c_service_descriptor, &(self._c_client))) + + def __dealloc__(self): + cdef debugserver_error_t err + if self._c_client is not NULL: + err = debugserver_client_free(self._c_client) + self.handle_error(err) + + cdef BaseError _error(self, int16_t ret): + return DebugServerError(ret) + + cpdef uint32_t send(self, bytes data): + cdef: + uint32_t bytes_send + char* c_data = data + try: + self.handle_error(debugserver_client_send(self._c_client, c_data, len(data), &bytes_send)) + except BaseError, e: + raise + + return bytes_send + + cpdef bytes send_command(self, DebugServerCommand command): + cdef: + char* c_response = NULL + bytes result + + try: + self.handle_error(debugserver_client_send_command(self._c_client, command._c_command, &c_response, NULL)) + if c_response: + result = c_response + return result + else: + return None + except BaseError, e: + raise + finally: + free(c_response) + + cpdef bytes receive(self, uint32_t size): + cdef: + uint32_t bytes_received + char* c_data = <char *>malloc(size) + bytes result + + try: + self.handle_error(debugserver_client_receive(self._c_client, c_data, size, &bytes_received)) + result = c_data[:bytes_received] + return result + except BaseError, e: + raise + finally: + free(c_data) + + cpdef bytes receive_with_timeout(self, uint32_t size, unsigned int timeout): + cdef: + uint32_t bytes_received + char* c_data = <char *>malloc(size) + bytes result + + try: + self.handle_error(debugserver_client_receive_with_timeout(self._c_client, c_data, size, &bytes_received, timeout)) + result = c_data[:bytes_received] + return result + except BaseError, e: + raise + finally: + free(c_data) + + cpdef bytes receive_response(self): + cdef: + char* c_response = NULL + bytes result + + try: + self.handle_error(debugserver_client_receive_response(self._c_client, &c_response, NULL)) + if c_response: + result = c_response + return result + else: + return None + except BaseError, e: + raise + finally: + free(c_response) + + cpdef bytes set_argv(self, int argc, argv): + cdef: + char** c_argv = to_cstring_array(argv) + char* c_response = NULL + bytes result + + try: + self.handle_error(debugserver_client_set_argv(self._c_client, argc, c_argv, &c_response)) + if c_response: + result = c_response + return result + else: + return None + except BaseError, e: + raise + finally: + free(c_argv) + free(c_response) + + cpdef bytes set_environment_hex_encoded(self, bytes env): + cdef: + char* c_env = env + char* c_response = NULL + bytes result + + try: + self.handle_error(debugserver_client_set_environment_hex_encoded(self._c_client, c_env, &c_response)) + if c_response: + result = c_response + return result + else: + return None + except BaseError, e: + raise + finally: + free(c_response) + + cpdef bytes encode_string(self, bytes buffer): + cdef: + char *c_buffer = buffer + uint32_t encoded_length = len(c_buffer) * 2 + 0x3 + 1 + char* c_encoded_buffer = <char *>malloc(encoded_length) + bytes result + + try: + debugserver_encode_string(c_buffer, &c_encoded_buffer, &encoded_length) + result = c_encoded_buffer[:encoded_length] + return result + except BaseError, e: + raise + finally: + free(c_encoded_buffer) + + cpdef bytes decode_string(self, bytes encoded_buffer): + cdef: + char* c_encoded_buffer = encoded_buffer + uint32_t encoded_length = len(c_encoded_buffer) + char *c_buffer = <char *>malloc(encoded_length) + bytes result + + try: + debugserver_decode_string(c_encoded_buffer, encoded_length, &c_buffer) + result = c_buffer + return result + except BaseError, e: + raise + finally: + free(c_buffer) diff --git a/cython/diagnostics_relay.pxi b/cython/diagnostics_relay.pxi new file mode 100644 index 0000000..0e6bd20 --- /dev/null +++ b/cython/diagnostics_relay.pxi @@ -0,0 +1,128 @@ +REQUEST_TYPE_ALL = "All" +REQUEST_TYPE_WIFI = "WiFi" +REQUEST_TYPE_GAS_GAUGE = "GasGauge" +REQUEST_TYPE_NAND = "NAND" + +cdef extern from "libimobiledevice/diagnostics_relay.h": + cdef struct diagnostics_relay_client_private: + pass + ctypedef diagnostics_relay_client_private *diagnostics_relay_client_t + + ctypedef enum diagnostics_relay_error_t: + DIAGNOSTICS_RELAY_E_SUCCESS = 0 + DIAGNOSTICS_RELAY_E_INVALID_ARG = -1 + DIAGNOSTICS_RELAY_E_PLIST_ERROR = -2 + DIAGNOSTICS_RELAY_E_MUX_ERROR = -3 + DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST = -4 + DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR = -256 + ctypedef enum diagnostics_relay_action_t: + DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT = (1 << 1) + DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_PASS = (1 << 2) + DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_FAIL = (1 << 3) + + diagnostics_relay_error_t diagnostics_relay_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, diagnostics_relay_client_t * client) + diagnostics_relay_error_t diagnostics_relay_client_free(diagnostics_relay_client_t client) + + diagnostics_relay_error_t diagnostics_relay_goodbye(diagnostics_relay_client_t client) + diagnostics_relay_error_t diagnostics_relay_sleep(diagnostics_relay_client_t client) + diagnostics_relay_error_t diagnostics_relay_restart(diagnostics_relay_client_t client, diagnostics_relay_action_t flags) + diagnostics_relay_error_t diagnostics_relay_shutdown(diagnostics_relay_client_t client, diagnostics_relay_action_t flags) + diagnostics_relay_error_t diagnostics_relay_request_diagnostics(diagnostics_relay_client_t client, char* type, plist.plist_t* diagnostics) + diagnostics_relay_error_t diagnostics_relay_query_mobilegestalt(diagnostics_relay_client_t client, plist.plist_t keys, plist.plist_t* result) + diagnostics_relay_error_t diagnostics_relay_query_ioregistry_entry(diagnostics_relay_client_t client, char* name, char* class_name, plist.plist_t* result) + diagnostics_relay_error_t diagnostics_relay_query_ioregistry_plane(diagnostics_relay_client_t client, char* plane, plist.plist_t* result) + +cdef class DiagnosticsRelayError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + DIAGNOSTICS_RELAY_E_SUCCESS: "Success", + DIAGNOSTICS_RELAY_E_INVALID_ARG: "Invalid argument", + DIAGNOSTICS_RELAY_E_PLIST_ERROR: "Property list error", + DIAGNOSTICS_RELAY_E_MUX_ERROR: "MUX error", + DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST: "Unknown request", + DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class DiagnosticsRelayClient(PropertyListService): + __service_name__ = "com.apple.mobile.diagnostics_relay" + cdef diagnostics_relay_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(diagnostics_relay_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef diagnostics_relay_error_t err + if self._c_client is not NULL: + err = diagnostics_relay_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return DiagnosticsRelayError(ret) + + cpdef goodbye(self): + self.handle_error(diagnostics_relay_goodbye(self._c_client)) + + cpdef sleep(self): + self.handle_error(diagnostics_relay_sleep(self._c_client)) + + cpdef restart(self, diagnostics_relay_action_t flags): + self.handle_error(diagnostics_relay_restart(self._c_client, flags)) + + cpdef shutdown(self, diagnostics_relay_action_t flags): + self.handle_error(diagnostics_relay_shutdown(self._c_client, flags)) + + cpdef plist.Node request_diagnostics(self, bytes type): + cdef: + plist.plist_t c_node = NULL + diagnostics_relay_error_t err + err = diagnostics_relay_request_diagnostics(self._c_client, type, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef plist.Node query_mobilegestalt(self, plist.Node keys = None): + cdef: + plist.plist_t c_node = NULL + diagnostics_relay_error_t err + plist.plist_t keys_c_node = NULL + if keys is not None: + keys_c_node = keys._c_node + err = diagnostics_relay_query_mobilegestalt(self._c_client, keys_c_node, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef plist.Node query_ioregistry_entry(self, bytes name, bytes class_name): + cdef: + plist.plist_t c_node = NULL + diagnostics_relay_error_t err + err = diagnostics_relay_query_ioregistry_entry(self._c_client, name, class_name, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef plist.Node query_ioregistry_plane(self, bytes plane = None): + cdef: + plist.plist_t c_node = NULL + diagnostics_relay_error_t err + err = diagnostics_relay_query_ioregistry_plane(self._c_client, plane, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise diff --git a/cython/file_relay.pxi b/cython/file_relay.pxi new file mode 100644 index 0000000..5e9cbbe --- /dev/null +++ b/cython/file_relay.pxi @@ -0,0 +1,68 @@ +cdef extern from "libimobiledevice/file_relay.h": + cdef struct file_relay_client_private: + pass + ctypedef file_relay_client_private *file_relay_client_t + ctypedef char** const_sources_t "const char**" + + ctypedef enum file_relay_error_t: + FILE_RELAY_E_SUCCESS = 0 + FILE_RELAY_E_INVALID_ARG = -1 + FILE_RELAY_E_PLIST_ERROR = -2 + FILE_RELAY_E_MUX_ERROR = -3 + FILE_RELAY_E_INVALID_SOURCE = -4 + FILE_RELAY_E_STAGING_EMPTY = -5 + FILE_RELAY_E_PERMISSION_DENIED = -6 + FILE_RELAY_E_UNKNOWN_ERROR = -256 + + file_relay_error_t file_relay_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, file_relay_client_t *client) + file_relay_error_t file_relay_client_free(file_relay_client_t client) + + file_relay_error_t file_relay_request_sources(file_relay_client_t client, const_sources_t sources, idevice_connection_t *connection) + +cdef class FileRelayError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + FILE_RELAY_E_SUCCESS: "Success", + FILE_RELAY_E_INVALID_ARG: "Invalid argument", + FILE_RELAY_E_PLIST_ERROR: "Property list error", + FILE_RELAY_E_MUX_ERROR: "MUX error", + FILE_RELAY_E_INVALID_SOURCE: "Invalid source", + FILE_RELAY_E_STAGING_EMPTY: "Staging empty", + FILE_RELAY_E_PERMISSION_DENIED: "Permission denied", + FILE_RELAY_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +from libc.stdlib cimport * + +cdef class FileRelayClient(PropertyListService): + __service_name__ = "com.apple.mobile.file_relay" + cdef file_relay_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(file_relay_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef file_relay_error_t err + if self._c_client is not NULL: + err = file_relay_client_free(self._c_client) + self.handle_error(err) + + cpdef iDeviceConnection request_sources(self, list sources): + cdef: + file_relay_error_t err + Py_ssize_t count = len(sources) + char** c_sources = <char**>malloc(sizeof(char*) * (count + 1)) + iDeviceConnection conn = iDeviceConnection.__new__(iDeviceConnection) + + for i, value in enumerate(sources): + c_sources[i] = value + c_sources[count] = NULL + + err = file_relay_request_sources(self._c_client, <const_sources_t>c_sources, &conn._c_connection) + free(c_sources) + self.handle_error(err) + return conn + + cdef inline BaseError _error(self, int16_t ret): + return FileRelayError(ret) diff --git a/cython/heartbeat.pxi b/cython/heartbeat.pxi new file mode 100644 index 0000000..2f58909 --- /dev/null +++ b/cython/heartbeat.pxi @@ -0,0 +1,60 @@ +cdef extern from "libimobiledevice/heartbeat.h": + cdef struct heartbeat_client_private: + pass + ctypedef heartbeat_client_private *heartbeat_client_t + + ctypedef enum heartbeat_error_t: + HEARTBEAT_E_SUCCESS = 0 + HEARTBEAT_E_INVALID_ARG = -1 + HEARTBEAT_E_PLIST_ERROR = -2 + HEARTBEAT_E_MUX_ERROR = -3 + HEARTBEAT_E_SSL_ERROR = -4 + HEARTBEAT_E_NOT_ENOUGH_DATA = -5 + HEARTBEAT_E_TIMEOUT = -6 + HEARTBEAT_E_UNKNOWN_ERROR = -256 + + heartbeat_error_t heartbeat_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, heartbeat_client_t * client) + heartbeat_error_t heartbeat_client_free(heartbeat_client_t client) + + heartbeat_error_t heartbeat_send(heartbeat_client_t client, plist.plist_t plist) + heartbeat_error_t heartbeat_receive(heartbeat_client_t client, plist.plist_t * plist) + heartbeat_error_t heartbeat_receive_with_timeout(heartbeat_client_t client, plist.plist_t * plist, uint32_t timeout_ms) + +cdef class HeartbeatError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + HEARTBEAT_E_SUCCESS: "Success", + HEARTBEAT_E_INVALID_ARG: "Invalid argument", + HEARTBEAT_E_PLIST_ERROR: "Property list error", + HEARTBEAT_E_MUX_ERROR: "MUX error", + HEARTBEAT_E_SSL_ERROR: "SSL Error", + HEARTBEAT_E_NOT_ENOUGH_DATA: 'Not enough data', + HEARTBEAT_E_TIMEOUT: 'Connection timeout', + HEARTBEAT_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class HeartbeatClient(PropertyListService): + __service_name__ = "com.apple.heartbeat" + cdef heartbeat_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(heartbeat_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef heartbeat_error_t err + if self._c_client is not NULL: + err = heartbeat_client_free(self._c_client) + self.handle_error(err) + + cdef inline int16_t _send(self, plist.plist_t node): + return heartbeat_send(self._c_client, node) + + cdef inline int16_t _receive(self, plist.plist_t* node): + return heartbeat_receive(self._c_client, node) + + cdef inline int16_t _receive_with_timeout(self, plist.plist_t* node, int timeout_ms): + return heartbeat_receive_with_timeout(self._c_client, node, timeout_ms) + + cdef inline BaseError _error(self, int16_t ret): + return HeartbeatError(ret) diff --git a/cython/house_arrest.pxi b/cython/house_arrest.pxi new file mode 100644 index 0000000..54eebc1 --- /dev/null +++ b/cython/house_arrest.pxi @@ -0,0 +1,84 @@ +cdef extern from "libimobiledevice/house_arrest.h": + cdef struct house_arrest_client_private: + pass + ctypedef house_arrest_client_private *house_arrest_client_t + + ctypedef enum house_arrest_error_t: + HOUSE_ARREST_E_SUCCESS = 0 + HOUSE_ARREST_E_INVALID_ARG = -1 + HOUSE_ARREST_E_PLIST_ERROR = -2 + HOUSE_ARREST_E_CONN_FAILED = -3 + HOUSE_ARREST_E_INVALID_MODE = -4 + HOUSE_ARREST_E_UNKNOWN_ERROR = -256 + + house_arrest_error_t house_arrest_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, house_arrest_client_t * client) + house_arrest_error_t house_arrest_client_free(house_arrest_client_t client) + + house_arrest_error_t house_arrest_send_request(house_arrest_client_t client, plist.plist_t dict) + house_arrest_error_t house_arrest_send_command(house_arrest_client_t client, char *command, char *appid) + house_arrest_error_t house_arrest_get_result(house_arrest_client_t client, plist.plist_t *dict) + + afc_error_t afc_client_new_from_house_arrest_client(house_arrest_client_t client, afc_client_t *afc_client) + +cdef class HouseArrestError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + HOUSE_ARREST_E_SUCCESS: "Success", + HOUSE_ARREST_E_INVALID_ARG: "Invalid argument", + HOUSE_ARREST_E_PLIST_ERROR: "Property list error", + HOUSE_ARREST_E_CONN_FAILED: "Connection failed", + HOUSE_ARREST_E_INVALID_MODE: "Invalid mode", + HOUSE_ARREST_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class HouseArrestClient(PropertyListService): + __service_name__ = "com.apple.mobile.house_arrest" + cdef house_arrest_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(house_arrest_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef house_arrest_error_t err + if self._c_client is not NULL: + err = house_arrest_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return HouseArrestError(ret) + + cpdef send_request(self, plist.Node message): + self.handle_error(house_arrest_send_request(self._c_client, message._c_node)) + + cpdef send_command(self, bytes command, bytes appid): + self.handle_error(house_arrest_send_command(self._c_client, command, appid)) + + cpdef plist.Node get_result(self): + cdef: + plist.plist_t c_node = NULL + house_arrest_error_t err + err = house_arrest_get_result(self._c_client, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef AfcClient to_afc_client(self): + cdef: + afc_client_t c_afc_client = NULL + AfcClient result + afc_error_t err + err = afc_client_new_from_house_arrest_client(self._c_client, &c_afc_client) + try: + result = AfcClient.__new__(AfcClient) + result._c_client = c_afc_client + result.handle_error(err) + return result + except BaseError, e: + if c_afc_client != NULL: + afc_client_free(c_afc_client); + raise diff --git a/cython/imobiledevice.pxd b/cython/imobiledevice.pxd new file mode 100644 index 0000000..238df68 --- /dev/null +++ b/cython/imobiledevice.pxd @@ -0,0 +1,110 @@ +#!python +#cython: language_level=3str + +cimport plist + +from libc.stdint cimport * + +cdef extern from "pyerrors.h": + ctypedef class __builtin__.Exception [object PyBaseExceptionObject]: + pass + +cdef class BaseError(Exception): + cdef dict _lookup_table + cdef int16_t _c_errcode + +cdef class Base: + cdef inline int handle_error(self, int16_t ret) except -1 + cdef BaseError _error(self, int16_t ret) + +cdef class iDeviceError(BaseError): pass + +cdef extern from "libimobiledevice/libimobiledevice.h": + cdef struct idevice_private: + pass + ctypedef idevice_private* idevice_t + cdef struct idevice_connection_private: + pass + ctypedef idevice_connection_private* idevice_connection_t + cdef enum idevice_connection_type: + CONNECTION_USBMUXD = 1 + CONNECTION_NETWORK + cdef enum idevice_event_type: + IDEVICE_DEVICE_ADD = 1 + IDEVICE_DEVICE_REMOVE + IDEVICE_DEVICE_PAIRED + ctypedef struct idevice_event_t: + idevice_event_type event + char *udid + idevice_connection_type conn_type + ctypedef idevice_event_t* const_idevice_event_t "const idevice_event_t*" + +cdef class iDeviceEvent: + cdef const_idevice_event_t _c_event + +cdef class iDeviceConnection(Base): + cdef idevice_connection_t _c_connection + + cpdef bytes receive_timeout(self, uint32_t max_len, unsigned int timeout) + cpdef bytes receive(self, max_len) + cpdef disconnect(self) + +cdef class iDevice(Base): + cdef idevice_t _c_dev + + cpdef iDeviceConnection connect(self, uint16_t port) + +cdef class BaseService(Base): + pass + +cdef class PropertyListService(BaseService): + cpdef send(self, plist.Node node) + cpdef object receive(self) + cpdef object receive_with_timeout(self, int timeout_ms) + cdef int16_t _send(self, plist.plist_t node) + cdef int16_t _receive(self, plist.plist_t* c_node) + cdef int16_t _receive_with_timeout(self, plist.plist_t* c_node, int timeout_ms) + +cdef extern from "libimobiledevice/lockdown.h": + cdef struct lockdownd_client_private: + pass + ctypedef lockdownd_client_private *lockdownd_client_t + cdef struct lockdownd_pair_record: + char *device_certificate + char *host_certificate + char *host_id + char *root_certificate + ctypedef lockdownd_pair_record *lockdownd_pair_record_t + cdef struct lockdownd_service_descriptor: + uint16_t port + uint8_t ssl_enabled + ctypedef lockdownd_service_descriptor *lockdownd_service_descriptor_t + +cdef class LockdownError(BaseError): pass + +cdef class LockdownPairRecord: + cdef lockdownd_pair_record_t _c_record + +cdef class LockdownServiceDescriptor(Base): + cdef lockdownd_service_descriptor_t _c_service_descriptor + +cdef class LockdownClient(PropertyListService): + cdef lockdownd_client_t _c_client + cdef readonly iDevice device + + cpdef bytes query_type(self) + cpdef plist.Node get_value(self, bytes domain=*, bytes key=*) + cpdef set_value(self, bytes domain, bytes key, object value) + cpdef remove_value(self, bytes domain, bytes key) + cpdef object start_service(self, object service) + cpdef object get_service_client(self, object service_class) + cpdef tuple start_session(self, bytes host_id) + cpdef stop_session(self, bytes session_id) + cpdef pair(self, object pair_record=*) + cpdef validate_pair(self, object pair_record=*) + cpdef unpair(self, object pair_record=*) + cpdef activate(self, plist.Node activation_record) + cpdef deactivate(self) + cpdef enter_recovery(self) + cpdef goodbye(self) + cpdef list get_sync_data_classes(self) diff --git a/cython/imobiledevice.pyx b/cython/imobiledevice.pyx new file mode 100644 index 0000000..8da2296 --- /dev/null +++ b/cython/imobiledevice.pyx @@ -0,0 +1,294 @@ +cdef class BaseError(Exception): + def __cinit__(self, int16_t errcode): + self._c_errcode = errcode + + def __nonzero__(self): + return self._c_errcode != 0 + + property message: + def __get__(self): + if self._c_errcode in self._lookup_table: + return self._lookup_table[self._c_errcode] + else: + return "Unknown error ({0})".format(self._c_errcode) + + property code: + def __get__(self): + return self._c_errcode + + def __str__(self): + return '%s (%s)' % (self.message, self.code) + + def __repr__(self): + return self.__str__() + +cdef class Base: + cdef inline int handle_error(self, int16_t ret) except -1: + if ret == 0: + return 0 + cdef BaseError err = self._error(ret) + raise err + + cdef BaseError _error(self, int16_t ret): pass + +cdef extern from "libimobiledevice/libimobiledevice.h": + ctypedef enum idevice_error_t: + IDEVICE_E_SUCCESS = 0 + IDEVICE_E_INVALID_ARG = -1 + IDEVICE_E_UNKNOWN_ERROR = -2 + IDEVICE_E_NO_DEVICE = -3 + IDEVICE_E_NOT_ENOUGH_DATA = -4 + IDEVICE_E_SSL_ERROR = -6 + IDEVICE_E_TIMEOUT = -7 + cdef enum idevice_options: + IDEVICE_LOOKUP_USBMUX = 1 << 1 + IDEVICE_LOOKUP_NETWORK = 1 << 2 + IDEVICE_LOOKUP_PREFER_NETWORK = 1 << 3 + ctypedef void (*idevice_event_cb_t) (const_idevice_event_t event, void *user_data) + cdef extern idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data) + cdef extern idevice_error_t idevice_event_unsubscribe() + idevice_error_t idevice_get_device_list(char ***devices, int *count) + idevice_error_t idevice_device_list_free(char **devices) + void idevice_set_debug_level(int level) + idevice_error_t idevice_new(idevice_t *device, char *udid) + idevice_error_t idevice_new_with_options(idevice_t *device, const char *udid, idevice_options options); + idevice_error_t idevice_free(idevice_t device) + idevice_error_t idevice_get_udid(idevice_t device, char** udid) + idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle) + idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connection_t *connection) + idevice_error_t idevice_disconnect(idevice_connection_t connection) + idevice_error_t idevice_connection_send(idevice_connection_t connection, char *data, uint32_t len, uint32_t *sent_bytes) + idevice_error_t idevice_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout) + idevice_error_t idevice_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes) + +cdef class iDeviceError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + IDEVICE_E_SUCCESS: 'Success', + IDEVICE_E_INVALID_ARG: 'Invalid argument', + IDEVICE_E_UNKNOWN_ERROR: 'Unknown error', + IDEVICE_E_NO_DEVICE: 'No device', + IDEVICE_E_NOT_ENOUGH_DATA: 'Not enough data', + IDEVICE_E_SSL_ERROR: 'SSL Error', + IDEVICE_E_TIMEOUT: 'Connection timeout' + } + BaseError.__init__(self, *args, **kwargs) + +def set_debug_level(int level): + idevice_set_debug_level(level) + +cdef class iDeviceEvent: + def __init__(self, *args, **kwargs): + raise TypeError("iDeviceEvent cannot be instantiated") + + def __str__(self): + return 'iDeviceEvent: %s (%s)' % (self.event == IDEVICE_DEVICE_ADD and 'Add' or 'Remove', self.udid) + + property event: + def __get__(self): + return self._c_event.event + property udid: + def __get__(self): + return self._c_event.udid + property conn_type: + def __get__(self): + return self._c_event.conn_type + +cdef void idevice_event_cb(const_idevice_event_t c_event, void *user_data) noexcept: + cdef iDeviceEvent event = iDeviceEvent.__new__(iDeviceEvent) + event._c_event = c_event + (<object>user_data)(event) + +def event_subscribe(object callback): + cdef iDeviceError err = iDeviceError(idevice_event_subscribe(idevice_event_cb, <void*>callback)) + if err: raise err + +def event_unsubscribe(): + cdef iDeviceError err = iDeviceError(idevice_event_unsubscribe()) + if err: raise err + +def get_device_list(): + cdef: + char** devices = NULL + int count + list result + bytes device + iDeviceError err = iDeviceError(idevice_get_device_list(&devices, &count)) + + if err: + if devices != NULL: + idevice_device_list_free(devices) + raise err + + result = [] + for i from 0 <= i < count: + device = devices[i] + result.append(device) + + err = iDeviceError(idevice_device_list_free(devices)) + if err: raise err + return result + +cdef class iDeviceConnection(Base): + def __init__(self, *args, **kwargs): + raise TypeError("iDeviceConnection cannot be instantiated. Please use iDevice.connect()") + + cpdef bytes receive_timeout(self, uint32_t max_len, unsigned int timeout): + cdef: + uint32_t bytes_received + char* c_data = <char *>malloc(max_len) + bytes result + + try: + self.handle_error(idevice_connection_receive_timeout(self._c_connection, c_data, max_len, &bytes_received, timeout)) + result = c_data[:bytes_received] + return result + except BaseError, e: + raise + finally: + free(c_data) + + cpdef bytes receive(self, max_len): + cdef: + uint32_t bytes_received + char* c_data = <char *>malloc(max_len) + bytes result + + try: + self.handle_error(idevice_connection_receive(self._c_connection, c_data, max_len, &bytes_received)) + result = c_data[:bytes_received] + return result + except BaseError, e: + raise + finally: + free(c_data) + + cpdef disconnect(self): + cdef idevice_error_t err + err = idevice_disconnect(self._c_connection) + self.handle_error(err) + + cdef BaseError _error(self, int16_t ret): + return iDeviceError(ret) + +from libc.stdlib cimport * + +cdef class iDevice(Base): + def __cinit__(self, object udid=None, *args, **kwargs): + cdef char* c_udid = NULL + if isinstance(udid, (str, bytes)): + c_udid = <bytes>udid + elif udid is not None: + raise TypeError("iDevice's constructor takes a string or None as the udid argument") + self.handle_error(idevice_new(&self._c_dev, c_udid)) + + def __dealloc__(self): + if self._c_dev is not NULL: + self.handle_error(idevice_free(self._c_dev)) + + cdef BaseError _error(self, int16_t ret): + return iDeviceError(ret) + + cpdef iDeviceConnection connect(self, uint16_t port): + cdef: + idevice_error_t err + idevice_connection_t c_conn = NULL + iDeviceConnection conn + err = idevice_connect(self._c_dev, port, &c_conn) + try: + self.handle_error(err) + + conn = iDeviceConnection.__new__(iDeviceConnection) + conn._c_connection = c_conn + + return conn + except Exception, e: + if c_conn != NULL: + idevice_disconnect(c_conn) + + property udid: + def __get__(self): + cdef: + char* udid + idevice_error_t err + err = idevice_get_udid(self._c_dev, &udid) + try: + self.handle_error(err) + return udid + except Exception, e: + if udid != NULL: + free(udid) + property handle: + def __get__(self): + cdef uint32_t handle + self.handle_error(idevice_get_handle(self._c_dev, &handle)) + return handle + +cdef extern from *: + ctypedef char* const_char_ptr "const char*" + +cdef class BaseService(Base): + __service_name__ = None + +cdef class PropertyListService(BaseService): + cpdef send(self, plist.Node node): + self.handle_error(self._send(node._c_node)) + + cpdef object receive(self): + cdef: + plist.plist_t c_node = NULL + int16_t err + err = self._receive(&c_node) + try: + self.handle_error(err) + + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef object receive_with_timeout(self, int timeout_ms): + cdef: + plist.plist_t c_node = NULL + int16_t err + err = self._receive_with_timeout(&c_node, timeout_ms) + try: + self.handle_error(err) + + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cdef int16_t _send(self, plist.plist_t node): + raise NotImplementedError("send is not implemented") + + cdef int16_t _receive(self, plist.plist_t* c_node): + raise NotImplementedError("receive is not implemented") + + cdef int16_t _receive_with_timeout(self, plist.plist_t* c_node, int timeout_ms): + raise NotImplementedError("receive_with_timeout is not implemented") + +cdef class DeviceLinkService(PropertyListService): + pass + +include "lockdown.pxi" +include "mobilesync.pxi" +include "notification_proxy.pxi" +include "sbservices.pxi" +include "mobilebackup.pxi" +include "mobilebackup2.pxi" +include "afc.pxi" +include "file_relay.pxi" +include "screenshotr.pxi" +include "installation_proxy.pxi" +include "mobile_image_mounter.pxi" +include "webinspector.pxi" +include "heartbeat.pxi" +include "diagnostics_relay.pxi" +include "misagent.pxi" +include "house_arrest.pxi" +include "restore.pxi" +include "debugserver.pxi" diff --git a/cython/installation_proxy.pxi b/cython/installation_proxy.pxi new file mode 100644 index 0000000..1d3e323 --- /dev/null +++ b/cython/installation_proxy.pxi @@ -0,0 +1,304 @@ +cdef extern from "libimobiledevice/installation_proxy.h": + cdef struct instproxy_client_private: + pass + ctypedef instproxy_client_private *instproxy_client_t + ctypedef void (*instproxy_status_cb_t) (plist.plist_t command, plist.plist_t status, void *user_data) + + ctypedef enum instproxy_error_t: + INSTPROXY_E_SUCCESS = 0 + INSTPROXY_E_INVALID_ARG = -1 + INSTPROXY_E_PLIST_ERROR = -2 + INSTPROXY_E_CONN_FAILED = -3 + INSTPROXY_E_OP_IN_PROGRESS = -4 + INSTPROXY_E_OP_FAILED = -5 + INSTPROXY_E_UNKNOWN_ERROR = -256 + + instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, instproxy_client_t *client) + instproxy_error_t instproxy_client_free(instproxy_client_t client) + instproxy_error_t instproxy_client_get_path_for_bundle_identifier(instproxy_client_t client, const char* bundle_id, char** path) + + instproxy_error_t instproxy_browse(instproxy_client_t client, plist.plist_t client_options, plist.plist_t *result) + instproxy_error_t instproxy_install(instproxy_client_t client, char *pkg_path, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) + instproxy_error_t instproxy_upgrade(instproxy_client_t client, char *pkg_path, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) + instproxy_error_t instproxy_uninstall(instproxy_client_t client, char *appid, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) + + instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist.plist_t client_options, plist.plist_t *result) + instproxy_error_t instproxy_archive(instproxy_client_t client, char *appid, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) + instproxy_error_t instproxy_restore(instproxy_client_t client, char *appid, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) + instproxy_error_t instproxy_remove_archive(instproxy_client_t client, char *appid, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) + +cdef void instproxy_notify_cb(plist.plist_t command, plist.plist_t status, void *py_callback) noexcept: + (<object>py_callback)(plist.plist_t_to_node(command, False), plist.plist_t_to_node(status, False)) + +cdef class InstallationProxyError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + INSTPROXY_E_SUCCESS: "Success", + INSTPROXY_E_INVALID_ARG: "Invalid argument", + INSTPROXY_E_PLIST_ERROR: "Property list error", + INSTPROXY_E_CONN_FAILED: "Connection failed", + INSTPROXY_E_OP_IN_PROGRESS: "Operation in progress", + INSTPROXY_E_OP_FAILED: "Operation failed", + INSTPROXY_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class InstallationProxyClient(PropertyListService): + __service_name__ = "com.apple.mobile.installation_proxy" + cdef instproxy_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + cdef: + iDevice dev = device + instproxy_error_t err + err = instproxy_client_new(dev._c_dev, descriptor._c_service_descriptor, &self._c_client) + self.handle_error(err) + + def __dealloc__(self): + cdef instproxy_error_t err + if self._c_client is not NULL: + err = instproxy_client_free(self._c_client) + self.handle_error(err) + + cpdef get_path_for_bundle_identifier(self, bytes bundle_id): + cdef: + char* c_bundle_id = bundle_id + char* c_path = NULL + bytes result + + try: + self.handle_error(instproxy_client_get_path_for_bundle_identifier(self._c_client, c_bundle_id, &c_path)) + if c_path != NULL: + result = c_path + return result + else: + return None + except BaseError, e: + raise + finally: + free(c_path) + + cpdef plist.Node browse(self, object client_options): + cdef: + plist.Node options + plist.plist_t c_options + plist.plist_t c_result = NULL + bint free_options = False + instproxy_error_t err + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + err = instproxy_browse(self._c_client, c_options, &c_result) + + try: + self.handle_error(err) + return plist.plist_t_to_node(c_result) + except Exception, e: + if c_result != NULL: + plist.plist_free(c_result) + raise + finally: + if free_options: + plist.plist_free(c_options) + + cpdef install(self, bytes pkg_path, object client_options, object callback=None): + cdef: + plist.Node options + plist.plist_t c_options + bint free_options = False + instproxy_error_t err + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + if callback is None: + err = instproxy_install(self._c_client, pkg_path, c_options, NULL, NULL) + else: + err = instproxy_install(self._c_client, pkg_path, c_options, instproxy_notify_cb, <void*>callback) + + try: + self.handle_error(err) + except Exception, e: + raise + finally: + if free_options: + plist.plist_free(c_options) + + cpdef upgrade(self, bytes pkg_path, object client_options, object callback=None): + cdef: + plist.Node options + plist.plist_t c_options + bint free_options = False + instproxy_error_t err + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + if callback is None: + err = instproxy_upgrade(self._c_client, pkg_path, c_options, NULL, NULL) + else: + err = instproxy_upgrade(self._c_client, pkg_path, c_options, instproxy_notify_cb, <void*>callback) + try: + self.handle_error(err) + except Exception, e: + raise + finally: + if free_options: + plist.plist_free(c_options) + + cpdef uninstall(self, bytes appid, object client_options, object callback=None): + cdef: + plist.Node options + plist.plist_t c_options + instproxy_error_t err + bint free_options = False + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + + if callback is None: + err = instproxy_uninstall(self._c_client, appid, c_options, NULL, NULL) + else: + err = instproxy_uninstall(self._c_client, appid, c_options, instproxy_notify_cb, <void*>callback) + + try: + self.handle_error(err) + except Exception, e: + raise + finally: + if free_options: + plist.plist_free(c_options) + + cpdef plist.Node lookup_archives(self, object client_options): + cdef: + plist.Node options + plist.plist_t c_options + plist.plist_t c_node = NULL + instproxy_error_t err + bint free_options = False + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + + err = instproxy_lookup_archives(self._c_client, c_options, &c_node) + + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except Exception, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + finally: + if free_options: + plist.plist_free(c_options) + + cpdef archive(self, bytes appid, object client_options, object callback=None): + cdef: + plist.Node options + plist.plist_t c_options + bint free_options = False + instproxy_error_t err + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + + if callback is None: + err = instproxy_archive(self._c_client, appid, c_options, NULL, NULL) + else: + err = instproxy_archive(self._c_client, appid, c_options, instproxy_notify_cb, <void*>callback) + + try: + self.handle_error(err) + except Exception, e: + raise + finally: + if free_options: + plist.plist_free(c_options) + + cpdef restore(self, bytes appid, object client_options, object callback=None): + cdef: + plist.Node options + plist.plist_t c_options + bint free_options = False + instproxy_error_t err + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + + if callback is None: + err = instproxy_restore(self._c_client, appid, c_options, NULL, NULL) + else: + err = instproxy_restore(self._c_client, appid, c_options, instproxy_notify_cb, <void*>callback) + + try: + self.handle_error(err) + except Exception, e: + raise + finally: + if free_options: + plist.plist_free(c_options) + + cpdef remove_archive(self, bytes appid, object client_options, object callback=None): + cdef: + plist.Node options + plist.plist_t c_options + bint free_options = False + instproxy_error_t err + if isinstance(client_options, plist.Dict): + options = client_options + c_options = options._c_node + elif isinstance(client_options, dict): + c_options = plist.native_to_plist_t(client_options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + + if callback is None: + err = instproxy_remove_archive(self._c_client, appid, c_options, NULL, NULL) + else: + err = instproxy_remove_archive(self._c_client, appid, c_options, instproxy_notify_cb, <void*>callback) + + try: + self.handle_error(err) + except Exception, e: + raise + finally: + if free_options: + plist.plist_free(c_options) + + cdef inline BaseError _error(self, int16_t ret): + return InstallationProxyError(ret) diff --git a/cython/lockdown.pxi b/cython/lockdown.pxi new file mode 100644 index 0000000..25edb4c --- /dev/null +++ b/cython/lockdown.pxi @@ -0,0 +1,351 @@ +cdef extern from "libimobiledevice/lockdown.h": + ctypedef enum lockdownd_error_t: + LOCKDOWN_E_SUCCESS + LOCKDOWN_E_INVALID_ARG + LOCKDOWN_E_INVALID_CONF + LOCKDOWN_E_PLIST_ERROR + LOCKDOWN_E_PAIRING_FAILED + LOCKDOWN_E_SSL_ERROR + LOCKDOWN_E_DICT_ERROR + LOCKDOWN_E_RECEIVE_TIMEOUT + LOCKDOWN_E_SET_VALUE_PROHIBITED + LOCKDOWN_E_GET_VALUE_PROHIBITED + LOCKDOWN_E_MUX_ERROR + LOCKDOWN_E_NO_RUNNING_SESSION + LOCKDOWN_E_INVALID_RESPONSE + LOCKDOWN_E_MISSING_KEY + LOCKDOWN_E_MISSING_VALUE + LOCKDOWN_E_GET_PROHIBITED + LOCKDOWN_E_SET_PROHIBITED + LOCKDOWN_E_REMOVE_PROHIBITED + LOCKDOWN_E_IMMUTABLE_VALUE + LOCKDOWN_E_PASSWORD_PROTECTED + LOCKDOWN_E_USER_DENIED_PAIRING + LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING + LOCKDOWN_E_MISSING_HOST_ID + LOCKDOWN_E_INVALID_HOST_ID + LOCKDOWN_E_SESSION_ACTIVE + LOCKDOWN_E_SESSION_INACTIVE + LOCKDOWN_E_MISSING_SESSION_ID + LOCKDOWN_E_INVALID_SESSION_ID + LOCKDOWN_E_MISSING_SERVICE + LOCKDOWN_E_INVALID_SERVICE + LOCKDOWN_E_SERVICE_LIMIT + LOCKDOWN_E_MISSING_PAIR_RECORD + LOCKDOWN_E_SAVE_PAIR_RECORD_FAILED + LOCKDOWN_E_INVALID_PAIR_RECORD + LOCKDOWN_E_INVALID_ACTIVATION_RECORD + LOCKDOWN_E_MISSING_ACTIVATION_RECORD + LOCKDOWN_E_SERVICE_PROHIBITED + LOCKDOWN_E_ESCROW_LOCKED + LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION + LOCKDOWN_E_FMIP_PROTECTED + LOCKDOWN_E_MC_PROTECTED + LOCKDOWN_E_MC_CHALLENGE_REQUIRED + LOCKDOWN_E_UNKNOWN_ERROR + + lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *client, char *label) + lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdownd_client_t *client, char *label) + lockdownd_error_t lockdownd_client_free(lockdownd_client_t client) + + lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **tp) + lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, char *domain, char *key, plist.plist_t *value) + lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, char *domain, char *key, plist.plist_t value) + lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, char *domain, char *key) + lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, char *identifier, lockdownd_service_descriptor_t *service) + lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, char *host_id, char **session_id, int *ssl_enabled) + lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, char *session_id) + lockdownd_error_t lockdownd_send(lockdownd_client_t client, plist.plist_t plist) + lockdownd_error_t lockdownd_receive(lockdownd_client_t client, plist.plist_t *plist) + lockdownd_error_t lockdownd_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) + lockdownd_error_t lockdownd_validate_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) + lockdownd_error_t lockdownd_unpair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) + lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist.plist_t activation_record) + lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client) + lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client) + lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client) + lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, char ***classes, int *count) + lockdownd_error_t lockdownd_data_classes_free(char **classes) + lockdownd_error_t lockdownd_service_descriptor_free(lockdownd_service_descriptor_t service) + +cdef class LockdownError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + LOCKDOWN_E_SUCCESS: "Success", + LOCKDOWN_E_INVALID_ARG: "Invalid argument", + LOCKDOWN_E_INVALID_CONF: "Invalid configuration", + LOCKDOWN_E_PLIST_ERROR: "Property list error", + LOCKDOWN_E_PAIRING_FAILED: "Pairing failed", + LOCKDOWN_E_SSL_ERROR: "SSL error", + LOCKDOWN_E_DICT_ERROR: "Dictionary error", + LOCKDOWN_E_RECEIVE_TIMEOUT: "Receive timeout", + LOCKDOWN_E_MUX_ERROR: "Mux Protocol Error", + LOCKDOWN_E_NO_RUNNING_SESSION: "No running session", + LOCKDOWN_E_INVALID_RESPONSE: "Invalid response", + LOCKDOWN_E_MISSING_KEY: "Missing key", + LOCKDOWN_E_MISSING_VALUE: "Missing value", + LOCKDOWN_E_GET_PROHIBITED: "Get value prohibited", + LOCKDOWN_E_SET_PROHIBITED: "Set value prohibited", + LOCKDOWN_E_REMOVE_PROHIBITED: "Remove value prohibited", + LOCKDOWN_E_IMMUTABLE_VALUE: "Immutable value", + LOCKDOWN_E_PASSWORD_PROTECTED: "Password protected", + LOCKDOWN_E_USER_DENIED_PAIRING: "User denied pairing", + LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING: "Pairing dialog response pending", + LOCKDOWN_E_MISSING_HOST_ID: "Missing host ID", + LOCKDOWN_E_INVALID_HOST_ID: "Invalid host ID", + LOCKDOWN_E_SESSION_ACTIVE: "Session active", + LOCKDOWN_E_SESSION_INACTIVE: "Session inactive", + LOCKDOWN_E_MISSING_SESSION_ID: "Missing session ID", + LOCKDOWN_E_INVALID_SESSION_ID: "Invalid session ID", + LOCKDOWN_E_MISSING_SERVICE: "Missing service", + LOCKDOWN_E_INVALID_SERVICE: "Invalid service", + LOCKDOWN_E_SERVICE_LIMIT: "Service limit reached", + LOCKDOWN_E_MISSING_PAIR_RECORD: "Missing pair record", + LOCKDOWN_E_SAVE_PAIR_RECORD_FAILED: "Saving pair record failed", + LOCKDOWN_E_INVALID_PAIR_RECORD: "Invalid pair record", + LOCKDOWN_E_INVALID_ACTIVATION_RECORD: "Invalid activation record", + LOCKDOWN_E_MISSING_ACTIVATION_RECORD: "Missing activation record", + LOCKDOWN_E_SERVICE_PROHIBITED: "Service prohibited", + LOCKDOWN_E_ESCROW_LOCKED: "Escrow locked", + LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION: "Pairing prohibited over this connection", + LOCKDOWN_E_FMIP_PROTECTED: "Find My iPhone/iPod/iPad protected", + LOCKDOWN_E_MC_PROTECTED: "MC protected", + LOCKDOWN_E_MC_CHALLENGE_REQUIRED: "MC challenge required", + LOCKDOWN_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class LockdownPairRecord: + #def __cinit__(self, bytes device_certificate, bytes host_certificate, bytes host_id, bytes root_certificate, *args, **kwargs): + property device_certificate: + def __get__(self): + cdef bytes result = self._c_record.device_certificate + return result + property host_certificate: + def __get__(self): + cdef bytes result = self._c_record.host_certificate + return result + property host_id: + def __get__(self): + cdef bytes result = self._c_record.host_id + return result + property root_certificate: + def __get__(self): + cdef bytes result = self._c_record.root_certificate + return result + +cdef class LockdownServiceDescriptor(Base): + #def __cinit__(self, uint16_t port, uint8_t ssl_enabled, *args, **kwargs): + def __dealloc__(self): + cdef lockdownd_error_t err + if self._c_service_descriptor is not NULL: + err = lockdownd_service_descriptor_free(self._c_service_descriptor) + self._c_service_descriptor = NULL + self.handle_error(err) + property port: + def __get__(self): + return self._c_service_descriptor.port + property ssl_enabled: + def __get__(self): + return self._c_service_descriptor.ssl_enabled + +cdef class LockdownClient(PropertyListService): + def __cinit__(self, iDevice device not None, bytes label=b'', bint handshake=True, *args, **kwargs): + cdef: + lockdownd_error_t err + char* c_label = NULL + if label: + c_label = label + if handshake: + err = lockdownd_client_new_with_handshake(device._c_dev, &self._c_client, c_label) + else: + err = lockdownd_client_new(device._c_dev, &self._c_client, c_label) + self.handle_error(err) + + self.device = device + + def __dealloc__(self): + cdef lockdownd_error_t err + if self._c_client is not NULL: + err = lockdownd_client_free(self._c_client) + self.handle_error(err) + + cpdef bytes query_type(self): + cdef: + lockdownd_error_t err + char* c_type = NULL + bytes result + err = lockdownd_query_type(self._c_client, &c_type) + try: + self.handle_error(err) + result = c_type + + return result + except BaseError, e: + raise + finally: + if c_type != NULL: + free(c_type) + + cpdef plist.Node get_value(self, bytes domain=None, bytes key=None): + cdef: + lockdownd_error_t err + plist.plist_t c_node = NULL + char* c_domain = NULL + char* c_key = NULL + if domain is not None: + c_domain = domain + if key is not None: + c_key = key + + err = lockdownd_get_value(self._c_client, c_domain, c_key, &c_node) + + try: + self.handle_error(err) + + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef set_value(self, bytes domain, bytes key, object value): + cdef: + plist.plist_t c_node = NULL + char* c_domain = NULL + char* c_key = NULL + + c_node = plist.native_to_plist_t(value) + if domain is not None: + c_domain = domain + if key is not None: + c_key = key + try: + self.handle_error(lockdownd_set_value(self._c_client, c_domain, c_key, c_node)) + except BaseError, e: + raise + finally: + if c_node != NULL: + c_node = NULL + + cpdef remove_value(self, bytes domain, bytes key): + self.handle_error(lockdownd_remove_value(self._c_client, domain, key)) + + cpdef object start_service(self, object service): + cdef: + char* c_service_name = NULL + lockdownd_service_descriptor_t c_descriptor = NULL + LockdownServiceDescriptor result + + if issubclass(service, BaseService) and \ + service.__service_name__ is not None \ + and isinstance(service.__service_name__, (str, bytes)): + c_service_name_str = service.__service_name__.encode('utf-8') + elif isinstance(service, (str, bytes)): + c_service_name_str = service.encode('utf-8') + else: + raise TypeError("LockdownClient.start_service() takes a BaseService or string as its first argument") + c_service_name = c_service_name_str + + try: + self.handle_error(lockdownd_start_service(self._c_client, c_service_name, &c_descriptor)) + + result = LockdownServiceDescriptor.__new__(LockdownServiceDescriptor) + result._c_service_descriptor = c_descriptor + + return result + except BaseError, e: + raise + + cpdef object get_service_client(self, object service_class): + cdef: + LockdownServiceDescriptor descriptor + + if not hasattr(service_class, '__service_name__') and \ + not service_class.__service_name__ is not None \ + and not isinstance(service_class.__service_name__, (str, bytes)): + raise TypeError("LockdownClient.get_service_client() takes a BaseService as its first argument") + + descriptor = self.start_service(service_class) + return service_class(self.device, descriptor) + + cpdef tuple start_session(self, bytes host_id): + cdef: + lockdownd_error_t err + char* c_session_id = NULL + bint ssl_enabled + bytes session_id + err = lockdownd_start_session(self._c_client, host_id, &c_session_id, <int *>&ssl_enabled) + try: + self.handle_error(err) + + session_id = c_session_id + return (session_id, ssl_enabled) + except BaseError, e: + raise + finally: + if c_session_id != NULL: + free(c_session_id) + + cpdef stop_session(self, bytes session_id): + self.handle_error(lockdownd_stop_session(self._c_client, session_id)) + + cpdef pair(self, object pair_record=None): + cdef lockdownd_pair_record_t c_pair_record = NULL + if pair_record is not None: + c_pair_record = (<LockdownPairRecord>pair_record)._c_record + self.handle_error(lockdownd_pair(self._c_client, c_pair_record)) + + cpdef validate_pair(self, object pair_record=None): + cdef lockdownd_pair_record_t c_pair_record = NULL + if pair_record is not None: + c_pair_record = (<LockdownPairRecord>pair_record)._c_record + self.handle_error(lockdownd_validate_pair(self._c_client, c_pair_record)) + + cpdef unpair(self, object pair_record=None): + cdef lockdownd_pair_record_t c_pair_record = NULL + if pair_record is not None: + c_pair_record = (<LockdownPairRecord>pair_record)._c_record + self.handle_error(lockdownd_unpair(self._c_client, c_pair_record)) + + cpdef activate(self, plist.Node activation_record): + self.handle_error(lockdownd_activate(self._c_client, activation_record._c_node)) + + cpdef deactivate(self): + self.handle_error(lockdownd_deactivate(self._c_client)) + + cpdef enter_recovery(self): + self.handle_error(lockdownd_enter_recovery(self._c_client)) + + cpdef goodbye(self): + self.handle_error(lockdownd_goodbye(self._c_client)) + + cpdef list get_sync_data_classes(self): + cdef: + char **classes = NULL + int count = 0 + list result = [] + bytes data_class + + try: + self.handle_error(lockdownd_get_sync_data_classes(self._c_client, &classes, &count)) + + for i from 0 <= i < count: + data_class = classes[i] + result.append(data_class) + + return result + except Exception, e: + raise + finally: + if classes != NULL: + lockdownd_data_classes_free(classes) + + cdef inline int16_t _send(self, plist.plist_t node): + return lockdownd_send(self._c_client, node) + + cdef inline int16_t _receive(self, plist.plist_t* node): + return lockdownd_receive(self._c_client, node) + + cdef inline BaseError _error(self, int16_t ret): + return LockdownError(ret) diff --git a/cython/misagent.pxi b/cython/misagent.pxi new file mode 100644 index 0000000..1fee4b9 --- /dev/null +++ b/cython/misagent.pxi @@ -0,0 +1,74 @@ +cdef extern from "libimobiledevice/misagent.h": + cdef struct misagent_client_private: + pass + ctypedef misagent_client_private *misagent_client_t + + ctypedef enum misagent_error_t: + MISAGENT_E_SUCCESS = 0 + MISAGENT_E_INVALID_ARG = -1 + MISAGENT_E_PLIST_ERROR = -2 + MISAGENT_E_CONN_FAILED = -3 + MISAGENT_E_REQUEST_FAILED = -4 + MISAGENT_E_UNKNOWN_ERROR = -256 + + misagent_error_t misagent_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, misagent_client_t * client) + misagent_error_t misagent_client_free(misagent_client_t client) + + misagent_error_t misagent_install(misagent_client_t client, plist.plist_t profile) + misagent_error_t misagent_copy(misagent_client_t client, plist.plist_t* profiles) + misagent_error_t misagent_remove(misagent_client_t client, char* profileID) + int misagent_get_status_code(misagent_client_t client) + +cdef class MisagentError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + MISAGENT_E_SUCCESS: "Success", + MISAGENT_E_INVALID_ARG: "Invalid argument", + MISAGENT_E_PLIST_ERROR: "Property list error", + MISAGENT_E_CONN_FAILED: "Connection failed", + MISAGENT_E_REQUEST_FAILED: "Request failed", + MISAGENT_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class MisagentClient(PropertyListService): + __service_name__ = "com.apple.misagent" + cdef misagent_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(misagent_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef misagent_error_t err + if self._c_client is not NULL: + err = misagent_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return MisagentError(ret) + + cpdef install(self, plist.Node profile): + cdef misagent_error_t err + err = misagent_install(self._c_client, profile._c_node) + self.handle_error(err) + + cpdef plist.Node copy(self): + cdef: + plist.plist_t c_node = NULL + misagent_error_t err + err = misagent_copy(self._c_client, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef remove(self, bytes profile_id): + cdef misagent_error_t err + err = misagent_remove(self._c_client, profile_id) + self.handle_error(err) + + cpdef int get_status_code(self): + return misagent_get_status_code(self._c_client) diff --git a/cython/mobile_image_mounter.pxi b/cython/mobile_image_mounter.pxi new file mode 100644 index 0000000..d9d40d5 --- /dev/null +++ b/cython/mobile_image_mounter.pxi @@ -0,0 +1,115 @@ +cdef extern from "libimobiledevice/mobile_image_mounter.h": + cdef struct mobile_image_mounter_client_private: + pass + ctypedef mobile_image_mounter_client_private *mobile_image_mounter_client_t + + ctypedef enum mobile_image_mounter_error_t: + MOBILE_IMAGE_MOUNTER_E_SUCCESS = 0 + MOBILE_IMAGE_MOUNTER_E_INVALID_ARG = -1 + MOBILE_IMAGE_MOUNTER_E_PLIST_ERROR = -2 + MOBILE_IMAGE_MOUNTER_E_CONN_FAILED = -3 + MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR = -256 + + mobile_image_mounter_error_t mobile_image_mounter_new(idevice_t device, lockdownd_service_descriptor_t descriptor, mobile_image_mounter_client_t *client) + mobile_image_mounter_error_t mobile_image_mounter_free(mobile_image_mounter_client_t client) + mobile_image_mounter_error_t mobile_image_mounter_lookup_image(mobile_image_mounter_client_t client, char *image_type, plist.plist_t *result) + mobile_image_mounter_error_t mobile_image_mounter_mount_image_with_options(mobile_image_mounter_client_t client, char *image_path, const unsigned char *signature, unsigned int signature_length, char *image_type, plist.plist_t options, plist.plist_t *result) + mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, char *image_path, const unsigned char *signature, unsigned int signature_length, char *image_type, plist.plist_t *result) + mobile_image_mounter_error_t mobile_image_mounter_unmount_image(mobile_image_mounter_client_t client, const char *mount_path); + mobile_image_mounter_error_t mobile_image_mounter_hangup(mobile_image_mounter_client_t client) + +cdef class MobileImageMounterError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + MOBILE_IMAGE_MOUNTER_E_SUCCESS: "Success", + MOBILE_IMAGE_MOUNTER_E_INVALID_ARG: "Invalid argument", + MOBILE_IMAGE_MOUNTER_E_PLIST_ERROR: "Property list error", + MOBILE_IMAGE_MOUNTER_E_CONN_FAILED: "Connection failed", + MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class MobileImageMounterClient(PropertyListService): + __service_name__ = "com.apple.mobile.mobile_image_mounter" + cdef mobile_image_mounter_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(mobile_image_mounter_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef mobile_image_mounter_error_t err + if self._c_client is not NULL: + err = mobile_image_mounter_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return MobileImageMounterError(ret) + + cpdef plist.Node lookup_image(self, bytes image_type): + cdef: + plist.plist_t c_node = NULL + mobile_image_mounter_error_t err + err = mobile_image_mounter_lookup_image(self._c_client, image_type, &c_node) + + try: + self.handle_error(err) + + return plist.plist_t_to_node(c_node) + except Exception, e: + if c_node != NULL: + plist.plist_free(c_node) + + cpdef plist.Node mount_image_with_options(self, bytes image_path, bytes signature, bytes image_type, object options): + cdef: + plist.Node n_options + plist.plist_t c_options + plist.plist_t c_result = NULL + bint free_options = False + plist.plist_t c_node = NULL + mobile_image_mounter_error_t err + if isinstance(options, plist.Dict): + n_options = options + c_options = n_options._c_node + elif isinstance(options, dict): + c_options = plist.native_to_plist_t(options) + free_options = True + else: + raise InstallationProxyError(INSTPROXY_E_INVALID_ARG) + err = mobile_image_mounter_mount_image_with_options(self._c_client, image_path, signature, len(signature), + image_type, c_options, &c_node) + if free_options: + plist.plist_free(c_options) + try: + self.handle_error(err) + + return plist.plist_t_to_node(c_node) + except Exception, e: + if c_node != NULL: + plist.plist_free(c_node) + + cpdef plist.Node mount_image(self, bytes image_path, bytes signature, bytes image_type): + cdef: + plist.plist_t c_node = NULL + mobile_image_mounter_error_t err + err = mobile_image_mounter_mount_image(self._c_client, image_path, signature, len(signature), + image_type, &c_node) + + try: + self.handle_error(err) + + return plist.plist_t_to_node(c_node) + except Exception, e: + if c_node != NULL: + plist.plist_free(c_node) + + cpdef unmount_image(self, bytes mount_path): + cdef: + mobile_image_mounter_error_t err + err = mobile_image_mounter_unmount_image(self._c_client, mount_path) + + self.handle_error(err) + + cpdef hangup(self): + cdef mobile_image_mounter_error_t err + err = mobile_image_mounter_hangup(self._c_client) + self.handle_error(err) diff --git a/cython/mobilebackup.pxi b/cython/mobilebackup.pxi new file mode 100644 index 0000000..f2d58d4 --- /dev/null +++ b/cython/mobilebackup.pxi @@ -0,0 +1,110 @@ +cdef extern from "libimobiledevice/mobilebackup.h": + cdef struct mobilebackup_client_private: + pass + ctypedef mobilebackup_client_private *mobilebackup_client_t + + ctypedef enum mobilebackup_error_t: + MOBILEBACKUP_E_SUCCESS = 0 + MOBILEBACKUP_E_INVALID_ARG = -1 + MOBILEBACKUP_E_PLIST_ERROR = -2 + MOBILEBACKUP_E_MUX_ERROR = -3 + MOBILEBACKUP_E_SSL_ERROR = -4 + MOBILEBACKUP_E_RECEIVE_TIMEOUT = -5 + MOBILEBACKUP_E_BAD_VERSION = -6 + MOBILEBACKUP_E_REPLY_NOT_OK = -7 + MOBILEBACKUP_E_UNKNOWN_ERROR = -256 + + ctypedef enum mobilebackup_flags_t: + MB_RESTORE_NOTIFY_SPRINGBOARD = (1 << 0) + MB_RESTORE_PRESERVE_SETTINGS = (1 << 1) + MB_RESTORE_PRESERVE_CAMERA_ROLL = (1 << 2) + + mobilebackup_error_t mobilebackup_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, mobilebackup_client_t * client) + mobilebackup_error_t mobilebackup_client_free(mobilebackup_client_t client) + mobilebackup_error_t mobilebackup_receive(mobilebackup_client_t client, plist.plist_t *plist) + mobilebackup_error_t mobilebackup_send(mobilebackup_client_t client, plist.plist_t plist) + mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, plist.plist_t backup_manifest, char *base_path, char *proto_version) + mobilebackup_error_t mobilebackup_send_backup_file_received(mobilebackup_client_t client) + mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, plist.plist_t backup_manifest, mobilebackup_flags_t flags, char *proto_version) + mobilebackup_error_t mobilebackup_receive_restore_file_received(mobilebackup_client_t client, plist.plist_t *result) + mobilebackup_error_t mobilebackup_receive_restore_application_received(mobilebackup_client_t client, plist.plist_t *result) + mobilebackup_error_t mobilebackup_send_restore_complete(mobilebackup_client_t client) + mobilebackup_error_t mobilebackup_send_error(mobilebackup_client_t client, char *reason) + +cdef class MobileBackupError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + MOBILEBACKUP_E_SUCCESS: "Success", + MOBILEBACKUP_E_INVALID_ARG: "Invalid argument", + MOBILEBACKUP_E_PLIST_ERROR: "Property list error", + MOBILEBACKUP_E_MUX_ERROR: "MUX error", + MOBILEBACKUP_E_SSL_ERROR: "SSL error", + MOBILEBACKUP_E_RECEIVE_TIMEOUT: "Receive timeout", + MOBILEBACKUP_E_BAD_VERSION: "Bad version", + MOBILEBACKUP_E_REPLY_NOT_OK: "Reply not OK", + MOBILEBACKUP_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class MobileBackupClient(PropertyListService): + __service_name__ = "com.apple.mobilebackup" + cdef mobilebackup_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(mobilebackup_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef mobilebackup_error_t err + if self._c_client is not NULL: + err = mobilebackup_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return MobileBackupError(ret) + + cdef inline int16_t _send(self, plist.plist_t node): + return mobilebackup_send(self._c_client, node) + + cdef inline int16_t _receive(self, plist.plist_t* node): + return mobilebackup_receive(self._c_client, node) + + cdef request_backup(self, plist.Node backup_manifest, bytes base_path, bytes proto_version): + self.handle_error(mobilebackup_request_backup(self._c_client, backup_manifest._c_node, base_path, proto_version)) + + cdef send_backup_file_received(self): + self.handle_error(mobilebackup_send_backup_file_received(self._c_client)) + + cdef request_restore(self, plist.Node backup_manifest, int flags, proto_version): + self.handle_error(mobilebackup_request_restore(self._c_client, backup_manifest._c_node, <mobilebackup_flags_t>flags, proto_version)) + + cpdef plist.Node receive_restore_file_received(self): + cdef: + plist.plist_t c_node = NULL + mobilebackup_error_t err + err = mobilebackup_receive_restore_file_received(self._c_client, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef plist.Node receive_restore_application_received(self): + cdef: + plist.plist_t c_node = NULL + mobilebackup_error_t err + err = mobilebackup_receive_restore_application_received(self._c_client, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cdef send_restore_complete(self): + self.handle_error(mobilebackup_send_restore_complete(self._c_client)) + + cdef send_error(self, bytes reason): + self.handle_error(mobilebackup_send_error(self._c_client, reason)) diff --git a/cython/mobilebackup2.pxi b/cython/mobilebackup2.pxi new file mode 100644 index 0000000..4b47e5b --- /dev/null +++ b/cython/mobilebackup2.pxi @@ -0,0 +1,123 @@ +cdef extern from "libimobiledevice/mobilebackup2.h": + cdef struct mobilebackup2_client_private: + pass + ctypedef mobilebackup2_client_private *mobilebackup2_client_t + + ctypedef enum mobilebackup2_error_t: + MOBILEBACKUP2_E_SUCCESS = 0 + MOBILEBACKUP2_E_INVALID_ARG = -1 + MOBILEBACKUP2_E_PLIST_ERROR = -2 + MOBILEBACKUP2_E_MUX_ERROR = -3 + MOBILEBACKUP2_E_SSL_ERROR = -4 + MOBILEBACKUP2_E_RECEIVE_TIMEOUT = -5 + MOBILEBACKUP2_E_BAD_VERSION = -6 + MOBILEBACKUP2_E_REPLY_NOT_OK = -7 + MOBILEBACKUP2_E_NO_COMMON_VERSION = -8 + MOBILEBACKUP2_E_UNKNOWN_ERROR = -256 + + mobilebackup2_error_t mobilebackup2_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, mobilebackup2_client_t * client) + mobilebackup2_error_t mobilebackup2_client_free(mobilebackup2_client_t client) + + mobilebackup2_error_t mobilebackup2_send_message(mobilebackup2_client_t client, char *message, plist.plist_t options) + mobilebackup2_error_t mobilebackup2_receive_message(mobilebackup2_client_t client, plist.plist_t *msg_plist, char **dlmessage) + mobilebackup2_error_t mobilebackup2_send_raw(mobilebackup2_client_t client, char *data, uint32_t length, uint32_t *bytes) + mobilebackup2_error_t mobilebackup2_receive_raw(mobilebackup2_client_t client, char *data, uint32_t length, uint32_t *bytes) + mobilebackup2_error_t mobilebackup2_version_exchange(mobilebackup2_client_t client, double local_versions[], char count, double *remote_version) + mobilebackup2_error_t mobilebackup2_send_request(mobilebackup2_client_t client, char *request, char *target_identifier, char *source_identifier, plist.plist_t options) + mobilebackup2_error_t mobilebackup2_send_status_response(mobilebackup2_client_t client, int status_code, char *status1, plist.plist_t status2) + +cdef class MobileBackup2Error(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + MOBILEBACKUP2_E_SUCCESS: "Success", + MOBILEBACKUP2_E_INVALID_ARG: "Invalid argument", + MOBILEBACKUP2_E_PLIST_ERROR: "Property list error", + MOBILEBACKUP2_E_MUX_ERROR: "MUX error", + MOBILEBACKUP2_E_SSL_ERROR: "SSL error", + MOBILEBACKUP2_E_RECEIVE_TIMEOUT: "Receive timeout", + MOBILEBACKUP2_E_BAD_VERSION: "Bad version", + MOBILEBACKUP2_E_REPLY_NOT_OK: "Reply not OK", + MOBILEBACKUP2_E_NO_COMMON_VERSION: "No common version", + MOBILEBACKUP2_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class MobileBackup2Client(PropertyListService): + __service_name__ = "com.apple.mobilebackup2" + cdef mobilebackup2_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(mobilebackup2_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef mobilebackup2_error_t err + if self._c_client is not NULL: + err = mobilebackup2_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return MobileBackup2Error(ret) + + cpdef send_message(self, bytes message, plist.Node options): + self.handle_error(mobilebackup2_send_message(self._c_client, message, options._c_node)) + + cpdef tuple receive_message(self): + cdef: + char* dlmessage = NULL + plist.plist_t c_node = NULL + mobilebackup2_error_t err + err = mobilebackup2_receive_message(self._c_client, &c_node, &dlmessage) + try: + self.handle_error(err) + return (plist.plist_t_to_node(c_node), <bytes>dlmessage) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + if dlmessage != NULL: + free(dlmessage) + raise + + cpdef int send_raw(self, bytes data, int length): + cdef: + uint32_t bytes_recvd = 0 + mobilebackup2_error_t err + err = mobilebackup2_send_raw(self._c_client, data, length, &bytes_recvd) + try: + self.handle_error(err) + return <bint>bytes_recvd + except BaseError, e: + raise + + cpdef int receive_raw(self, bytearray data, int length): + cdef: + uint32_t bytes_recvd = 0 + mobilebackup2_error_t err + err = mobilebackup2_receive_raw(self._c_client, data, length, &bytes_recvd) + + # Throwing an exception when we test if theres more data to read is excessive + if err == -1 and bytes_recvd == 0: + return 0 + + try: + self.handle_error(err) + return <bint>bytes_recvd + except BaseError, e: + raise + + cpdef float version_exchange(self, double[::1] local_versions): + cdef: + double[::1] temp = None + double remote_version = 0.0 + mobilebackup2_error_t err + err = mobilebackup2_version_exchange(self._c_client, &local_versions[0], len(local_versions), &remote_version) + try: + self.handle_error(err) + return <float>remote_version + except BaseError, e: + raise + + cpdef send_request(self, bytes request, bytes target_identifier, bytes source_identifier, plist.Node options): + self.handle_error(mobilebackup2_send_request(self._c_client, request, target_identifier, source_identifier, options._c_node)) + + cpdef send_status_response(self, int status_code, bytes status1, plist.Node status2): + self.handle_error(mobilebackup2_send_status_response(self._c_client, status_code, status1, status2._c_node)) diff --git a/cython/mobilesync.pxi b/cython/mobilesync.pxi new file mode 100644 index 0000000..23f0005 --- /dev/null +++ b/cython/mobilesync.pxi @@ -0,0 +1,164 @@ +cdef extern from "libimobiledevice/mobilesync.h": + cdef struct mobilesync_client_private: + pass + ctypedef mobilesync_client_private *mobilesync_client_t + ctypedef enum mobilesync_error_t: + MOBILESYNC_E_SUCCESS = 0 + MOBILESYNC_E_INVALID_ARG = -1 + MOBILESYNC_E_PLIST_ERROR = -2 + MOBILESYNC_E_MUX_ERROR = -3 + MOBILESYNC_E_SSL_ERROR = -4 + MOBILESYNC_E_RECEIVE_TIMEOUT = -5 + MOBILESYNC_E_BAD_VERSION = -6 + MOBILESYNC_E_SYNC_REFUSED = -7 + MOBILESYNC_E_CANCELLED = -8 + MOBILESYNC_E_WRONG_DIRECTION = -9 + MOBILESYNC_E_NOT_READY = -10 + MOBILESYNC_E_UNKNOWN_ERROR = -256 + + ctypedef enum mobilesync_sync_type_t: + MOBILESYNC_SYNC_TYPE_FAST + MOBILESYNC_SYNC_TYPE_SLOW + MOBILESYNC_SYNC_TYPE_RESET + + ctypedef struct mobilesync_anchors: + char *device_anchor + char *host_anchor + ctypedef mobilesync_anchors *mobilesync_anchors_t + + mobilesync_error_t mobilesync_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilesync_client_t * client) + mobilesync_error_t mobilesync_client_free(mobilesync_client_t client) + mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist.plist_t *plist) + mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist.plist_t plist) + + mobilesync_error_t mobilesync_start(mobilesync_client_t client, char *data_class, mobilesync_anchors_t anchors, uint64_t computer_data_class_version, mobilesync_sync_type_t *sync_type, uint64_t *device_data_class_version, char** error_description) + mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, char* reason) + mobilesync_error_t mobilesync_finish(mobilesync_client_t client) + + mobilesync_error_t mobilesync_get_all_records_from_device(mobilesync_client_t client) + mobilesync_error_t mobilesync_get_changes_from_device(mobilesync_client_t client) + mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist.plist_t *entities, uint8_t *is_last_record, plist.plist_t *actions) + mobilesync_error_t mobilesync_acknowledge_changes_from_device(mobilesync_client_t client) + + mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_client_t client) + mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist.plist_t changes, uint8_t is_last_record, plist.plist_t actions) + mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plist.plist_t *mapping) + + mobilesync_anchors_t mobilesync_anchors_new(char *device_anchor, char *computer_anchor) + void mobilesync_anchors_free(mobilesync_anchors_t anchors) + + plist.plist_t mobilesync_actions_new() + void mobilesync_actions_add(plist.plist_t actions, ...) + void mobilesync_actions_free(plist.plist_t actions) + +SYNC_TYPE_FAST = MOBILESYNC_SYNC_TYPE_FAST +SYNC_TYPE_SLOW = MOBILESYNC_SYNC_TYPE_SLOW +SYNC_TYPE_RESET = MOBILESYNC_SYNC_TYPE_RESET + +cdef class MobileSyncError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + MOBILESYNC_E_SUCCESS: "Success", + MOBILESYNC_E_INVALID_ARG: "Invalid argument", + MOBILESYNC_E_PLIST_ERROR: "Property list error", + MOBILESYNC_E_MUX_ERROR: "MUX error", + MOBILESYNC_E_SSL_ERROR: "SSL error", + MOBILESYNC_E_RECEIVE_TIMEOUT: "Receive timeout", + MOBILESYNC_E_BAD_VERSION: "Bad version", + MOBILESYNC_E_SYNC_REFUSED: "Sync refused", + MOBILESYNC_E_CANCELLED: "Sync cancelled", + MOBILESYNC_E_WRONG_DIRECTION: "Wrong sync direction", + MOBILESYNC_E_NOT_READY: "Not ready to receive changes", + MOBILESYNC_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class MobileSyncClient(DeviceLinkService): + __service_name__ = "com.apple.mobilesync" + cdef mobilesync_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(mobilesync_client_new(device._c_dev, descriptor._c_service_descriptor, &(self._c_client))) + + def __dealloc__(self): + cdef mobilesync_error_t err + if self._c_client is not NULL: + err = mobilesync_client_free(self._c_client) + self.handle_error(err) + + cpdef tuple start(self, bytes data_class, bytes device_anchor, bytes host_anchor): + cdef: + mobilesync_anchors_t anchors = NULL + mobilesync_sync_type_t sync_type + uint64_t computer_data_class_version = 1 + uint64_t device_data_class_version + char* error_description = NULL + + if device_anchor is None: + anchors = mobilesync_anchors_new(NULL, host_anchor) + else: + anchors = mobilesync_anchors_new(device_anchor, host_anchor) + + try: + self.handle_error(mobilesync_start(self._c_client, data_class, anchors, computer_data_class_version, &sync_type, &device_data_class_version, &error_description)) + return (sync_type, <bint>computer_data_class_version, <bint>device_data_class_version, <bytes>error_description) + except Exception, e: + raise + finally: + mobilesync_anchors_free(anchors) + + cpdef finish(self): + self.handle_error(mobilesync_finish(self._c_client)) + + cpdef cancel(self, bytes reason): + self.handle_error(mobilesync_cancel(self._c_client, reason)) + + cpdef get_all_records_from_device(self): + self.handle_error(mobilesync_get_all_records_from_device(self._c_client)) + + cpdef get_changes_from_device(self): + self.handle_error(mobilesync_get_changes_from_device(self._c_client)) + + cpdef tuple receive_changes(self): + cdef: + plist.plist_t entities = NULL + uint8_t is_last_record = 0 + plist.plist_t actions = NULL + try: + self.handle_error(mobilesync_receive_changes(self._c_client, &entities, &is_last_record, &actions)) + return (plist.plist_t_to_node(entities), <bint>is_last_record, plist.plist_t_to_node(actions)) + except Exception, e: + if entities != NULL: + plist.plist_free(entities) + if actions != NULL: + plist.plist_free(actions) + raise + + cpdef acknowledge_changes_from_device(self): + self.handle_error(mobilesync_acknowledge_changes_from_device(self._c_client)) + + cpdef ready_to_send_changes_from_computer(self): + self.handle_error(mobilesync_ready_to_send_changes_from_computer(self._c_client)) + + cpdef send_changes(self, plist.Node changes, bint is_last_record, plist.Node actions): + self.handle_error(mobilesync_send_changes(self._c_client, changes._c_node, is_last_record, actions._c_node)) + + cpdef remap_identifiers(self): + cdef plist.plist_t remapping = NULL + + try: + self.handle_error(mobilesync_remap_identifiers(self._c_client, &remapping)) + return plist.plist_t_to_node(remapping) + except Exception, e: + if remapping != NULL: + plist.plist_free(remapping) + raise + + cdef int16_t _send(self, plist.plist_t node): + return mobilesync_send(self._c_client, node) + + cdef int16_t _receive(self, plist.plist_t* node): + return mobilesync_receive(self._c_client, node) + + cdef inline BaseError _error(self, int16_t ret): + return MobileSyncError(ret) diff --git a/cython/notification_proxy.pxi b/cython/notification_proxy.pxi new file mode 100644 index 0000000..261200e --- /dev/null +++ b/cython/notification_proxy.pxi @@ -0,0 +1,110 @@ +cdef extern from "libimobiledevice/notification_proxy.h": + cdef struct np_client_private: + pass + ctypedef np_client_private *np_client_t + ctypedef enum np_error_t: + NP_E_SUCCESS = 0 + NP_E_INVALID_ARG = -1 + NP_E_PLIST_ERROR = -2 + NP_E_CONN_FAILED = -3 + NP_E_UNKNOWN_ERROR = -256 + ctypedef void (*np_notify_cb_t) (const_char_ptr notification, void *userdata) + np_error_t np_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, np_client_t *client) + np_error_t np_client_free(np_client_t client) + np_error_t np_post_notification(np_client_t client, char *notification) + np_error_t np_observe_notification(np_client_t client, char *notification) + np_error_t np_observe_notifications(np_client_t client, char **notification_spec) + np_error_t np_set_notify_callback(np_client_t client, np_notify_cb_t notify_cb, void *userdata) + + cdef char* C_NP_SYNC_WILL_START "NP_SYNC_WILL_START" + cdef char* C_NP_SYNC_DID_START "NP_SYNC_DID_START" + cdef char* C_NP_SYNC_DID_FINISH "NP_SYNC_DID_FINISH" + cdef char* C_NP_SYNC_LOCK_REQUEST "NP_SYNC_LOCK_REQUEST" + + cdef char* C_NP_SYNC_CANCEL_REQUEST "NP_SYNC_CANCEL_REQUEST" + cdef char* C_NP_SYNC_SUSPEND_REQUEST "NP_SYNC_SUSPEND_REQUEST" + cdef char* C_NP_SYNC_RESUME_REQUEST "NP_SYNC_RESUME_REQUEST" + cdef char* C_NP_PHONE_NUMBER_CHANGED "NP_PHONE_NUMBER_CHANGED" + cdef char* C_NP_DEVICE_NAME_CHANGED "NP_DEVICE_NAME_CHANGED" + cdef char* C_NP_TIMEZONE_CHANGED "NP_TIMEZONE_CHANGED" + cdef char* C_NP_TRUSTED_HOST_ATTACHED "NP_TRUSTED_HOST_ATTACHED" + cdef char* C_NP_HOST_DETACHED "NP_HOST_DETACHED" + cdef char* C_NP_HOST_ATTACHED "NP_HOST_ATTACHED" + cdef char* C_NP_REGISTRATION_FAILED "NP_REGISTRATION_FAILED" + cdef char* C_NP_ACTIVATION_STATE "NP_ACTIVATION_STATE" + cdef char* C_NP_BRICK_STATE "NP_BRICK_STATE" + cdef char* C_NP_DS_DOMAIN_CHANGED "NP_DS_DOMAIN_CHANGED" + cdef char* C_NP_BACKUP_DOMAIN_CHANGED "NP_BACKUP_DOMAIN_CHANGED" + cdef char* C_NP_APP_INSTALLED "NP_APP_INSTALLED" + cdef char* C_NP_APP_UNINSTALLED "NP_APP_UNINSTALLED" + cdef char* C_NP_DEV_IMAGE_MOUNTED "NP_DEV_IMAGE_MOUNTED" + cdef char* C_NP_ATTEMPTACTIVATION "NP_ATTEMPTACTIVATION" + cdef char* C_NP_ITDBPREP_DID_END "NP_ITDBPREP_DID_END" + cdef char* C_NP_LANGUAGE_CHANGED "NP_LANGUAGE_CHANGED" + cdef char* C_NP_ADDRESS_BOOK_PREF_CHANGED "NP_ADDRESS_BOOK_PREF_CHANGED" + +NP_SYNC_WILL_START = C_NP_SYNC_WILL_START +NP_SYNC_DID_START = C_NP_SYNC_DID_START +NP_SYNC_DID_FINISH = C_NP_SYNC_DID_FINISH +NP_SYNC_LOCK_REQUEST = C_NP_SYNC_LOCK_REQUEST + +NP_SYNC_CANCEL_REQUEST = C_NP_SYNC_CANCEL_REQUEST +NP_SYNC_SUSPEND_REQUEST = C_NP_SYNC_SUSPEND_REQUEST +NP_SYNC_RESUME_REQUEST = C_NP_SYNC_RESUME_REQUEST +NP_PHONE_NUMBER_CHANGED = C_NP_PHONE_NUMBER_CHANGED +NP_DEVICE_NAME_CHANGED = C_NP_DEVICE_NAME_CHANGED +NP_TIMEZONE_CHANGED = C_NP_TIMEZONE_CHANGED +NP_TRUSTED_HOST_ATTACHED = C_NP_TRUSTED_HOST_ATTACHED +NP_HOST_DETACHED = C_NP_HOST_DETACHED +NP_HOST_ATTACHED = C_NP_HOST_ATTACHED +NP_REGISTRATION_FAILED = C_NP_REGISTRATION_FAILED +NP_ACTIVATION_STATE = C_NP_ACTIVATION_STATE +NP_BRICK_STATE = C_NP_BRICK_STATE +NP_DS_DOMAIN_CHANGED = C_NP_DS_DOMAIN_CHANGED +NP_BACKUP_DOMAIN_CHANGED = C_NP_BACKUP_DOMAIN_CHANGED +NP_APP_INSTALLED = C_NP_APP_INSTALLED +NP_APP_UNINSTALLED = C_NP_APP_UNINSTALLED +NP_DEV_IMAGE_MOUNTED = C_NP_DEV_IMAGE_MOUNTED +NP_ATTEMPTACTIVATION = C_NP_ATTEMPTACTIVATION +NP_ITDBPREP_DID_END = C_NP_ITDBPREP_DID_END +NP_LANGUAGE_CHANGED = C_NP_LANGUAGE_CHANGED +NP_ADDRESS_BOOK_PREF_CHANGED = C_NP_ADDRESS_BOOK_PREF_CHANGED + +cdef void np_notify_cb(const_char_ptr notification, void *py_callback) noexcept: + (<object>py_callback)(notification) + +cdef class NotificationProxyError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + NP_E_SUCCESS: "Success", + NP_E_INVALID_ARG: "Invalid argument", + NP_E_PLIST_ERROR: "Property list error", + NP_E_CONN_FAILED: "Connection failed", + NP_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class NotificationProxyClient(PropertyListService): + __service_name__ = "com.apple.mobile.notification_proxy" + cdef np_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(np_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef np_error_t err + if self._c_client is not NULL: + err = np_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return NotificationProxyError(ret) + + cpdef set_notify_callback(self, object callback): + self.handle_error(np_set_notify_callback(self._c_client, np_notify_cb, <void*>callback)) + + cpdef observe_notification(self, bytes notification): + self.handle_error(np_observe_notification(self._c_client, notification)) + + cpdef post_notification(self, bytes notification): + self.handle_error(np_post_notification(self._c_client, notification)) diff --git a/cython/restore.pxi b/cython/restore.pxi new file mode 100644 index 0000000..9d03935 --- /dev/null +++ b/cython/restore.pxi @@ -0,0 +1,131 @@ +cdef extern from "libimobiledevice/restore.h": + cdef struct restored_client_private: + pass + ctypedef restored_client_private *restored_client_t + + ctypedef enum restored_error_t: + RESTORE_E_SUCCESS = 0 + RESTORE_E_INVALID_ARG = -1 + RESTORE_E_PLIST_ERROR = -2 + RESTORE_E_MUX_ERROR = -3 + RESTORE_E_NOT_ENOUGH_DATA = -4 + RESTORE_E_RECEIVE_TIMEOUT = -5 + RESTORE_E_UNKNOWN_ERROR = -256 + + restored_error_t restored_client_new(idevice_t device, restored_client_t *client, char *label) + restored_error_t restored_client_free(restored_client_t client) + + restored_error_t restored_query_type(restored_client_t client, char **tp, uint64_t *version) + restored_error_t restored_query_value(restored_client_t client, char *key, plist.plist_t *value) + restored_error_t restored_get_value(restored_client_t client, char *key, plist.plist_t *value) + restored_error_t restored_send(restored_client_t client, plist.plist_t plist) + restored_error_t restored_receive(restored_client_t client, plist.plist_t *plist) + restored_error_t restored_goodbye(restored_client_t client) + + restored_error_t restored_start_restore(restored_client_t client, plist.plist_t options, uint64_t version) + restored_error_t restored_reboot(restored_client_t client) + + void restored_client_set_label(restored_client_t client, char *label) + +cdef class RestoreError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + RESTORE_E_SUCCESS: "Success", + RESTORE_E_INVALID_ARG: "Invalid argument", + RESTORE_E_PLIST_ERROR: "Property list error", + RESTORE_E_MUX_ERROR: "MUX Error", + RESTORE_E_NOT_ENOUGH_DATA: "Not enough data", + RESTORE_E_RECEIVE_TIMEOUT: "Receive timeout", + RESTORE_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class RestoreClient(PropertyListService): + cdef restored_client_t _c_client + + def __cinit__(self, iDevice device not None, bytes label=b'', *args, **kwargs): + cdef: + restored_error_t err + char* c_label = NULL + if label: + c_label = label + err = restored_client_new(device._c_dev, &self._c_client, c_label) + self.handle_error(err) + + self.device = device + + def __dealloc__(self): + cdef restored_error_t err + if self._c_client is not NULL: + err = restored_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return RestoreError(ret) + + cdef inline int16_t _send(self, plist.plist_t node): + return restored_send(self._c_client, node) + + cdef inline int16_t _receive(self, plist.plist_t* node): + return restored_receive(self._c_client, node) + + cpdef tuple query_type(self): + cdef: + restored_error_t err + char* c_type = NULL + uint64_t c_version = 0 + tuple result + err = restored_query_type(self._c_client, &c_type, &c_version) + try: + self.handle_error(err) + result = (c_type, c_version) + return result + except BaseError, e: + raise + finally: + if c_type != NULL: + free(c_type) + + cpdef plist.Node query_value(self, bytes key=None): + cdef: + restored_error_t err + plist.plist_t c_node = NULL + char* c_key = NULL + if key is not None: + c_key = key + err = restored_query_value(self._c_client, c_key, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef plist.Node get_value(self, bytes key=None): + cdef: + restored_error_t err + plist.plist_t c_node = NULL + char* c_key = NULL + if key is not None: + c_key = key + err = restored_get_value(self._c_client, c_key, &c_node) + try: + self.handle_error(err) + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + + cpdef goodbye(self): + self.handle_error(restored_goodbye(self._c_client)) + + cpdef start_restore(self, plist.Node options, uint64_t version): + self.handle_error(restored_start_restore(self._c_client, options._c_node, version)) + + cpdef reboot(self): + self.handle_error(restored_reboot(self._c_client)) + + cpdef set_label(self, bytes label): + restored_client_set_label(self._c_client, label) diff --git a/cython/sbservices.pxi b/cython/sbservices.pxi new file mode 100644 index 0000000..8ff2595 --- /dev/null +++ b/cython/sbservices.pxi @@ -0,0 +1,80 @@ +cdef extern from "libimobiledevice/sbservices.h": + cdef struct sbservices_client_private: + pass + ctypedef sbservices_client_private *sbservices_client_t + ctypedef enum sbservices_error_t: + SBSERVICES_E_SUCCESS = 0 + SBSERVICES_E_INVALID_ARG = -1 + SBSERVICES_E_PLIST_ERROR = -2 + SBSERVICES_E_CONN_FAILED = -3 + SBSERVICES_E_UNKNOWN_ERROR = -256 + sbservices_error_t sbservices_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, sbservices_client_t *client) + sbservices_error_t sbservices_client_free(sbservices_client_t client) + sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist.plist_t *state, char *format_version) + sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist.plist_t newstate) + sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, char *bundleId, char **pngdata, uint64_t *pngsize) + +cdef class SpringboardServicesError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + SBSERVICES_E_SUCCESS: "Success", + SBSERVICES_E_INVALID_ARG: "Invalid argument", + SBSERVICES_E_PLIST_ERROR: "Property list error", + SBSERVICES_E_CONN_FAILED: "Connection failed", + SBSERVICES_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class SpringboardServicesClient(PropertyListService): + __service_name__ = "com.apple.springboardservices" + cdef sbservices_client_t _c_client + cdef char* format_version + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(sbservices_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + self.format_version = "2" + + def __dealloc__(self): + if self._c_client is not NULL: + err = sbservices_client_free(self._c_client) + self.handle_error(err) + + cdef inline BaseError _error(self, int16_t ret): + return SpringboardServicesError(ret) + + property format_version: + def __get__(self): + return <bytes>self.format_version + def __set__(self, char* newversion): + self.format_version = newversion + + property icon_state: + def __get__(self): + cdef: + plist.plist_t c_node = NULL + sbservices_error_t err + err = sbservices_get_icon_state(self._c_client, &c_node, self.format_version) + try: + self.handle_error(err) + + return plist.plist_t_to_node(c_node) + except BaseError, e: + if c_node != NULL: + plist.plist_free(c_node) + raise + def __set__(self, plist.Node newstate not None): + self.handle_error(sbservices_set_icon_state(self._c_client, newstate._c_node)) + + cpdef bytes get_pngdata(self, bytes bundleId): + cdef: + char* pngdata = NULL + uint64_t pngsize + sbservices_error_t err + err = sbservices_get_icon_pngdata(self._c_client, bundleId, &pngdata, &pngsize) + try: + self.handle_error(err) + + return pngdata[:pngsize] + except BaseError, e: + free(pngdata) + raise diff --git a/cython/screenshotr.pxi b/cython/screenshotr.pxi new file mode 100644 index 0000000..a1e82e2 --- /dev/null +++ b/cython/screenshotr.pxi @@ -0,0 +1,66 @@ +cdef extern from "libimobiledevice/screenshotr.h": + cdef struct screenshotr_client_private: + pass + ctypedef screenshotr_client_private *screenshotr_client_t + + ctypedef enum screenshotr_error_t: + SCREENSHOTR_E_SUCCESS = 0 + SCREENSHOTR_E_INVALID_ARG = -1 + SCREENSHOTR_E_PLIST_ERROR = -2 + SCREENSHOTR_E_MUX_ERROR = -3 + SCREENSHOTR_E_SSL_ERROR = -4 + SCREENSHOTR_E_RECEIVE_TIMEOUT = 5 + SCREENSHOTR_E_BAD_VERSION = -6 + SCREENSHOTR_E_UNKNOWN_ERROR = -256 + + screenshotr_error_t screenshotr_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, screenshotr_client_t * client) + screenshotr_error_t screenshotr_client_free(screenshotr_client_t client) + screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, char **imgdata, uint64_t *imgsize) + +cdef class ScreenshotrError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + SCREENSHOTR_E_SUCCESS: "Success", + SCREENSHOTR_E_INVALID_ARG: "Invalid argument", + SCREENSHOTR_E_PLIST_ERROR: "Property list error", + SCREENSHOTR_E_MUX_ERROR: "MUX error", + SCREENSHOTR_E_SSL_ERROR: "SSL error", + SCREENSHOTR_E_RECEIVE_TIMEOUT: "Receive timeout", + SCREENSHOTR_E_BAD_VERSION: "Bad version", + SCREENSHOTR_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class ScreenshotrClient(DeviceLinkService): + __service_name__ = "com.apple.mobile.screenshotr" + cdef screenshotr_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(screenshotr_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef screenshotr_error_t err + if self._c_client is not NULL: + err = screenshotr_client_free(self._c_client) + self.handle_error(err) + + cpdef bytes take_screenshot(self): + cdef: + char* c_data = NULL + uint64_t data_size + bytes result + screenshotr_error_t err + + err = screenshotr_take_screenshot(self._c_client, &c_data, &data_size) + try: + self.handle_error(err) + + result = c_data[:data_size] + return result + except Exception, e: + if c_data != NULL: + free(c_data) + raise + + cdef inline BaseError _error(self, int16_t ret): + return ScreenshotrError(ret) diff --git a/cython/webinspector.pxi b/cython/webinspector.pxi new file mode 100644 index 0000000..f77547d --- /dev/null +++ b/cython/webinspector.pxi @@ -0,0 +1,60 @@ +cdef extern from "libimobiledevice/webinspector.h": + cdef struct webinspector_client_private: + pass + ctypedef webinspector_client_private *webinspector_client_t + + ctypedef enum webinspector_error_t: + WEBINSPECTOR_E_SUCCESS = 0 + WEBINSPECTOR_E_INVALID_ARG = -1 + WEBINSPECTOR_E_PLIST_ERROR = -2 + WEBINSPECTOR_E_MUX_ERROR = -3 + WEBINSPECTOR_E_SSL_ERROR = -4 + WEBINSPECTOR_E_RECEIVE_TIMEOUT = -5, + WEBINSPECTOR_E_NOT_ENOUGH_DATA = -6, + WEBINSPECTOR_E_UNKNOWN_ERROR = -256 + + webinspector_error_t webinspector_client_new(idevice_t device, lockdownd_service_descriptor_t descriptor, webinspector_client_t * client) + webinspector_error_t webinspector_client_free(webinspector_client_t client) + + webinspector_error_t webinspector_send(webinspector_client_t client, plist.plist_t plist) + webinspector_error_t webinspector_receive(webinspector_client_t client, plist.plist_t * plist) + webinspector_error_t webinspector_receive_with_timeout(webinspector_client_t client, plist.plist_t * plist, uint32_t timeout_ms) + +cdef class WebinspectorError(BaseError): + def __init__(self, *args, **kwargs): + self._lookup_table = { + WEBINSPECTOR_E_SUCCESS: "Success", + WEBINSPECTOR_E_INVALID_ARG: "Invalid argument", + WEBINSPECTOR_E_PLIST_ERROR: "Property list error", + WEBINSPECTOR_E_MUX_ERROR: "MUX error", + WEBINSPECTOR_E_SSL_ERROR: "SSL Error", + WEBINSPECTOR_E_NOT_ENOUGH_DATA: 'Not enough data', + WEBINSPECTOR_E_RECEIVE_TIMEOUT: 'Connection timeout', + WEBINSPECTOR_E_UNKNOWN_ERROR: "Unknown error" + } + BaseError.__init__(self, *args, **kwargs) + +cdef class WebinspectorClient(PropertyListService): + __service_name__ = "com.apple.webinspector" + cdef webinspector_client_t _c_client + + def __cinit__(self, iDevice device not None, LockdownServiceDescriptor descriptor, *args, **kwargs): + self.handle_error(webinspector_client_new(device._c_dev, descriptor._c_service_descriptor, &self._c_client)) + + def __dealloc__(self): + cdef webinspector_error_t err + if self._c_client is not NULL: + err = webinspector_client_free(self._c_client) + self.handle_error(err) + + cdef inline int16_t _send(self, plist.plist_t node): + return webinspector_send(self._c_client, node) + + cdef inline int16_t _receive(self, plist.plist_t* node): + return webinspector_receive(self._c_client, node) + + cdef inline int16_t _receive_with_timeout(self, plist.plist_t* node, int timeout_ms): + return webinspector_receive_with_timeout(self._c_client, node, timeout_ms) + + cdef inline BaseError _error(self, int16_t ret): + return WebinspectorError(ret) diff --git a/dev/Makefile.am b/dev/Makefile.am deleted file mode 100644 index 72c00a3..0000000 --- a/dev/Makefile.am +++ /dev/null @@ -1,36 +0,0 @@ -AM_CPPFLAGS = -I$(top_srcdir)/include - -AM_CFLAGS = $(GLOBAL_CFLAGS) $(libglib2_CFLAGS) $(libgnutls_CFLAGS) $(libtasn1_CFLAGS) $(libgthread2_CFLAGS) $(LFS_CFLAGS) -AM_LDFLAGS = $(libglib2_LIBS) $(libgnutls_LIBS) $(libtasn1_LIBS) $(libgthread2_LIBS) - -if ENABLE_DEVTOOLS -noinst_PROGRAMS = ideviceclient lckd-client afccheck filerelaytest housearresttest - -ideviceclient_SOURCES = ideviceclient.c -ideviceclient_CFLAGS = $(AM_CFLAGS) -ideviceclient_LDFLAGS = $(AM_LDFLAGS) -ideviceclient_LDADD = ../src/libimobiledevice.la - -lckd_client_SOURCES = lckdclient.c -lckd_client_CFLAGS = $(AM_CFLAGS) -lckd_client_LDFLAGS = -lreadline $(AM_LDFLAGS) -lckd_client_LDADD = ../src/libimobiledevice.la - -afccheck_SOURCES = afccheck.c -afccheck_CFLAGS = $(AM_CFLAGS) -afccheck_LDFLAGS = $(AM_LDFLAGS) -afccheck_LDADD = ../src/libimobiledevice.la - -filerelaytest_SOURCES = filerelaytest.c -filerelaytest_CFLAGS = $(AM_CFLAGS) -filerelaytest_LDFLAGS = $(AM_LDFLAGS) -filerelaytest_LDADD = ../src/libimobiledevice.la - -housearresttest_SOURCES = housearresttest.c -housearresttest_CFLAGS = $(AM_CFLAGS) -housearresttest_LDFLAGS = $(AM_LDFLAGS) -housearresttest_LDADD = ../src/libimobiledevice.la - -endif # ENABLE_DEVTOOLS - -EXTRA_DIST = ideviceclient.c lckdclient.c afccheck.c filerelaytest.c housearresttest.c diff --git a/dev/afccheck.c b/dev/afccheck.c deleted file mode 100644 index b4d8910..0000000 --- a/dev/afccheck.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * afccheck.c - * creates threads and check communication through AFC is done rigth - * - * Copyright (c) 2008 Jonathan Beck All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <glib.h> - -#include <libimobiledevice/libimobiledevice.h> -#include <libimobiledevice/lockdown.h> -#include <libimobiledevice/afc.h> - -#define BUFFER_SIZE 20000 -#define NB_THREADS 10 - - -typedef struct { - afc_client_t afc; - int id; -} param; - - -static void check_afc(gpointer data) -{ - //prepare a buffer - unsigned int buffersize = BUFFER_SIZE * sizeof(unsigned int); - int *buf = (int *) malloc(buffersize); - int *buf2 = (int *) malloc(buffersize); - unsigned int bytes = 0; - uint64_t position = 0; - - //fill buffer - int i = 0; - for (i = 0; i < BUFFER_SIZE; i++) { - buf[i] = ((param *) data)->id * i; - } - - //now writes buffer on device - uint64_t file = 0; - char path[50]; - sprintf(path, "/Buf%i", ((param *) data)->id); - afc_file_open(((param *) data)->afc, path, AFC_FOPEN_RW, &file); - afc_file_write(((param *) data)->afc, file, (char *) buf, buffersize, &bytes); - afc_file_close(((param *) data)->afc, file); - file = 0; - if (bytes != buffersize) - printf("Write operation failed\n"); - - //now read it - bytes = 0; - afc_file_open(((param *) data)->afc, path, AFC_FOPEN_RDONLY, &file); - afc_file_read(((param *) data)->afc, file, (char *) buf2, buffersize/2, &bytes); - afc_file_read(((param *) data)->afc, file, (char *) buf2 + (buffersize/2), buffersize/2, &bytes); - if(AFC_E_SUCCESS != afc_file_tell(((param *) data)->afc, file, &position)) - printf("Tell operation failed\n"); - afc_file_close(((param *) data)->afc, file); - if (position != buffersize) - printf("Read operation failed\n"); - - //compare buffers - for (i = 0; i < BUFFER_SIZE; i++) { - if (buf[i] != buf2[i]) { - printf("Buffers are differents, stream corrupted\n"); - break; - } - } - - //cleanup - afc_remove_path(((param *) data)->afc, path); - g_thread_exit(0); -} - -int main(int argc, char *argv[]) -{ - lockdownd_client_t client = NULL; - idevice_t phone = NULL; - GError *err; - uint16_t port = 0; - afc_client_t afc = NULL; - - if (argc > 1 && !strcasecmp(argv[1], "--debug")) { - idevice_set_debug_level(1); - } else { - idevice_set_debug_level(0); - } - - if (IDEVICE_E_SUCCESS != idevice_new(&phone, NULL)) { - printf("No device found, is it plugged in?\n"); - return 1; - } - - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "afccheck")) { - idevice_free(phone); - return 1; - } - - if (LOCKDOWN_E_SUCCESS == lockdownd_start_service(client, "com.apple.afc", &port) && !port) { - lockdownd_client_free(client); - idevice_free(phone); - fprintf(stderr, "Something went wrong when starting AFC."); - return 1; - } - - afc_client_new(phone, port, &afc); - - //makes sure thread environment is available - if (!g_thread_supported()) - g_thread_init(NULL); - - GThread *threads[NB_THREADS]; - param data[NB_THREADS]; - - int i = 0; - for (i = 0; i < NB_THREADS; i++) { - data[i].afc = afc; - data[i].id = i + 1; - threads[i] = g_thread_create((GThreadFunc) check_afc, data + i, TRUE, &err); - } - - for (i = 0; i < NB_THREADS; i++) { - g_thread_join(threads[i]); - } - - lockdownd_client_free(client); - idevice_free(phone); - - return 0; -} diff --git a/dev/filerelaytest.c b/dev/filerelaytest.c deleted file mode 100644 index 6e611c0..0000000 --- a/dev/filerelaytest.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * filerelaytest.c - * Simple Test program showing the usage of the file_relay interface. - * - * Copyright (c) 2010 Nikias Bassen All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ -#include <stdio.h> -#include <libimobiledevice/libimobiledevice.h> -#include <libimobiledevice/lockdown.h> -#include <libimobiledevice/file_relay.h> - -int main(int argc, char **argv) -{ - idevice_t dev = NULL; - lockdownd_client_t client = NULL; - file_relay_client_t frc = NULL; - - if (idevice_new(&dev, NULL) != IDEVICE_E_SUCCESS) { - printf("no device connected?!\n"); - goto leave_cleanup; - } - - printf("connecting...\n"); - if (lockdownd_client_new_with_handshake(dev, &client, NULL) != LOCKDOWN_E_SUCCESS) { - printf("could not connect to lockdownd!\n"); - goto leave_cleanup; - } - - uint16_t port = 0; - if (lockdownd_start_service(client, "com.apple.mobile.file_relay", &port) != LOCKDOWN_E_SUCCESS) { - printf("could not start file_relay service!\n"); - goto leave_cleanup; - } - - if (client) { - lockdownd_client_free(client); - client = NULL; - } - - if (file_relay_client_new(dev, port, &frc) != FILE_RELAY_E_SUCCESS) { - printf("could not connect to file_relay service!\n"); - goto leave_cleanup; - } - - idevice_connection_t dump = NULL; - const char *sources[] = {"AppleSupport", "Network", "VPN", "WiFi", "UserDatabases", "CrashReporter", "tmp", "SystemConfiguration", NULL}; - - printf("Requesting"); - int i = 0; - while (sources[i]) { - printf(" %s", sources[i]); - i++; - } - printf("\n"); - - if (file_relay_request_sources(frc, sources, &dump) != FILE_RELAY_E_SUCCESS) { - printf("could not get sources\n"); - goto leave_cleanup; - } - - if (!dump) { - printf("did not get connection!\n"); - goto leave_cleanup; - } - - uint32_t cnt = 0; - uint32_t len = 0; - char buf[4096]; - FILE *f = fopen("dump.cpio.gz", "w"); - setbuf(stdout, NULL); - printf("receiving "); - while (idevice_connection_receive(dump, buf, 4096, &len) == IDEVICE_E_SUCCESS) { - fwrite(buf, 1, len, f); - cnt += len; - printf("."); - len = 0; - } - printf("\n"); - fclose(f); - printf("total size received: %d\n", cnt); - -leave_cleanup: - if (frc) { - file_relay_client_free(frc); - } - if (client) { - lockdownd_client_free(client); - } - if (dev) { - idevice_free(dev); - } - - return 0; -} diff --git a/dev/housearresttest.c b/dev/housearresttest.c deleted file mode 100644 index 7282a8c..0000000 --- a/dev/housearresttest.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * housearresttest.c - * Simple Test program showing the usage of the house_arrest interface. - * - * Copyright (c) 2010 Nikias Bassen All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <libimobiledevice/libimobiledevice.h> -#include <libimobiledevice/lockdown.h> -#include <libimobiledevice/house_arrest.h> -#include <libimobiledevice/afc.h> - -static void print_usage(int argc, char **argv) -{ - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS] APPID\n", (name ? name + 1: argv[0])); - printf("Test the house_arrest service.\n\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -t, --test\t\ttest creating, writing, and deleting a file\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); -} - -int main(int argc, char **argv) -{ - idevice_t dev = NULL; - lockdownd_client_t client = NULL; - house_arrest_client_t hac = NULL; - house_arrest_error_t res; - int i; - char *uuid = NULL; - const char *appid = NULL; - int test_file_io = 0; - - /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { - idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { - i++; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; - } - uuid = strdup(argv[i]); - continue; - } - else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--test")) { - test_file_io = 1; - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); - return 0; - } - else { - appid = argv[i]; - break; - } - } - - if (!appid) { - print_usage(argc, argv); - return 0; - } - - if (idevice_new(&dev, uuid) != IDEVICE_E_SUCCESS) { - printf("no device connected?!\n"); - goto leave_cleanup; - } - - if (lockdownd_client_new_with_handshake(dev, &client, NULL) != LOCKDOWN_E_SUCCESS) { - printf("could not connect to lockdownd!\n"); - goto leave_cleanup; - } - - uint16_t port = 0; - if (lockdownd_start_service(client, "com.apple.mobile.house_arrest", &port) != LOCKDOWN_E_SUCCESS) { - printf("could not start house_arrest service!\n"); - goto leave_cleanup; - } - - if (client) { - lockdownd_client_free(client); - client = NULL; - } - - if (house_arrest_client_new(dev, port, &hac) != HOUSE_ARREST_E_SUCCESS) { - printf("could not connect to house_arrest service!\n"); - goto leave_cleanup; - } - - res = house_arrest_send_command(hac, "VendDocuments", appid); - if (res != HOUSE_ARREST_E_SUCCESS) { - printf("error %d when trying to get VendDocuments\n", res); - goto leave_cleanup; - } - - plist_t dict = NULL; - if (house_arrest_get_result(hac, &dict) != HOUSE_ARREST_E_SUCCESS) { - if (house_arrest_get_result(hac, &dict) != HOUSE_ARREST_E_SUCCESS) { - printf("hmmm....\n"); - goto leave_cleanup; - } - } - - plist_t node = plist_dict_get_item(dict, "Error"); - if (node) { - char *str = NULL; - plist_get_string_val(node, &str); - printf("Error: %s\n", str); - if (str) free(str); - plist_free(dict); - dict = NULL; - goto leave_cleanup; - } - node = plist_dict_get_item(dict, "Status"); - if (node) { - char *str = NULL; - plist_get_string_val(node, &str); - if (str && (strcmp(str, "Complete") != 0)) { - printf("Warning: Status is not 'Complete' but '%s'\n", str); - } - if (str) free(str); - plist_free(dict); - dict = NULL; - } - if (dict) { - plist_free(dict); - } - - afc_client_t afc = NULL; - afc_error_t ae = afc_client_new_from_house_arrest_client(hac, &afc); - if (ae != AFC_E_SUCCESS) { - printf("afc error %d\n", ae); - } - if (ae == AFC_E_SUCCESS) { - char **list = NULL; - afc_read_directory(afc, "/", &list); - printf("Directory contents:\n"); - if (list) { - while (list[0]) { - if (strcmp(list[0], ".") && strcmp(list[0], "..")) { - puts(list[0]); - } - list++; - } - } - - if (test_file_io) { - uint64_t tf = 0; - printf("\n==== Performing file tests ====\n"); - printf("Opening file 'foobar' for writing: "); - if (afc_file_open(afc, "/foobar", AFC_FOPEN_RW, &tf) == AFC_E_SUCCESS) { - uint32_t wb = 0; - printf("OK\n"); - - printf("Writing to file: "); - if (afc_file_write(afc, tf, "test\r\n", 6, &wb) != AFC_E_SUCCESS) { - printf("ERROR\n"); - } else { - printf("OK\n"); - } - afc_file_close(afc, tf); - printf("Deleting file 'foobar': "); - if (afc_remove_path(afc, "/foobar") == AFC_E_SUCCESS) { - printf("OK\n"); - } else { - printf("ERROR\n"); - } - } else { - printf("ERROR\n"); - } - } - afc_client_free(afc); - } else { - printf("failed to connect to afc service, error %d\n", ae); - } - -leave_cleanup: - if (hac) { - house_arrest_client_free(hac); - } - if (client) { - lockdownd_client_free(client); - } - if (dev) { - idevice_free(dev); - } - - return 0; -} diff --git a/dev/ideviceclient.c b/dev/ideviceclient.c deleted file mode 100644 index d952594..0000000 --- a/dev/ideviceclient.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * main.c - * Test program for testing several services. - * - * Copyright (c) 2008 Zach C. All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <glib.h> - -#include <libimobiledevice/libimobiledevice.h> -#include <libimobiledevice/lockdown.h> -#include <libimobiledevice/afc.h> -#include <libimobiledevice/notification_proxy.h> - -static void notifier(const char *notification, void *userdata) -{ - printf("---------------------------------------------------------\n"); - printf("------> Notification received: %s\n", notification); - printf("---------------------------------------------------------\n"); -} - -static void perform_notification(idevice_t phone, lockdownd_client_t client, const char *notification) -{ - uint16_t nport = 0; - np_client_t np; - - lockdownd_start_service(client, "com.apple.mobile.notification_proxy", &nport); - if (nport) { - printf("::::::::::::::: np was started ::::::::::::\n"); - np_client_new(phone, nport, &np); - if (np) { - printf("::::::::: PostNotification %s\n", notification); - np_post_notification(np, notification); - np_client_free(np); - } - } else { - printf("::::::::::::::: np was NOT started ::::::::::::\n"); - } -} - -int main(int argc, char *argv[]) -{ - unsigned int bytes = 0; - uint16_t port = 0, i = 0; - uint16_t npp; - lockdownd_client_t client = NULL; - idevice_t phone = NULL; - uint64_t lockfile = 0; - np_client_t gnp = NULL; - - if (argc > 1 && !strcasecmp(argv[1], "--debug")) { - idevice_set_debug_level(1); - } else { - idevice_set_debug_level(0); - } - - if (IDEVICE_E_SUCCESS != idevice_new(&phone, NULL)) { - printf("No device found, is it plugged in?\n"); - return -1; - } - - char *uuid = NULL; - if (IDEVICE_E_SUCCESS == idevice_get_uuid(phone, &uuid)) { - printf("DeviceUniqueID : %s\n", uuid); - } - if (uuid) - free(uuid); - - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceclient")) { - idevice_free(phone); - printf("Exiting.\n"); - return -1; - } - - char *nnn = NULL; - if (LOCKDOWN_E_SUCCESS == lockdownd_get_device_name(client, &nnn)) { - printf("DeviceName : %s\n", nnn); - free(nnn); - } - - lockdownd_start_service(client, "com.apple.afc", &port); - - if (port) { - afc_client_t afc = NULL; - afc_client_new(phone, port, &afc); - if (afc) { - lockdownd_start_service(client, "com.apple.mobile.notification_proxy", &npp); - if (npp) { - printf("Notification Proxy started.\n"); - np_client_new(phone, npp, &gnp); - } else { - printf("ERROR: Notification proxy could not be started.\n"); - } - if (gnp) { - const char *nspec[5] = { - NP_SYNC_CANCEL_REQUEST, - NP_SYNC_SUSPEND_REQUEST, - NP_SYNC_RESUME_REQUEST, - NP_ITDBPREP_DID_END, - NULL - }; - np_observe_notifications(gnp, nspec); - np_set_notify_callback(gnp, notifier, NULL); - } - - perform_notification(phone, client, NP_SYNC_WILL_START); - - afc_file_open(afc, "/com.apple.itunes.lock_sync", AFC_FOPEN_RW, &lockfile); - if (lockfile) { - printf("locking file\n"); - afc_file_lock(afc, lockfile, AFC_LOCK_EX); - - perform_notification(phone, client, NP_SYNC_DID_START); - } - - char **dirs = NULL; - afc_read_directory(afc, "/eafaedf", &dirs); - if (!dirs) - afc_read_directory(afc, "/", &dirs); - printf("Directory time.\n"); - for (i = 0; dirs[i]; i++) { - printf("/%s\n", dirs[i]); - } - - g_strfreev(dirs); - - dirs = NULL; - afc_get_device_info(afc, &dirs); - if (dirs) { - for (i = 0; dirs[i]; i += 2) { - printf("%s: %s\n", dirs[i], dirs[i + 1]); - } - } - g_strfreev(dirs); - - uint64_t my_file = 0; - char **info = NULL; - uint64_t fsize = 0; - if (AFC_E_SUCCESS == afc_get_file_info(afc, "/readme.libimobiledevice.fx", &info) && info) { - for (i = 0; info[i]; i += 2) { - printf("%s: %s\n", info[i], info[i+1]); - if (!strcmp(info[i], "st_size")) { - fsize = atoll(info[i+1]); - } - } - } - - if (AFC_E_SUCCESS == - afc_file_open(afc, "/readme.libimobiledevice.fx", AFC_FOPEN_RDONLY, &my_file) && my_file) { - printf("A file size: %llu\n", (long long)fsize); - char *file_data = (char *) malloc(sizeof(char) * fsize); - afc_file_read(afc, my_file, file_data, fsize, &bytes); - if (bytes > 0) { - printf("The file's data:\n"); - fwrite(file_data, 1, bytes, stdout); - } - printf("\nClosing my file.\n"); - afc_file_close(afc, my_file); - free(file_data); - } else - printf("couldn't open a file\n"); - - afc_file_open(afc, "/readme.libimobiledevice.fx", AFC_FOPEN_WR, &my_file); - if (my_file) { - char *outdatafile = strdup("this is a bitchin text file\n"); - afc_file_write(afc, my_file, outdatafile, strlen(outdatafile), &bytes); - free(outdatafile); - if (bytes > 0) - printf("Wrote a surprise. ;)\n"); - else - printf("I wanted to write a surprise, but... :(\n"); - afc_file_close(afc, my_file); - } - printf("Deleting a file...\n"); - bytes = afc_remove_path(afc, "/delme"); - if (bytes) - printf("Success.\n"); - else - printf("Failure. (expected unless you have a /delme file on your phone)\n"); - - printf("Renaming a file...\n"); - bytes = afc_rename_path(afc, "/renme", "/renme2"); - if (bytes > 0) - printf("Success.\n"); - else - printf("Failure. (expected unless you have a /renme file on your phone)\n"); - - printf("Seek & read\n"); - afc_file_open(afc, "/readme.libimobiledevice.fx", AFC_FOPEN_RDONLY, &my_file); - if (AFC_E_SUCCESS != afc_file_seek(afc, my_file, 5, SEEK_CUR)) - printf("WARN: SEEK DID NOT WORK\n"); - char *threeletterword = (char *) malloc(sizeof(char) * 5); - afc_file_read(afc, my_file, threeletterword, 3, &bytes); - threeletterword[3] = '\0'; - if (bytes > 0) - printf("Result: %s\n", threeletterword); - else - printf("Couldn't read!\n"); - free(threeletterword); - afc_file_close(afc, my_file); - } - - if (gnp && lockfile) { - printf("XXX sleeping\n"); - sleep(5); - - printf("XXX unlocking file\n"); - afc_file_lock(afc, lockfile, AFC_LOCK_UN); - - printf("XXX closing file\n"); - afc_file_close(afc, lockfile); - - printf("XXX sleeping\n"); - sleep(5); - //perform_notification(phone, client, NP_SYNC_DID_FINISH); - } - - if (gnp) { - np_client_free(gnp); - gnp = NULL; - } - - afc_client_free(afc); - } else { - printf("Start service failure.\n"); - } - - printf("All done.\n"); - - lockdownd_client_free(client); - idevice_free(phone); - - return 0; -} diff --git a/dev/lckdclient.c b/dev/lckdclient.c deleted file mode 100644 index 773de9f..0000000 --- a/dev/lckdclient.c +++ /dev/null @@ -1,115 +0,0 @@ -/* - * lckdclient.c - * Rudimentary command line interface to the Lockdown protocol - * - * Copyright (c) 2008 Jonathan Beck All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <glib.h> -#include <readline/readline.h> -#include <readline/history.h> - -#include <libimobiledevice/libimobiledevice.h> -#include <libimobiledevice/lockdown.h> - -int main(int argc, char *argv[]) -{ - lockdownd_client_t client = NULL; - idevice_t phone = NULL; - - idevice_set_debug_level(1); - - if (IDEVICE_E_SUCCESS != idevice_new(&phone, NULL)) { - printf("No device found, is it plugged in?\n"); - return -1; - } - - char *uuid = NULL; - if (IDEVICE_E_SUCCESS == idevice_get_uuid(phone, &uuid)) { - printf("DeviceUniqueID : %s\n", uuid); - } - if (uuid) - free(uuid); - - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "lckdclient")) { - idevice_free(phone); - return -1; - } - - using_history(); - int loop = TRUE; - while (loop) { - char *cmd = readline("> "); - if (cmd) { - - gchar **args = g_strsplit(cmd, " ", 0); - - int len = 0; - if (args) { - while (*(args + len)) { - g_strstrip(*(args + len)); - len++; - } - } - - if (len > 0) { - add_history(cmd); - if (!strcmp(*args, "quit")) - loop = FALSE; - - if (!strcmp(*args, "get") && len >= 2) { - plist_t value = NULL; - if (LOCKDOWN_E_SUCCESS == lockdownd_get_value(client, len == 3 ? *(args + 1):NULL, len == 3 ? *(args + 2):*(args + 1), &value)) - { - char *xml = NULL; - uint32_t length; - plist_to_xml(value, &xml, &length); - printf("Success : value = %s\n", xml); - free(xml); - } - else - printf("Error\n"); - - if (value) - plist_free(value); - } - - if (!strcmp(*args, "start") && len == 2) { - uint16_t port = 0; - if(LOCKDOWN_E_SUCCESS == lockdownd_start_service(client, *(args + 1), &port)) { - printf("started service %s on port %i\n", *(args + 1), port); - } - else - { - printf("failed to start service %s on device.\n", *(args + 1)); - } - } - } - g_strfreev(args); - } - free(cmd); - cmd = NULL; - } - clear_history(); - lockdownd_client_free(client); - idevice_free(phone); - - return 0; -} diff --git a/docs/Makefile.am b/docs/Makefile.am index 69ddb58..8156d4f 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -1,4 +1,25 @@ -man_MANS = idevice_id.1 ideviceinfo.1 idevicesyslog.1 idevicebackup.1 ideviceimagemounter.1 idevicescreenshot.1 idevicepair.1 ideviceenterrecovery.1 idevicedate.1 +man_MANS = \ + idevice_id.1 \ + ideviceinfo.1 \ + idevicebtlogger.1 \ + idevicesyslog.1 \ + idevicebackup.1 \ + idevicebackup2.1 \ + ideviceimagemounter.1 \ + idevicescreenshot.1 \ + idevicepair.1 \ + ideviceenterrecovery.1 \ + idevicedate.1 \ + ideviceprovision.1 \ + idevicedebugserverproxy.1 \ + idevicediagnostics.1 \ + idevicecrashreport.1 \ + idevicename.1 \ + idevicedebug.1 \ + idevicedevmodectl.1 \ + idevicenotificationproxy.1 \ + idevicesetlocation.1 \ + afcclient.1 EXTRA_DIST = $(man_MANS) diff --git a/docs/afcclient.1 b/docs/afcclient.1 new file mode 100644 index 0000000..a4eeacb --- /dev/null +++ b/docs/afcclient.1 @@ -0,0 +1,74 @@ +.TH "afcclient" 1 +.SH NAME +afcclient \- Interact with AFC/HouseArrest service on a connected device. +.SH SYNOPSIS +.B afcclient +[OPTIONS] [COMMAND ...] + +.SH DESCRIPTION + +Utility to interact with AFC/HouseArrest service. This allows access to parts +of the filesystem on an iOS device. + +\f[B]afcclient\f[] can be used interactively with a command prompt, or run a single command and exit. + +.SH COMMANDS +.TP +.B devinfo +print device information +.TP +.B info PATH +print file attributes of file at PATH +.TP +.B ls PATH +print directory contents of PATH +.TP +.B mv OLD NEW +rename file OLD to NEW +.TP +.B mkdir PATH +create directory at PATH +.TP +.B ln [-s] FILE [LINK] +Create a (symbolic) link to file named LINKNAME. \f[B]NOTE: This feature has been disabled in newer versions of iOS\f[]. +.TP +.B rm [-rf] PATH +remove item at PATH +.TP +.B get [-rf] PATH [LOCALPATH] +transfer file at PATH from device to LOCALPATH, or current directory if omitted. If LOCALPATH is a directory, the file will be stored inside the directory. +.TP +.B put [-rf] LOCALPATH [PATH] +transfer local file at LOCALPATH to device at PATH, or current directory if omitted. If PATH is a directory, the file will be stored inside the directory. +.TP + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID +.TP +.B \-n, \-\-network +connect to network device (not recommended, since the connection might be terminated at any time) +.TP +.B \--container <appid> +Access the app container directory of the app with given \f[B]appid\f[] +.TP +.B \--documents <appid> +Access the Documents directory of the app with given \f[B]appid\f[] +.TP +.B \-h, \-\-help +Prints usage information +.TP +.B \-d, \-\-debug +Enable communication debugging +.TP +.B \-v, \-\-version +Prints version information + +.SH AUTHOR +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/doxygen/custom.css b/docs/doxygen/custom.css new file mode 100644 index 0000000..62183bf --- /dev/null +++ b/docs/doxygen/custom.css @@ -0,0 +1,1722 @@ +body, table, div, p, dl { + font: 14px -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; + line-height: 1.5; +} + +/* @group Heading Levels */ + +h1.groupheader { + font-size: 28px; +} + +.title { + font: inherit; + font-size: 2rem; + font-weight: bold; + margin: 0; + padding-bottom: 0.3rem; + border-bottom: 1px solid #eaecef; +} + +h2.groupheader { + border-bottom: none; + color: rgb(36, 41, 46); + font-size: 1.5rem; + font-weight: bold; + margin: 1.75rem 0px 1rem 0px; + padding: 0 0.3rem 0 0; + border-bottom: 1px solid #eaecef; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; +} + +.textblock h1, +.textblock h2, +.textblock h3, +.textblock h4, +.textblock h5, +.textblock h6 { + margin: 1.75rem 0px 1rem 0px; + padding: 0 0.3rem 0 0; + border-bottom: 1px solid #eaecef; +} + +h1 { + font-size: 1.75rem; +} + +h2 { + color: rgb(36, 41, 46); + font-size: 1.5rem; + font-weight: bold; + margin: 1.75rem 0 1rem 0px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px #1fa4a9; +} + +dt { + font-weight: bold; +} + +div.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; +} + +p.startli, p.startdd, p.starttd { + margin-top: 2px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.qindex, div.navtab { + background-color: #fff; + border: 1px solid #d1d5da; + padding: 0; + border-radius: 3px; + text-align: left; +} + +div.qindex, div.navpath { + width: auto; + line-height: 140%; +} + +div.navtab { + margin-right: 2rem; +} + +@media (max-width: 576px) { + .contents td.top:not(.mempage), + div.navtab { + display: none; + } +} + +div.navtab table { + border-spacing: 0; +} + +div.navtab table td.navtab { + position: relative; + display: block; + padding: 8px 10px; + border-bottom: 1px solid #e1e4e8; +} + +div.navtab table td.navtab:hover { + background-color: #f6f8fa; +} + +/* @group Link Styling */ + +a { + color: #005082; + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: #005082; +} + +a:hover { + text-decoration: underline; +} + +a.qindex { + font-weight: bold; +} + +a.qindexHL { + font-weight: bold; + background-color: #fff; + color: inherit; + border: none; +} + +a.qindexHL:before { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 2px; + content: ""; + background-color: #f7ae1a; +} + +.contents a.qindexHL:visited { + color: inherit; +} + +a.el { + font-weight: normal; +} + +a.elRef { +} + +a.code, a.code:visited { + color: #3465a4; +} + +a.codeRef, a.codeRef:visited { + color: #3465a4; +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +pre.fragment { + border: 1px solid rgb(221, 221, 221); + border-radius: 3px; + background-color: rgb(248, 248, 248); + padding: 1rem; + margin: 1rem 0px; + overflow: auto; + word-wrap: break-word; + font-size: inherit; + line-height: inherit; + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; +} + +div.fragment { + padding: 1rem; + margin: 1rem 0px; + border: solid 1px rgb(221, 221, 221); + border-radius: 3px; + background-color: rgb(248, 248, 248); +} + +div.line { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + font-size: inherit; + min-height: 1rem; + line-height: inherit; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: initial; + padding-left: initial; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; + display: inline-block; + min-width: 100%; +} + +div.line.glow { + background-color: cyan; + box-shadow: 0 0 10px cyan; +} + + +span.lineno { + padding-right: 4px; + text-align: right; + border-right: 2px solid #0F0; + background-color: #E8E8E8; + white-space: pre; +} +span.lineno a { + background-color: #D8D8D8; +} + +span.lineno a:hover { + background-color: #C8C8C8; +} + +div.ah { + background-color: black; + font-weight: bold; + color: #ffffff; + margin-bottom: 3px; + margin-top: 3px; + padding: 0.2em; + border: solid thin #333; + border-radius: 0.5em; + -webkit-border-radius: .5em; + -moz-border-radius: .5em; + box-shadow: 2px 2px 3px #999; + -webkit-box-shadow: 2px 2px 3px #999; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444)); + background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000); +} + +div.groupHeader { + margin-left: 0px; + margin-top: 9px; + margin-bottom: 4.7px; + + font-size: 19px; + font-weight: normal; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + background-color: white; + color: rgb(36, 41, 46); + margin: 0; +} + +div.contents { + padding: 1rem 2rem; + margin: 0; +} + +td.indexkey { + background-color: #EBEFF6; + font-weight: bold; + border: 1px solid #C4CFE5; + margin: 2px 0px 2px 0; + padding: 2px 10px; + white-space: nowrap; + vertical-align: top; +} + +td.indexvalue { + background-color: #EBEFF6; + border: 1px solid #C4CFE5; + padding: 2px 10px; + margin: 2px 0px; +} + +tr.memlist { + background-color: #EEF1F7; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { +} + +img.formulaInl { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: left; + margin-left: 2rem; + margin-right: 2rem; + margin-bottom: 1rem; +} + +img.footer { + border: 0px; + vertical-align: middle; +} + +/* @group Code Colorization */ + +span.keyword { + color: #008000; +} + +span.keywordtype { + color: #604020; +} + +span.keywordflow { + color: #e08000; +} + +span.comment { + color: #800000; +} + +span.preprocessor { + color: #806020; +} + +span.stringliteral { + color: #002080; +} + +span.charliteral { + color: #008080; +} + +span.vhdldigit { + color: #ff00ff; +} + +span.vhdlchar { + color: #000000; +} + +span.vhdlkeyword { + color: #700070; +} + +span.vhdllogic { + color: #ff0000; +} + +blockquote { + background-color: #F7F8FB; + border-left: 2px solid #9CAFD4; + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #A3B4D7; +} + +th.dirtab { + background: #EBEFF6; + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid #e1e4e8; +} + +hr.footer { + height: 0px; + border-top: 1px solid #e1e4e8; + margin-left: 2rem; + margin-right: 2rem; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: inherit; + padding: 0px; + background-color: transparent; + border-color: #c8e1ff; +} + +table.memberdecls tr:not(.heading) { + background-color: #f1f8ff; +} + +table.memberdecls tr:not(.heading) td.ititle { + background-color: #fff; + border: none; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: #1fa4a9; + box-shadow: none; +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: transparent; + border: none; + margin: 0; + padding: 1rem 1rem 0.5rem 1rem; + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; +} + +.mdescLeft, +.memItemLeft, +.memTemplItemLeft { + border-left: 1px solid #c8e1ff; +} + +.mdescRight, +.memItemRight, +.memTemplItemRight { + border-right: 1px solid #c8e1ff; +} + +.memberdecls tr:nth-child(2) td { + border-top: 1px solid #c8e1ff; + padding-bottom: 1rem; +} + +.memberdecls tr:last-child td { + border-bottom: none; + padding-bottom: 1rem; +} + +.memberdecls tr:nth-last-child(2) td { + padding-bottom: 1rem; +} + +.mdescLeft, .mdescRight { + padding: 0 1rem 1rem 1rem; + color: black; + + /* font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; */ + font-style: normal; +} + +.memSeparator { + background-color: #fff; + border-top: 1px solid #c8e1ff; + border-bottom: 1px solid #c8e1ff; + line-height: 0.5rem; + margin: 0px; + padding: 0px; +} + +tr:last-child .memSeparator { + border-bottom: none; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight { + width: 100%; +} + +.mdescRight a.el:first-child, +.memItemRight a.el:first-child, +.memTemplItemRight a.el:first-child { + font-weight: bold; +} + +.memTemplParams { + color: #005082; + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 0 0.3rem 0 0; + border: none; + border-bottom: 1px solid #eaecef; + margin: 0 0 1.75rem 0; + line-height: inherit; + background: none; + font-weight: bold; + font-size: 2rem; + width: 100%; + float: left; +} + +.permalink { + font-size: 65%; + display: inline-block; + vertical-align: middle; + display: none; +} + +.permalink a:hover { + text-decoration: none; +} + +.memtemplate { + font-size: 100%; + color: black; + font-weight: normal; + margin-left: 1px; +} + +.memnav { + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 2rem; + margin-right: 0; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; + background-color: #f1f8ff; + border-color: #c8e1ff; +} + +.memitem.glow { + box-shadow: 0 0 15px #1fa4a9; +} + +.memname { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + margin-left: 0px; + border-spacing: initial; +} + +.memname td { + font-weight: bold; + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border: none; + border: 1px solid #c8e1ff; + padding: 1rem; + color: black; + font-weight: bold; + text-shadow: none; + background-image: none; + background-color: #f1f8ff; + /* opera specific markup */ + box-shadow: none; + border-top-right-radius: 0px; + border-top-left-radius: 0px; + /* firefox specific markup */ + -moz-box-shadow: none; + -moz-border-radius-topright: 0px; + -moz-border-radius-topleft: 0px; + /* webkit specific markup */ + -webkit-box-shadow: none; + -webkit-border-top-right-radius: 0px; + -webkit-border-top-left-radius: 0px; +} + +.memdoc, dl.reflist dd { + border: none; + padding: 6px; + background-color: #FBFCFD; + border-top-width: 0; + background-image: none; + background-color: #FFFFFF; + /* opera specific markup */ + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + box-shadow: none; + /* firefox specific markup */ + -moz-border-radius-bottomleft: 0px; + -moz-border-radius-bottomright: 0px; + -moz-box-shadow: none; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 0px; + -webkit-border-bottom-right-radius: 0px; + -webkit-box-shadow: none; +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: #01496d; + white-space: nowrap; +} +.paramname em { + font-style: italic; + font-weight: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + font-style: italic; + font-weight: normal; + text-shadow: none; + padding-right: 1rem; +} + +.params .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: middle; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: rgb(172, 172, 172);; + border: none; + text-shadow: none; + color: white; + margin-right: 4px; + padding: 2px 3px; + border-radius: 4px; + font-size: 9pt; + white-space: nowrap; + vertical-align: middle; +} + +/* @end */ + +/* these are for tree view when not used as main index */ + +div.directory { + border-top: 1px solid #eaecef; + border-left: 1px solid #eaecef; + border-right: 1px solid #eaecef; + border-bottom: 1px solid #eaecef; + margin: 10px 0px; + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 8px; + vertical-align: middle; + line-height: 1.5rem; +} + +.directory tr { + border-top: 1px solid #eaecef; +} + +.directory tr:first-child { + border-top: none; +} + +.directory td.entry { + white-space: nowrap; + padding: 8px; +} + +@media (max-width: 576px) { + .directory td.entry > span:first-child { + display: none !important; + } +} + +.directory td.entry a { + outline: none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding: 8px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.even { + padding-left: 0; + background-color: inherit; +} + +.directory tr:not(.even) { + background-color: #f6f8fa; +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + display: none; + white-space: nowrap; + width: auto; + text-align: right; + font-size: 0.75rem; + font-weight: bold; + padding: 8px; + border-bottom: 1px solid #eaecef; +} + +.directory .levels span { + cursor: pointer; + padding: 0 0.5rem; + color: #3d578c; +} + +.arrow { + color: #6c757d; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: inherit; + display: inline-block; + width: 16px; + height: 16px; +} + +.icon { + display: inline-block; + margin: 0; + + font-family: Arial, Helvetica; + font-weight: bold; + font-size: 0.75rem; + text-align: center; + + width: 16px; + height: 16px; + line-height: 16px; + + border-radius: 4px; + background-color: #6194cb; + color: white; +} + +.icona { + width: 16px; + height: 16px; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.iconfopen { + width: 16px; + height: 16px; + margin-bottom: 0; + background-image:url('folder-open.png'); + background-position: 0px -1px; + background-repeat: no-repeat; + vertical-align: middle; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.iconfclosed { + width: 16px; + height: 16px; + margin-bottom: 0; + background-image:url('folder.png'); + background-position: 0px 0px; + background-repeat: no-repeat; + vertical-align: middle; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.icondoc { + width: 16px; + height: 16px; + margin-bottom: 0; + background-image:url('text-x-generic.png'); + background-position: 0px 0px; + background-repeat: no-repeat; + vertical-align: middle; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: #2A3D61; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid #2D4068; + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: #374F7F; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid #c6cbd1; + border-spacing: 0px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; + font-size: 1rem; +} + +.fieldtable td, .fieldtable th { + padding: 0.5rem 1rem; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid #c6cbd1; + border-bottom: 1px solid #c6cbd1; + vertical-align: middle; +} + +.fieldtable td.fieldname { + padding-top: 0.5rem; + font-style: italic; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid #c6cbd1; +} + +.fieldtable td.fielddoc p { + font-size: 1rem; +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable tbody tr:nth-of-type(odd) { + background-color: #f6f8fa; +} + +.fieldtable th { + background-image: none; + background-repeat:repeat-x; + background-color: #fff; + font-size: inherit; + font-weight: bold; + color: inherit; + padding: 0.5rem 1rem; + text-align: inherit; + -moz-border-radius-topleft: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-top-right-radius: 3px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom: 1px solid #c6cbd1; +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: none; + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul { + font-size: 1rem; + background-image: none; + background-repeat: repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color: inherit; + border: none; + overflow:hidden; + margin:0px; + padding: 0px; +} + +.navpath li { + list-style-type: none; + float: left; + background-image: none; + background-repeat: no-repeat; + background-position: right; + color: inherit; +} + +#top .navpath li+li { + padding-left: .5rem; +} + +#top .navpath li+li::before { + display: inline-block; + padding-right: .5rem; + color: #6c757d; + content: "/"; +} + +@media (min-width: 576px) { + address.footer .navpath li+li { + padding-left: .5rem; + } + + address.footer .navpath li+li::before { + display: inline-block; + padding-right: .5rem; + color: #6c757d; + content: "/"; + } +} + +.navpath li.navelem a { + height:32px; + display: inline-block; + text-decoration: none; + outline: none; + color: inherit; + font-family: inherit; + text-shadow: none; + text-decoration: none; +} + +.navpath li.navelem a:hover { + color: inherit; +} + +address.footer { + text-align: left; + padding-right: 0; +} + +address.footer ul.navpath { + display: inline-block; + padding: 0; +} + +.navpath li.footer { + list-style-type: none; + float: left; + padding-left: 0.5rem; + padding-right: 0; + background-image: none; + background-repeat: no-repeat; + background-position: right; + color: inherit; + font-size: 0.75rem; +} + +div.summary { + float: none; + font-size: 0.75rem; + padding-right: 5px; + width: 100%; + text-align: left; +} + +div.summary a { + white-space: nowrap; +} + +div.ingroups { + font-size: 0.75rem; + width: 50%; + text-align: left; +} + +div.ingroups a { + white-space: nowrap; +} + +div.header { + background-image: none; + background-color: white; + padding: 1rem 2rem 0 2rem; + margin: 0px; + border: none; +} + +div.headertitle { + margin: 1rem 0px 0rem 0px; + padding: 0 0.3rem 0 0; +} + +dl { + padding: 0 0 0 10px; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */ +dl.section { + margin-left: 0px; + padding-left: 0px; +} + +dl.note { + margin-left: 0px; + padding: 1rem; + border-left: 6px solid; + border: 1px solid rgba(27,31,35,.15); + background-color: #fffbdd; + color: #735c0f; +} + +dl.section.note dd { + margin-left: 0; + margin-bottom: 0; +} + +dl.warning, dl.attention { + margin-left: 0px; + padding: 1rem; + + border-left: 6px solid; + border-color: #ab2333; +} + +dl.pre, dl.post, dl.invariant { + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #4e9a06; +} + +dl.deprecated { + margin-left: 0px; + padding: 1rem; + border-left: 6px solid; + border-color: #505050; +} + +dl.deprecated dt a.el { +} + +dl.todo { + margin-left: 0px; + padding: 1rem; + border-left:4px solid; + border-color: #04898e; +} + +dl.test { + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #21376d; +} + +dl.bug { + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #8f5902; +} + +dl.section dd { + margin-bottom: 0.5rem; +} + + +#projectlogo { + text-align: center; + vertical-align: bottom; + border-collapse: separate; + padding-right: 1rem; +} + +#projectlogo img { + border: 0px none; + width: 8rem; + height: auto; +} + +#projectname { + font: inherit; + font-size: 300%; + margin: 0px; + padding: 2px 0px; +} + +#projectbrief { + font: inherit; + font-size: 120%; + margin: 0px; + padding: 0px; +} + +#projectnumber { + font: inherit; + font-size: 50%; + margin: 0px; + padding: 0px; +} + +#titlearea { + padding: 1rem; + margin: 0px; + width: auto; + border-bottom: none; + background-color: #fafbfc; +} + +.image { + text-align: center; +} + +.dotgraph { + text-align: center; +} + +.mscgraph { + text-align: center; +} + +.caption { + font-weight: bold; +} + +div.zoom { + border: 1px solid #90A5CE; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:#334975; + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; +} + +dl.citelist dd { + margin:2px 0; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: #F4F6FA; + border: 1px solid #D8DFEE; + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 20px 10px 10px; + width: 200px; +} + +div.toc li { + background: url("bdwn.png") no-repeat scroll 0 5px transparent; + font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif; + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 Arial,FreeSans,sans-serif; + color: #4665A2; + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 30px; +} + +div.toc li.level4 { + margin-left: 45px; +} + +.inherit_header { + font-weight: bold; + color: gray; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + padding-top: 1.75rem; + margin-top: 0; + margin-bottom: 1rem; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + + +.tabs, .tabs2, .tabs3 { + background-image: none; + background-color: #fafbfc; + color: #24292e; + padding: 0 1rem; + font-family: inherit; + font-size: inherit; + width: auto; +} + +.tabs { + border-bottom: 1px solid #e1e4e8; +} + +.tablist { + position: relative; + top: 1px; +} + +.tabs2 { + margin-top: 1rem; + border-bottom: 1px solid #e1e4e8; + background-color: #fff; +} + +.tabs3 { + margin-top: 1rem; + border-bottom: 1px solid #e1e4e8; + background-color: #fff; +} + +.tablist li { + background-image: none; +} + +.tablist a { + background-image: none; + text-shadow: none; + color: #586069; + border: 1px solid transparent; + border-top: 3px solid transparent; + border-radius: 3px 3px 0 0; + padding: 0 1rem; +} + +.tablist a:hover { + background-image: none; + text-shadow: none; + color: #24292e; +} + +.tablist li.current a { + text-shadow: none; + background-image: none; + background-color: #fff; + color: #24292e; +} + +#navrow1 .tablist li.current a { + border-color: #f7ae1a #e1e4e8 transparent; +} + +#navrow2 .tablist li.current a { + border-color: #e45e25 #e1e4e8 transparent; +} + +#navrow3 .tablist li.current a { + border-color: #1fa4a9 #e1e4e8 transparent; +} + +#navrow4 .tablist li.current a { + border-color: #01496d #e1e4e8 transparent; +} + +.tabs li.current { + background-color: #fff; +} + +.tabs2 li.current { + background-color: #fff; +} + +.navpath { + border: none; + margin-top: 1rem; + padding: 0 2rem; +} + +.navpath ul { + background-color: transparent; + border: none; +} + +.navpath li { + padding: 0; +} + +.navpath li.navelem a { + color: #005082; + text-shadow: none; + padding: 0 0.25rem; + font-size: 1rem; +} + +.navpath li.navelem a:hover { + color: #005082; + text-shadow: none; + text-decoration: underline; +} + +/* @group Markdown */ + +table.markdownTable td, +table.markdownTable th { + border: 1px solid #c6cbd1; + padding: 0.5rem 1rem; +} + +.markdownTable tbody tr:nth-of-type(odd) { + background-color: #f6f8fa; +} + +th.markdownTableHeadLeft, +th.markdownTableHeadRight, +th.markdownTableHeadCenter, +th.markdownTableHeadNone { + background-color: #fff; + color: inherit; + font-size: inherit; + font-weight: bold; + padding: 0.5rem 1rem; + text-align: inherit; + -moz-border-radius-topleft: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-top-right-radius: 3px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom: 1px solid #c6cbd1; +} + +/* @end */ + +@media (max-width: 576px) { + .navpath { + padding: 0 1rem; + } + + .navpath li.navelem a { + padding: 0; + } + + div.header { + padding: 1rem 1rem 0 1rem; + } + + div.contents { + padding: 1rem 1rem 0 1rem; + } + + hr.footer { + margin-left: 1rem; + margin-right: 1rem; + } + + address.footer { + margin-left: 1rem; + margin-right: 1rem; + } + + .memtitle { + font-size: 1.5rem; + word-break: break-all; + } + + .memname td { + float: left; + word-break: break-all; + } + + div.memitem { + margin-bottom: 1rem; + } + + .memdoc table.params { + word-break: break-word; + } + + div.memproto { + display: table; + } + + dl.section dt { + margin-bottom: 0.5rem; + } + + dl.section dd { + margin-left: 0; + word-break: break-word; + } + + .contents > table, + .contents > table thead, + .contents > table tbody, + .contents > table th, + .contents > table td, + .contents > table tr { + display: block; + } + + .contents table.directory, + .contents table.directory thead, + .contents table.directory tbody, + .contents table.directory th, + .contents table.directory td { + display: block; + } + + .contents table.directory tr { + } + + div:not(.PageDoc) .contents table, + div:not(.PageDoc) .contents thead, + div:not(.PageDoc) .contents tbody, + div:not(.PageDoc) .contents th, + div:not(.PageDoc) .contents td, + div:not(.PageDoc) .contents tr { + display: block; + } + + .mdescLeft, + .memItemLeft, + .memTemplItemLeft { + text-align: left; + border-left: 1px solid #c8e1ff; + border-right: 1px solid #c8e1ff; + word-break: break-word; + white-space: normal; + padding-bottom: 0; + } + + table td.mdescLeft { + display: none; + } + + table td.mdescRight, + table td.memItemRight, + table td.memTemplItemRight { + border-top: none !important; + border-left: 1px solid #c8e1ff; + border-right: 1px solid #c8e1ff; + display: table-cell; + word-break: break-word; + padding-top: 0; + } + + .directory td.desc { + display: table-cell; + } + + dl.params dd { + margin-left: 0; + } + + table.params td.paramname { + margin-top: 0.5rem; + } + + #projectlogo img { + border: 0px none; + width: 4rem; + height: auto; + } + + #projectname { + font-size: 1rem; + } + + #projectbrief { + font-size: 0.75rem; + } + + #projectnumber { + font-size: 0.75rem; + } +}
\ No newline at end of file diff --git a/docs/doxygen/favicon.ico b/docs/doxygen/favicon.ico Binary files differnew file mode 100644 index 0000000..b542c5b --- /dev/null +++ b/docs/doxygen/favicon.ico diff --git a/docs/doxygen/folder-open.png b/docs/doxygen/folder-open.png Binary files differnew file mode 100644 index 0000000..b67403d --- /dev/null +++ b/docs/doxygen/folder-open.png diff --git a/docs/doxygen/folder.png b/docs/doxygen/folder.png Binary files differnew file mode 100644 index 0000000..901edc9 --- /dev/null +++ b/docs/doxygen/folder.png diff --git a/docs/doxygen/footer.html b/docs/doxygen/footer.html new file mode 100644 index 0000000..9eef78d --- /dev/null +++ b/docs/doxygen/footer.html @@ -0,0 +1,9 @@ +<hr class="footer"/> +<address class="footer"> + <ul class="navpath"> + <li class="footer">© 2007-$year <a href="https://libimobiledevice.org">$projectname</a> All rights reserved</li> + <li class="footer">Generated on <em>$datetime</em> by <a href="https://www.doxygen.org">doxygen $doxygenversion</a></li> + <li class="footer">Icons by <a href="http://tango-project.org/">Tango</a></li> + <li class="footer">Design © $year by <a href="https://mirell.com" target="_blank">Mirell</a></li> + </ui> +</address>
\ No newline at end of file diff --git a/docs/doxygen/header.html b/docs/doxygen/header.html new file mode 100644 index 0000000..bc41a11 --- /dev/null +++ b/docs/doxygen/header.html @@ -0,0 +1,64 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=9"/> +<meta name="generator" content="doxygen $doxygenversion"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<!--BEGIN PROJECT_NAME--><title>$title - $projectname $projectnumber</title><!--END PROJECT_NAME--> +<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME--> + +<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/> + +<meta property="og:locale" content="en_US" /> +<meta property="og:type" content="website" /> +<meta property="og:title" content="$projectbrief - $projectname" /> +<meta property="og:url" content="https://libimobiledevice.org/docs/$projectname/$projectnumber/" /> +<meta property="og:site_name" content="$projectname - $projectbrief" /> +<meta name="twitter:card" content="summary_large_image" /> + +<script type="text/javascript" src="$relpath^jquery.js"></script> +<script type="text/javascript" src="$relpath^dynsections.js"></script> +$treeview +$search +$mathjax +<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> +$extrastylesheet +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> + +<!--BEGIN TITLEAREA--> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr style="height: 56px;"> + <!--BEGIN PROJECT_LOGO--> + <td id="projectlogo"><a href="$relpath^index.html"><img alt="Logo" src="$relpath^$projectlogo"/></a></td> + <!--END PROJECT_LOGO--> + <!--BEGIN PROJECT_NAME--> + <td id="projectalign" style="padding-left: 0.5em;"> + <div id="projectname">$projectname + <!--BEGIN PROJECT_NUMBER--> <span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER--> + </div> + <!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief - <a href="https://libimobiledevice.org"><small>Return to Homepage</small></a></div><!--END PROJECT_BRIEF--> + </td> + <!--END PROJECT_NAME--> + <!--BEGIN !PROJECT_NAME--> + <!--BEGIN PROJECT_BRIEF--> + <td style="padding-left: 0.5em;"> + <div id="projectbrief">$projectbrief</div> + </td> + <!--END PROJECT_BRIEF--> + <!--END !PROJECT_NAME--> + <!--BEGIN DISABLE_INDEX--> + <!--BEGIN SEARCHENGINE--> + <td>$searchbox</td> + <!--END SEARCHENGINE--> + <!--END DISABLE_INDEX--> + </tr> + </tbody> +</table> +</div> +<!--END TITLEAREA--> +<!-- end header part -->
\ No newline at end of file diff --git a/docs/doxygen/layout.xml b/docs/doxygen/layout.xml new file mode 100644 index 0000000..46f068d --- /dev/null +++ b/docs/doxygen/layout.xml @@ -0,0 +1,226 @@ +<doxygenlayout version="1.0"> + <!-- Generated by doxygen 1.8.16 --> + <!-- Navigation index tabs for HTML output --> + <navindex> + <tab type="mainpage" visible="yes" title="About"/> + <tab type="modules" visible="yes" title="" intro=""/> + <tab type="namespaces" visible="yes" title=""> + <tab type="namespacelist" visible="yes" title="" intro=""/> + <tab type="namespacemembers" visible="yes" title="" intro=""/> + </tab> + <tab type="interfaces" visible="yes" title=""> + <tab type="interfacelist" visible="yes" title="" intro=""/> + <tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="interfacehierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="classes" visible="yes" title=""> + <tab type="classlist" visible="yes" title="" intro=""/> + <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="hierarchy" visible="yes" title="" intro=""/> + <tab type="classmembers" visible="yes" title="" intro=""/> + </tab> + <tab type="structs" visible="yes" title=""> + <tab type="structlist" visible="yes" title="" intro=""/> + <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> + </tab> + <tab type="exceptions" visible="yes" title=""> + <tab type="exceptionlist" visible="yes" title="" intro=""/> + <tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="exceptionhierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="files" visible="yes" title=""> + <tab type="filelist" visible="yes" title="" intro=""/> + <tab type="globals" visible="yes" title="" intro=""/> + </tab> + <tab type="examples" visible="yes" title="" intro=""/> + <tab type="pages" visible="yes" title="" intro=""/> + </navindex> + + <!-- Layout definition for a class page --> + <class> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <inheritancegraph visible="$CLASS_GRAPH"/> + <collaborationgraph visible="$COLLABORATION_GRAPH"/> + <memberdecl> + <nestedclasses visible="yes" title=""/> + <publictypes title=""/> + <services title=""/> + <interfaces title=""/> + <publicslots title=""/> + <signals title=""/> + <publicmethods title=""/> + <publicstaticmethods title=""/> + <publicattributes title=""/> + <publicstaticattributes title=""/> + <protectedtypes title=""/> + <protectedslots title=""/> + <protectedmethods title=""/> + <protectedstaticmethods title=""/> + <protectedattributes title=""/> + <protectedstaticattributes title=""/> + <packagetypes title=""/> + <packagemethods title=""/> + <packagestaticmethods title=""/> + <packageattributes title=""/> + <packagestaticattributes title=""/> + <properties title=""/> + <events title=""/> + <privatetypes title=""/> + <privateslots title=""/> + <privatemethods title=""/> + <privatestaticmethods title=""/> + <privateattributes title=""/> + <privatestaticattributes title=""/> + <friends title=""/> + <related title="" subtitle=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <services title=""/> + <interfaces title=""/> + <constructors title=""/> + <functions title=""/> + <related title=""/> + <variables title=""/> + <properties title=""/> + <events title=""/> + </memberdef> + <allmemberslink visible="yes"/> + <usedfiles visible="$SHOW_USED_FILES"/> + <authorsection visible="yes"/> + </class> + + <!-- Layout definition for a namespace page --> + <namespace> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <memberdecl> + <nestednamespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection visible="yes"/> + </namespace> + + <!-- Layout definition for a file page --> + <file> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <includegraph visible="$INCLUDE_GRAPH"/> + <includedbygraph visible="$INCLUDED_BY_GRAPH"/> + <memberdecl> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection/> + <sourcelink visible="yes"/> + </file> + + <!-- Layout definition for a group page --> + <group> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <groupgraph visible="$GROUP_GRAPHS"/> + <memberdecl> + <nestedgroups visible="yes" title=""/> + <dirs visible="yes" title=""/> + <files visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <pagedocs/> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + </memberdef> + <authorsection visible="yes"/> + </group> + + <!-- Layout definition for a directory page --> + <directory> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <directorygraph visible="yes"/> + <memberdecl> + <dirs visible="yes"/> + <files visible="yes"/> + </memberdecl> + </directory> +</doxygenlayout>
\ No newline at end of file diff --git a/docs/doxygen/logo-vector-clean.svg b/docs/doxygen/logo-vector-clean.svg new file mode 100644 index 0000000..a953115 --- /dev/null +++ b/docs/doxygen/logo-vector-clean.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 283.5 283.5" height="793.7" viewBox="0 0 793.7 793.7" width="793.7" xmlns="http://www.w3.org/2000/svg"><g stroke-width=".10005" transform="matrix(13.831 0 0 13.831 -1173.2 -291.34)"><path d="m94.968 35.193v-10.71l9.2751 5.355z" fill="#f7ae1a"/><path d="m94.968 35.193 9.2751-5.355v10.71z" fill="#e45e25"/><path d="m94.968 42.414 9.2751 5.355-9.2751 5.355z" fill="#d2343e"/><path d="m94.968 53.124 9.2751-5.355v10.71z" fill="#bb2033"/><path d="m94.968 53.124 9.2751 5.355-9.2751 5.355z" fill="#ab2333"/></g><g fill="#2e3436" fill-opacity=".19964" transform="matrix(13.831 0 0 13.831 -1361.02498 -319.002)"><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(-.16726 .09657 -.09657 -.16726 121.04 71.121)"/><path d="m148.27 69.213h-10.71l5.355-9.2751z" stroke-width=".10005" transform="matrix(-.86603 .5 .5 .86603 211.62 -57.046)"/><path d="m142.92-59.938h-10.71l5.355-9.2751z" stroke-width=".10005" transform="matrix(-.8660254 .5 -.5 -.8660254 211.614597 -57.0459)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(0 .19314 -.19314 0 132.86 60.576)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(.16726 .09657 -.09657 .16726 129.64 55.775)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" transform="matrix(0 .19314 -.19314 0 142.13 55.221)"/></g><g transform="matrix(13.831 0 0 13.831 -1173.2 -291.34)"><g stroke-width=".10005"><path d="m94.968 63.834 9.2751-5.355v10.71z" fill="#7b2f44"/><path d="m104.24 69.189 9.2751-5.355-9.2751-5.355z" fill="#21376d"/><path d="m104.24 58.479 9.2751-5.355v10.71z" fill="#005082"/><path d="m113.52 63.834v-10.71l9.2751 5.355z" fill="#023e49"/><path d="m122.79 58.479-9.2751-5.355 9.2751-5.355z" fill="#007e96"/></g><path d="m122.79 58.479v-10.71l9.2751 5.355z" fill="#0a9197"/></g><g fill="#ccc" transform="translate(2.808 194.51)"><polygon/><polygon/></g></svg>
\ No newline at end of file diff --git a/docs/doxygen/text-x-generic.png b/docs/doxygen/text-x-generic.png Binary files differnew file mode 100644 index 0000000..2d7f2d6 --- /dev/null +++ b/docs/doxygen/text-x-generic.png diff --git a/docs/idevice_id.1 b/docs/idevice_id.1 index c06fb5e..caded75 100644 --- a/docs/idevice_id.1 +++ b/docs/idevice_id.1 @@ -1,49 +1,43 @@ .TH "idevice_id" 1 .SH NAME -idevice_id \- Prints device name or a list of attached iPhone/iPod Touch devices. +idevice_id \- List attached devices or print device name of given device. .SH SYNOPSIS .B idevice_id -[OPTIONS] [UUID] +[OPTIONS] [UDID] .SH DESCRIPTION -Prints device name or a list of attached iPhone/iPod Touch devices. -The UUID is a 40-digit hexadecimal number of the device -for which the name should be retrieved. +\f[B]idevice_id\f[] prints a list of attached devices. If a UDID is given, +the name of the connected device with that UDID will be retrieved. + +Without any options, \f[B]idevice_id\f[] will list all available devices, +USB and network. +The output can be further controlled with the following OPTIONS. .SH OPTIONS .TP .B \-l, \-\-list -list UUID of all attached devices -.TP +List UDIDs of all devices attached via USB. +.TP +.B \-n, \-\-network +List UDIDs of all devices available via network. +.TP .B \-d, \-\-debug -enable communication debugging. -.TP +Enable communication debugging. +.TP .B \-h, \-\-help -prints usage information. -.TP - -.SH AUTHORS - Zach C. - - Jonathan Beck - - Matt Colyer - - Martin Aumueller - - Christophe Fergeau - - Martin S. - - Paul Sladen - - Patrick Walton +Prints usage information. +.TP +.B \-v, \-\-version +Prints version information. +.TP - Zoltan Balaton +.SH AUTHOR +Nikias Bassen - Nikias Bassen +Man page written to conform with Debian by Julien Lavergne. - Todd Zullinger +.SH ON THE WEB +https://libimobiledevice.org -Man page written to conform with Debian by Julien Lavergne. +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicebackup.1 b/docs/idevicebackup.1 index 5ae867e..6f2a8f9 100644 --- a/docs/idevicebackup.1 +++ b/docs/idevicebackup.1 @@ -1,24 +1,32 @@ .TH "idevicebackup" 1 .SH NAME -idevicebackup \- Create or restore backup for iPhone/iPod Touch devices. +idevicebackup \- Create or restore backup for devices. .SH SYNOPSIS .B idevicebackup [OPTIONS] CMD [DIRECTORY] .SH DESCRIPTION -Create or restore backup from the current or specified directory. +Create or restore backup in/from the specified directory. + +\f[B]NOTE\f[]: This tool is outdated. See idevicebackup2(1) for an updated tool. .SH OPTIONS .TP -.B \-u, \-\-uuid UUID -target specific device by its 40-digit device UUID. -.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP .B \-d, \-\-debug enable communication debugging. .TP .B \-h, \-\-help prints usage information. +.TP +.B \-v, \-\-version +prints version information. .SH COMMANDS .TP @@ -34,3 +42,11 @@ Martin Szulecki Nikias Bassen Man page written to conform with Debian by Julien Lavergne. + +.SH SEE ALSO +idevicebackup2(1) + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicebackup2.1 b/docs/idevicebackup2.1 new file mode 100644 index 0000000..79b6dc4 --- /dev/null +++ b/docs/idevicebackup2.1 @@ -0,0 +1,104 @@ +.TH "idevicebackup2" 1 +.SH NAME +idevicebackup2 \- Create or restore backups for devices running iOS 4 or later. +.SH SYNOPSIS +.B idevicebackup2 +[OPTIONS] CMD [CMDOPTIONS] DIRECTORY + +.SH DESCRIPTION + +Create or restore backup in/from the specified directory. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-s, \-\-source UDID +use backup data from device specified by UDID. +.TP +.B \-i, \-\-interactive +request passwords interactively on the command line. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-h, \-\-help +prints usage information. +.TP +.B \-v, \-\-version +prints version information. + +.SH COMMANDS +.TP +.B backup +create backup for the device. +.TP +.B \t\-\-full +force full backup from device. +.TP +.B restore +restore last backup to the device. +.TP +.B \t\-\-system +restore system files, too. +.TP +.B \t\-\-no\-reboot +do NO reboot the system when done. +.TP +.B \t\-\-copy +create a copy of backup folder before restoring. +.TP +.B \t\-\-settings +restore device settings from the backup. +.TP +.B \t\-\-remove +remove items which are not being restored. +.TP +.B \t\-\-skip-apps +do not trigger re-installation of apps after restore. +.TP +.B \t\-\-password PWD +supply the password for the encrypted source backup. If omitted, the password +will be requested in interactive mode (\f[B]\-i\f[]), or it can be passed using +the environment variable \f[B]BACKUP_PASSWORD\f[]. +.TP +.B info +show details about last completed backup of device. +.TP +.B list +list files of last completed backup in CSV format. +.TP +.B unback +unpack a completed backup in DIRECTORY/_unback_/ +.TP +.B encryption on|off [PWD] +enable or disable backup encryption. The password will be requested in +interactive mode (\f[B]\-i\f[]) if omitted, or it can be passed using the +environment variable \f[B]BACKUP_PASSWORD\f[]. +.TP +.B changepw [OLD NEW] +change backup password on target device. The passwords will be requested in +interactive mode (\f[B]\-i\f[]) if omitted, or they can be passed using the +environment variables \f[B]BACKUP_PASSWORD\f[] (old password) and +\f[B]BACKUP_PASSWORD_NEW\f[] (new password) respectively. +.TP +.B cloud on|off +enable or disable cloud use (requires iCloud account). +.SH SECURITY CONSIDERATIONS +Passing passwords on the command line is not advised, since it might reveal +the backup password to other users via process list or command line history. +Use interactive mode (\f[B]\-i\f[]) or pass them via environment variable(s) +as mentioned in the description of the respective commands above. +.SH AUTHORS +Martin Szulecki + +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicebtlogger.1 b/docs/idevicebtlogger.1 new file mode 100644 index 0000000..98a723f --- /dev/null +++ b/docs/idevicebtlogger.1 @@ -0,0 +1,60 @@ +.TH "idevicebtlogger" 1 +.SH NAME +idevicebtlogger \- Capture HCI traffic of a connected device. +.SH SYNOPSIS +.B idevicebtlogger +[OPTIONS] +<FILE> + +.SH DESCRIPTION + +Capture HCI traffic of a connected device. Requires Bluetooth logging profile to be installed on device with iOS 13 or higher. See https://www.bluetooth.com/blog/a-new-way-to-debug-iosbluetooth-applications/ for iOS device configuration. + +The HCI traffic can be stored in Apple's native PacketLogger format or converted into PCAP format for live feedback in Wireshark. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID +.TP +.B \-n, \-\-network +connect to network device +.TP +.B \-f, \-\-format FORMAT +set log format: PacketLogger (default), or pcap +.TP +.B \-x, \-\-exit +exit when device disconnects +.TP +.B \-d, \-\-debug +enable communication debugging +.TP +.B \-h, \-\-help +prints usage information +.TP +.B \-v, \-\-version +prints version information. + +.SH EXAMPLES +.TP +.B idevicebtlogger \-u 00008030\-0000111ABC000DEF +Capture HCI traffic of device with UDID 00008030-0000111ABC000DEF. +.TP +.B idevicebtlogger \-x +Capture HCI traffic of device and exit when the device is unplugged. +.TP +.B idevicebtlogger \-f pcap +Capture HCI traffic of device in PCAP format. +.TP +.B idevicebtlogger -f pcap - | wireshark -k -i - +Capture HCI traffic and pipe it into Wireshark for live feedback. + +.SH AUTHORS +Geoffrey Kruse + +Matthias Ringwald + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicecrashreport.1 b/docs/idevicecrashreport.1 new file mode 100644 index 0000000..6acd6e9 --- /dev/null +++ b/docs/idevicecrashreport.1 @@ -0,0 +1,50 @@ +.TH "idevicecrashreport" 1 +.SH NAME +idevicecrashreport \- Retrieve crash reports from a device. +.SH SYNOPSIS +.B idevicecrashreport +[OPTIONS] DIRECTORY + +.SH DESCRIPTION + +Simple utility to move crash reports from a device to a local directory. + +The utility outputs lines prefixed with either "Link:", "Copy:" or "Move:" +depending on whether a symlink was created, a file was copied or moved from +the device to the target DIRECTORY. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-e, \-\-extract +extract raw crash report into separate '.crash' files. +.TP +.B \-k, \-\-keep +copy but do not remove crash reports from device. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-f, \-\-filter NAME +filter crash reports by NAME (case sensitive) +.TP +.B \-h, \-\-help +prints usage information. +.TP +.B \-v, \-\-version +prints version information. + +.SH AUTHOR +Martin Szulecki + +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicedate.1 b/docs/idevicedate.1 index 15c9895..dcdc57e 100644 --- a/docs/idevicedate.1 +++ b/docs/idevicedate.1 @@ -1,22 +1,25 @@ .TH "idevicedate" 1 .SH NAME -idevicedate \- Display the current date or set it on an iDevice. +idevicedate \- Display the current date or set it on a device. .SH SYNOPSIS .B idevicedate [OPTIONS] .SH DESCRIPTION -Show information about the first connected iPhone/iPod Touch. +Simple utility to manage the clock on a device. .SH OPTIONS .TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP .B \-d, \-\-debug enable communication debugging. .TP -.B \-u, \-\-uuid UUID -target specific device by its 40-digit device UUID. -.TP .B \-s, \-\-set TIMESTAMP set UTC time described by TIMESTAMP .TP @@ -25,6 +28,14 @@ set time of device to current system time .TP .B \-h, \-\-help prints usage information +.TP +.B \-v, \-\-version +prints version information. .SH AUTHOR Martin Szulecki + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicedebug.1 b/docs/idevicedebug.1 new file mode 100644 index 0000000..7314baa --- /dev/null +++ b/docs/idevicedebug.1 @@ -0,0 +1,45 @@ +.TH "idevicedebug" 1 +.SH NAME +idevicedebug \- Interact with the debugserver service of a device. +.SH SYNOPSIS +.B idevicedebug +[OPTIONS] COMMAND + +.SH DESCRIPTION + +Interact with the debug service of a device. Currently the only implemented +command is "run" and allows execution of developer apps and watch the +stdout/stderr of the process. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-e, \-\-env NAME=VALUE +set environment variable NAME to VALUE. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-h, \-\-help +prints usage information. +.TP +.B \-v, \-\-version +prints version information. + +.SH COMMANDS +.TP +.B run BUNDLEID [ARGS...] +run app with BUNDLEID and optional ARGS on device. + +.SH AUTHORS +Martin Szulecki + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicedebugserverproxy.1 b/docs/idevicedebugserverproxy.1 new file mode 100644 index 0000000..69200ee --- /dev/null +++ b/docs/idevicedebugserverproxy.1 @@ -0,0 +1,49 @@ +.TH "idevicedebugserverproxy" 1 +.SH NAME +idevicedebugserverproxy \- Remote debugging proxy. +.SH SYNOPSIS +.B idevicedebugserverproxy +[OPTIONS] [PORT] + +.SH DESCRIPTION + +Proxy a debugserver connection from a device for remote debugging. +After starting up, clients can connect to PORT and communicate with the remote +debugserver using the LLVM remote serial debugging protocol. +Thus connecting using LLDB or a LLVM based gdb to this port would allow +remote debugging. +The developer disk image needs to be mounted for this service to be available. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-l, \-\-lldb +Enable lldb support. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-h, \-\-help +prints usage information. +.TP +.B \-v, \-\-version +prints version information. + +.SH USAGE +.TP +.B PORT +The port under which the proxy should listen for connections from clients. +If omitted, the next available port will be used and printed to stdout. + +.SH AUTHORS +Martin Szulecki + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicedevmodectl.1 b/docs/idevicedevmodectl.1 new file mode 100644 index 0000000..5edaa80 --- /dev/null +++ b/docs/idevicedevmodectl.1 @@ -0,0 +1,58 @@ +.TH "idevicedevmodectl" 1 +.SH NAME +idevicedevmodectl \- Enable Developer Mode on iOS 16+ devices or print the current status. +.SH SYNOPSIS +.B idevicedevmodectl +COMMAND +[OPTIONS] + +.SH DESCRIPTION + +Enable Developer Mode on iOS 16+ devices or print the current status. + +.SH NOTE +Passcode-protected devices will NOT allow enabling of Developer Mode from the command line. It has to be enabled on the device itself under Settings -> Privacy & Security -> Developer Mode. +The \f[B]enable\f[] command will try to enable it, and tell you if that's the case. +If the menu is not shown, you may use the \f[B]reveal\f[] command to reveal it. + +.SH COMMANDS +.TP +.B list +Prints the Developer Mode status of all connected devices, or for a specific one if \f[B]\-\-udid\f[] is given. +.TP +.B enable +Enable Developer Mode (device will reboot), and confirm it after device booted up again. +.TP +.B arm +Arm the Developer Mode (device will reboot) +.TP +.B confirm +Confirm enabling of Developer Mode +.TP +.B reveal +Reveal the Developer Mode menu on the device under Settings -> Privacy & Security + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID +.TP +.B \-n, \-\-network +connect to network device +.TP +.B \-d, \-\-debug +enable communication debugging +.TP +.B \-h, \-\-help +print usage information +.TP +.B \-v, \-\-version +print version information + +.SH AUTHORS +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicediagnostics.1 b/docs/idevicediagnostics.1 new file mode 100644 index 0000000..2f28b8d --- /dev/null +++ b/docs/idevicediagnostics.1 @@ -0,0 +1,62 @@ +.TH "idevicediagnostics" 1 +.SH NAME +idevicediagnostics \- Interact with the diagnostics interface of a device. +.SH SYNOPSIS +.B idevicediagnostics +[OPTIONS] COMMAND + +.SH DESCRIPTION + +Interact with the diagnostics interface of a device which allows one to retrieve +all kinds of information including diagnostics data, mobilegestalt data, remote +access to the IORegistry and certain commands like restart, shutdown and sleep. +Only available for iOS 4 and later. Accessing IORegistry is only supported on +iOS 5 and later. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-h, \-\-help +prints usage information. +.TP +.B \-v, \-\-version +prints version information. + +.SH COMMANDS +.TP +.B diagnostics [TYPE] +print diagnostics information from device optionally by TYPE. This includes +"All", "WiFi", "GasGauge" or "NAND". Default is "All". +.TP +.B mobilegestalt KEY [...] +print values of mobilegestalt keys passed as arguments after the command and +separated by a space. +.TP +.B ioreg [PLANE] +print IORegistry of device, optionally by PLANE like "IODeviceTree", "IOPower" + or "IOService". Only available on iOS 5 and later. +.TP +.B shutdown +shutdown device +.TP +.B restart +restart device +.TP +.B sleep +put device into sleep mode which also disconnects it from the host. + +.SH AUTHORS +Martin Szulecki + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/ideviceenterrecovery.1 b/docs/ideviceenterrecovery.1 index a543092..d455826 100644 --- a/docs/ideviceenterrecovery.1 +++ b/docs/ideviceenterrecovery.1 @@ -1,13 +1,13 @@ .TH "ideviceenterrecovery" 1 .SH NAME -ideviceenterrecovery \- Makes a device with the supplied 40-digit UUID enter recovery mode immediately. +ideviceenterrecovery \- Make a device enter recovery mode. .SH SYNOPSIS .B ideviceenterrecovery -[OPTIONS] UUID +[OPTIONS] UDID .SH DESCRIPTION -Makes a device with the supplied 40-digit UUID enter recovery mode immediately. +Makes a device with the supplied UDID enter recovery mode immediately. .SH OPTIONS .TP @@ -16,8 +16,16 @@ enable communication debugging. .TP .B \-h, \-\-help prints usage information. +.TP +.B \-v, \-\-version +prints version information. .SH AUTHORS Martin Szulecki Man page written to conform with Debian by Julien Lavergne. + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/ideviceimagemounter.1 b/docs/ideviceimagemounter.1 index 55d81e9..1fe7e45 100644 --- a/docs/ideviceimagemounter.1 +++ b/docs/ideviceimagemounter.1 @@ -1,24 +1,43 @@ .TH "ideviceimagemounter" 1 .SH NAME -ideviceimagemounter \- Mount disk images on the iPhone/iPod Touch. +ideviceimagemounter \- Mount, list, or unmount a disk image on the device. .SH SYNOPSIS .B ideviceimagemounter -[OPTIONS] IMAGE_FILE IMAGE_SIGNATURE_FILE +[OPTIONS] COMMAND [COMMAND OPTIONS] .SH DESCRIPTION -Mounts the specified disk image on the iPhone/iPod Touch device. +Mount, list, or unmount a disk image on the device. + +.SH COMMANDS +.TP +.B mount PATH +Mount the developer disk image at PATH. +For iOS 17+, PATH is a directory containing a .dmg image, a BuildManifest.plist, +and a Firmware sub-directory. + +For older versions PATH is a .dmg filename with a .dmg.signature file in the same directory, or with +another parameter pointing to a file elsewhere. +.TP +.B list +List mounted disk images. +.TP +.B unmount PATH +Unmount the image mounted at PATH. +.TP +.B devmodestatus +Query the developer mode status (iOS 16+) .SH OPTIONS .TP -.B \-d, \-\-debug -enable communication debugging. +.B \-u, \-\-udid UDID +target specific device by UDID. .TP -.B \-u, \-\-uuid UUID -target specific device by its 40-digit device UUID. +.B \-n, \-\-network +connect to network device. .TP -.B \-l, \-\-list -list mount information +.B \-d, \-\-debug +enable communication debugging. .TP .B \-t, \-\-imagetype NAME the image type to use, default is 'Developer' @@ -29,13 +48,15 @@ use XML output .B \-h, \-\-help prints usage information .TP -.B IMAGE_FILE -the image filename to mount -.TP -.B IMAGE_SIGNATURE_FILE -corresponding signature file for image filename +.B \-v, \-\-version +prints version information. .SH AUTHOR Nikias Bassen Man page written to conform with Debian by Julien Lavergne. + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/ideviceinfo.1 b/docs/ideviceinfo.1 index e350dd0..3944612 100644 --- a/docs/ideviceinfo.1 +++ b/docs/ideviceinfo.1 @@ -1,22 +1,28 @@ .TH "ideviceinfo" 1 .SH NAME -ideviceinfo \- Show information about the first connected iPhone/iPod Touch. +ideviceinfo \- Show information about the first connected device. .SH SYNOPSIS .B ideviceinfo [OPTIONS] .SH DESCRIPTION -Show information about the first connected iPhone/iPod Touch. +Show information about the first connected device. .SH OPTIONS .TP +.B \-s, \-\-simple +use a simple connection to avoid auto-pairing with the device. +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP .B \-d, \-\-debug enable communication debugging. .TP -.B \-u, \-\-uuid UUID -target specific device by its 40-digit device UUID. -.TP .B \-q, \-\-domain NAME set domain of query to NAME. Default: None. .TP @@ -27,9 +33,17 @@ only query key specified by NAME. Default: All keys. output information as xml plist instead of key/value pairs. .TP .B \-h, \-\-help -prints usage information +prints usage information. +.TP +.B \-v, \-\-version +prints version information. .SH AUTHOR Martin Szulecki Man page written to conform with Debian by Julien Lavergne. + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicename.1 b/docs/idevicename.1 new file mode 100644 index 0000000..34ce490 --- /dev/null +++ b/docs/idevicename.1 @@ -0,0 +1,41 @@ +.TH "idevicename" 1 +.SH NAME +idevicename \- Display the device name or set it to NAME if specified. +.SH SYNOPSIS +.B idevicename +[OPTIONS] [NAME] + +.SH DESCRIPTION + +Simple utility to manage the device name. + +If called without any extra argument this tool will print the current device name. + +If +.B NAME +is given the device name will be set to the name specified. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-h, \-\-help +prints usage information +.TP +.B \-v, \-\-version +prints version information. + +.SH AUTHOR +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicenotificationproxy.1 b/docs/idevicenotificationproxy.1 new file mode 100644 index 0000000..56dd0b0 --- /dev/null +++ b/docs/idevicenotificationproxy.1 @@ -0,0 +1,44 @@ +.TH "idevicenotificationproxy" 1 +.SH NAME +idevicenotificationproxy \- Post or observe notifications on a device. +.SH SYNOPSIS +.B idevicenotificationproxy +[OPTIONS] COMMAND + +.SH DESCRIPTION + +Post or observe notifications on an iOS device from the command line. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-h, \-\-help +prints usage information. +.TP +.B \-v, \-\-version +prints version information. + +.SH COMMANDS +.TP +.B post ID [ID...] +post notification IDs to device and exit. +.TP +.B observe ID [ID...] +observe notification IDs in the foreground until CTRL+C or signal is received. + +.SH AUTHORS + +Martin Szulecki + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicepair.1 b/docs/idevicepair.1 index da76b7f..eb6e7d4 100644 --- a/docs/idevicepair.1 +++ b/docs/idevicepair.1 @@ -1,43 +1,80 @@ .TH "idevicepair" 1 .SH NAME -idevicepair \- Manage pairings with iPhone/iPod Touch/iPad devices and this host. +idevicepair \- Manage host pairings with devices and usbmuxd. .SH SYNOPSIS .B idevicepair [OPTIONS] COMMAND .SH DESCRIPTION -Manage pairings with iPhone/iPod Touch/iPad devices and this host. +Manage host pairings with devices and usbmuxd. .SH OPTIONS .TP -.B \-u, \-\-uuid UUID -target specific device by its 40-digit device UUID. -.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-w, \-\-wireless +perform wireless pairing (\f[B]see NOTE\f[]). +.TP +.B \-n, \-\-network +connect to network device (\f[B]see NOTE\f[]). +.TP .B \-d, \-\-debug enable communication debugging. -.TP +.TP .B \-h, \-\-help prints usage information. +.TP +.B \-v, \-\-version +prints version information. .SH COMMANDS .TP +.B systembuid +print the system buid of the usbmuxd host. +.TP .B hostid -print the host id of this computer. +print the host id for target device. .TP .B pair -pair device with this computer. +pair device with this host. .TP .B validate -validate if device is paired with this computer. +validate if device is paired with this host. .TP .B unpair -unpair device with this computer. +unpair device with this host. .TP .B list -list devices paired with this computer. +list devices paired with this host. + +.SH NOTE +Pairing over network (wireless pairing) is only supported by Apple TV +devices. To perform a wireless pairing, you need to use the \f[B]\-w\f[] +command line switch. + +Make sure to put the device into pairing mode first by opening +Settings > Remotes and Devices > Remote App and Devices. + +The pairable device will become visible with a special UDID, and then you +can run idevicepair like this: + +.B idevicepair -u fffc8:ab:cd:12:34:56fff -w pair + +idevicepair will then ask for the PIN that the device is displaying and +continues with the pairing once entered. + +Please note that wireless pairing is currently not supported on Linux. .SH AUTHORS Nikias Bassen -Man page written to conform with Debian by Julien Lavergne. +Martin Szulecki + +Julien Lavergne + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/ideviceprovision.1 b/docs/ideviceprovision.1 new file mode 100644 index 0000000..3597d6e --- /dev/null +++ b/docs/ideviceprovision.1 @@ -0,0 +1,58 @@ +.TH "ideviceprovision" 1 +.SH NAME +ideviceprovision \- Manage provisioning profiles on a device. +.SH SYNOPSIS +.B ideviceprovision +[OPTIONS] COMMAND + +.SH DESCRIPTION + +Manage provisioning profiles on a device. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP +.B \-x, \-\-xml +print XML output when using the 'dump' command. +.TP +.B \-d, \-\-debug +enable communication debugging. +.TP +.B \-h, \-\-help +prints usage information. +.TP +.B \-v, \-\-version +prints version information. + +.SH COMMANDS +.TP +.B install FILE +Install the provisioning profile specified by FILE. A valid ".mobileprovision" +file is expected. +.TP +.B list +Get a list of all provisioning profiles on the device. +.TP +.B copy PATH +Retrieves all provisioning profiles from the device and stores them into the +existing directory specified by PATH. The files will be stored +as "UUID.mobileprovision". +.TP +.B remove UUID +Removes the provisioning profile identified by UUID. +.TP +.B dump FILE +Prints detailed information about the provisioning profile specified by FILE. + +.SH AUTHORS +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicescreenshot.1 b/docs/idevicescreenshot.1 index cf0ed15..4da78af 100644 --- a/docs/idevicescreenshot.1 +++ b/docs/idevicescreenshot.1 @@ -1,29 +1,44 @@ .TH "idevicescreenshot" 1 .SH NAME -idevicescreenshot \- Gets a screenshot from the connected iPhone/iPod Touch. +idevicescreenshot \- Gets a screenshot from the connected device. .SH SYNOPSIS .B idevicescreenshot -[OPTIONS] +[OPTIONS] [FILE] .SH DESCRIPTION -Gets a screenshot from the connected iPhone/iPod Touch. +Gets a screenshot from the connected device. + +The screenshot is saved as a TIFF image with the given FILE name, where the +default name is "screenshot-DATE.tiff", +e.g.: ./screenshot-2013-12-31-23-59-59.tiff NOTE: A mounted developer disk image is required on the device, otherwise - the screenshotr service is not available. +the screenshotr service is not available. .SH OPTIONS .TP +.B \-u, \-\-udid UDID +target specific device by UDID. +.TP +.B \-n, \-\-network +connect to network device. +.TP .B \-d, \-\-debug enable communication debugging. .TP -.B \-u, \-\-uuid UUID -target specific device by its 40-digit device UUID. -.TP .B \-h, \-\-help prints usage information +.TP +.B \-v, \-\-version +prints version information. .SH AUTHOR Nikias Bassen Man page written to conform with Debian by Julien Lavergne. + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicesetlocation.1 b/docs/idevicesetlocation.1 new file mode 100644 index 0000000..941a6e5 --- /dev/null +++ b/docs/idevicesetlocation.1 @@ -0,0 +1,38 @@ +.TH "idevicesetlocation" 1 +.SH NAME +idevicesetlocation \- Simulate location on iOS device. +.SH SYNOPSIS +.B idevicesetlocation +[OPTIONS] -- <LAT> <LONG> + +.B idevicesetlocation +[OPTIONS] reset + +.SH DESCRIPTION + +Simulate location on iOS device with mounted developer disk image. + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID +.TP +.B \-n, \-\-network +connect to network device +.TP +.B \-d, \-\-debug +enable communication debugging +.TP +.B \-h, \-\-help +prints usage information +.TP +.B \-v, \-\-version +prints version information. + +.SH AUTHOR +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/docs/idevicesyslog.1 b/docs/idevicesyslog.1 index 178d7c5..66ae2e4 100644 --- a/docs/idevicesyslog.1 +++ b/docs/idevicesyslog.1 @@ -1,26 +1,128 @@ .TH "idevicesyslog" 1 .SH NAME -idevicesyslog \- Relay syslog of a connected iPhone/iPod Touch. +idevicesyslog \- Relay syslog of a connected device. .SH SYNOPSIS .B idevicesyslog [OPTIONS] .SH DESCRIPTION -Relay syslog of a connected iPhone/iPod Touch. +Relay syslog of a connected device. .SH OPTIONS -.TP +.TP +.B \-u, \-\-udid UDID +target specific device by UDID +.TP +.B \-n, \-\-network +connect to network device +.TP +.B \-x, \-\-exit +exit when device disconnects +.TP .B \-d, \-\-debug -enable communication debugging. +enable communication debugging .TP -.B \-u, \-\-uuid UUID -target specific device by its 40-digit device UUID -.TP .B \-h, \-\-help -prints usage information. +prints usage information +.TP +.B \-v, \-\-version +Prints version information. +.TP +.B \-\-no\-colors +disable colored output +.TP +.B \-o, \-\-output FILE +Write to FILE instead of stdout. This will disable writing colored output, but can be re-enabled with \f[B]\-\-colors\f[]. +If FILE already exists, it will be overwritten without warning. +.TP +.B \-\-colors +Force writing colored output, e.g. when using \f[B]\-\-output\f[]. + +.SH FILTER OPTIONS +.TP +.B \-m, \-\-match STRING +only print messages that contain STRING -.SH AUTHOR -Martin Szulecki +This option will set a filter to only printed log messages that contain the given string. +.TP +.B \-t, \-\-trigger STRING +start logging when matching STRING + +When specified, logging will start as soon as a log messages is encountered that contains the given string. See also +\f[B]\-T, \-\-untrigger\f[]. Other filters are still applied but obviously filtered messages are only printed after logging has started. +.TP +.B \-T, \-\-untrigger STRING +stop logging when matching STRING + +When specified logging will halt as soon as a log message is encountered that contains the given string. See also +\f[B]\-t, \-\-trigger\f[]. Other filters are still applied but obviously filtered messages are only printed before logging stops. + +NOTE: If no \f[B]\-\-trigger\f[] is given, idevicesyslog will exit after a matching log message was encountered. +.TP +.B \-p, \-\-process PROCESS +only print messages from matching process(es) + +PROCESS is a string that can either be a numeric pid or a process name. It also supports multiple process names or pids in one string, separated by | (make sure to use quotes!). +.TP +.B \-e, \-\-exclude PROCESS +print all messages except matching process(es) + +PROCESS is a string that can either be a numeric pid or a process name. It also supports multiple process names or pids in one string, separated by | (make sure to use quotes!). +.TP +.B \-q, \-\-quiet +set a filter to exclude common noisy processes + +Since the syslog can be quite noisy, this quick command line switch allows silencing a predefined set of commonly known processes. The list of processes that are silenced can be retrieved with \f[B]\-\-quiet\-list\f[]. +.TP +.B \-\-quiet\-list +prints the list of processes for \f[B]\-\-quiet\f[] and exits +.TP +.B \-k, \-\-kernel +only print kernel messages + +This is actually equivalent to passing \f[B]\-\-process kernel\f[] with the exception that it can be used with \f[B]\-\-quiet\f[] to silence out the noisy process but still get all the kernel log messages. +.TP +.B \-K, \-\-no\-kernel +suppress kernel messages + +This is equivalent to passing \f[B]\-\-exclude kernel\f[]. + +.SH EXAMPLES +.TP +.B idevicesyslog \-u 00008030\-0000111ABC000DEF +Relay syslog of device with UDID 00008030-0000111ABC000DEF. +.TP +.B idevicesyslog \-x +Relay syslog of device and exit when the device is unplugged. +.TP +.B idevicesyslog \-m '####' \-e 'identityservicesd' \-K +Only print log messages that contain the string #### and do NOT originate from identityservicesd or the kernel. +.TP +.B idevicesyslog \-p MyApp \-p ReportCrash +Only print log messages from the process named 'MyApp' and 'ReportCrash'. +.TP +.B idevicesyslog \-p 'MyApp|ReportCrash' +Same as previous example with different syntax. +.TP +.B idevicesyslog \-e 'backboardd|CommCenter|mDNSResponder' +Suppress log messages from backboardd, CommCenter, and mDNSResponder. +.TP +.B idevicesyslog \-q \-k +Suppress log messages from common noisy processes, but DO print kernel log messages. +.TP +.B idevicesyslog \-K +Suppress log messages from kernel, but print everything else +.TP +.B idevicesyslog \-t 'backlight on' \-T 'backlight off' \-q +Start logging when the device turns on backlight and stop logging when it turns backlight off, and suppress noisy processes + +.SH AUTHORS +Nikias Bassen, Martin Szulecki Man page written to conform with Debian by Julien Lavergne. + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/doxygen.cfg.in b/doxygen.cfg.in index a5d08bc..4cbbb2d 100644 --- a/doxygen.cfg.in +++ b/doxygen.cfg.in @@ -1,1417 +1,2441 @@ -# Doxyfile 1.5.6 +# Doxyfile 1.8.16 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project +# doxygen (www.doxygen.org) for a project. # -# All text after a hash (#) is considered a comment and will be ignored +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. # The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. PROJECT_NAME = @PACKAGE@ -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. PROJECT_NUMBER = @VERSION@ -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "API Documentation" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = docs/doxygen/logo-vector-clean.svg + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. OUTPUT_DIRECTORY = docs -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. CREATE_SUBDIRS = NO -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, -# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, -# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, -# and Ukrainian. +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. +# The default value is: YES. REPEAT_BRIEF = YES -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. -ABBREVIATE_BRIEF = +ABBREVIATE_BRIEF = -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief # description. +# The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. +# The default value is: NO. INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. FULL_PATH_NAMES = YES -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = include -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. JAVADOC_AUTOBRIEF = YES -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. -QT_AUTOBRIEF = NO +JAVADOC_BANNER = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. -MULTILINE_CPP_IS_BRIEF = NO +QT_AUTOBRIEF = NO -# If the DETAILS_AT_TOP tag is set to YES then Doxygen -# will output the detailed description near the top, like JavaDoc. -# If set to NO, the detailed description appears after the member -# documentation. +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. -DETAILS_AT_TOP = NO +MULTILINE_CPP_IS_BRIEF = NO -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. -SEPARATE_MEMBER_PAGES = NO +SEPARATE_MEMBER_PAGES = YES -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 8 -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = YES -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = YES + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. +# The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. +# The default value is: NO. CPP_CLI_SUPPORT = NO -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. SIP_SUPPORT = NO -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. IDL_PROPERTY_SUPPORT = YES -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. +# The default value is: NO. DISTRIBUTE_GROUP_DOC = NO -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. SUBGROUPING = NO -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. TYPEDEF_HIDES_STRUCT = YES +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. EXTRACT_ALL = NO -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. EXTRACT_PRIVATE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. EXTRACT_STATIC = NO -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. EXTRACT_LOCAL_CLASSES = NO -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. EXTRACT_LOCAL_METHODS = NO -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. HIDE_IN_BODY_DOCS = NO -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# (including Cygwin) ands Mac users are advised to set this option to NO. +# The default value is: system dependent. CASE_SENSE_NAMES = NO -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. SHOW_INCLUDE_FILES = NO -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. SORT_BRIEF_DOCS = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. SORT_GROUP_NAMES = NO -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. SORT_BY_SCOPE_NAME = NO -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. GENERATE_DEPRECATEDLIST= YES -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. +# The default value is: YES. -SHOW_USED_FILES = YES +SHOW_USED_FILES = NO -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. SHOW_FILES = YES -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. SHOW_NAMESPACES = YES -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command <command> <input-file>, where <command> is the value of -# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = docs/doxygen/layout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. -FILE_VERSION_FILTER = +CITE_BIB_FILES = #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. QUIET = NO -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. WARNINGS = YES -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. WARN_IF_DOC_ERROR = YES -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. WARN_NO_PARAMDOC = NO -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. -INPUT = src include/@PACKAGE@ +INPUT = \ + include/@PACKAGE@ \ + README.md -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. INPUT_ENCODING = UTF-8 -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. -FILE_PATTERNS = +FILE_PATTERNS = -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. RECURSIVE = NO -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. -EXCLUDE = dev +EXCLUDE = -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded # from the input. +# The default value is: NO. EXCLUDE_SYMLINKS = NO -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = LIBIMOBILEDEVICE_API -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). -EXAMPLE_PATH = +EXAMPLE_PATH = -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. EXAMPLE_RECURSIVE = NO -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. -IMAGE_PATH = +FILTER_PATTERNS = -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command <filter> <input-file>, where <filter> -# is the value of the INPUT_FILTER tag, and <input-file> is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. If FILTER_PATTERNS is specified, this tag will be -# ignored. +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. -INPUT_FILTER = +FILTER_SOURCE_FILES = NO -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_PATTERNS = +FILTER_SOURCE_PATTERNS = -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. -FILTER_SOURCE_FILES = NO +USE_MDFILE_AS_MAINPAGE = README.md #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. SOURCE_BROWSER = NO -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. STRIP_CODE_COMMENTS = YES -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. REFERENCED_BY_RELATION = YES -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. REFERENCES_RELATION = YES -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. Otherwise they will link to the documentstion. +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. REFERENCES_LINK_SOURCE = YES -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. ALPHABETICAL_INDEX = NO -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. GENERATE_HTML = YES -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a # standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = docs/doxygen/header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = docs/doxygen/footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = \ + docs/doxygen/custom.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = \ + docs/doxygen/favicon.ico \ + docs/doxygen/folder.png \ + docs/doxygen/folder-open.png \ + docs/doxygen/text-x-generic.png + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = +HTML_DYNAMIC_SECTIONS = NO -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. -HTML_ALIGN_MEMBERS = YES +GENERATE_DOCSET = NO -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. -GENERATE_HTMLHELP = NO +DOCSET_FEEDNAME = "Doxygen generated docs" -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. -GENERATE_DOCSET = NO +DOCSET_BUNDLE_ID = org.doxygen.Project -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. -DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_PUBLISHER_ID = org.doxygen.Publisher -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. -DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_NAME = Publisher -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. -HTML_DYNAMIC_SECTIONS = NO +GENERATE_HTMLHELP = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be # written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 1 - # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to FRAME, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, -# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are -# probably better off using the HTML help feature. Other possible values -# for this tag are: HIERARCHIES, which will generate the Groups, Directories, -# and Class Hiererachy pages using a tree view instead of an ordered list; -# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which -# disables this behavior completely. For backwards compatibility with previous -# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE -# respectively. +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NONE -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: https://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + #--------------------------------------------------------------------------- -# configuration options related to the LaTeX output +# Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. GENERATE_LATEX = NO -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_OUTPUT = latex -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. COMPACT_LATEX = NO -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. -PAPER_TYPE = a4wide +LATEX_FOOTER = -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +LATEX_EXTRA_STYLESHEET = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_EXTRA_FILES = -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. PDF_HYPERLINKS = NO -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a # higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. USE_PDFLATEX = NO -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HIDE_INDICES = NO +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- -# configuration options related to the RTF output +# Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. GENERATE_RTF = NO -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. COMPACT_RTF = NO -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- -# configuration options related to the man page output +# Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. GENERATE_MAN = NO -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_OUTPUT = man -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_EXTENSION = .3 -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_LINKS = NO #--------------------------------------------------------------------------- -# configuration options related to the XML output +# Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. GENERATE_XML = NO -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. -XML_SCHEMA = +XML_PROGRAMLISTING = YES -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. -XML_DTD = +XML_NS_MEMB_FILE_SCOPE = NO -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- -XML_PROGRAMLISTING = YES +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output +# Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- -# configuration options related to the Perl module output +# Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. This is useful -# if you want to understand what is going on. On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_PRETTY = YES -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- -# Configuration options related to the preprocessor +# Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. SEARCH_INCLUDES = NO -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = LIBIMOBILEDEVICE_API -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- -# Configuration::additions related to external references +# Configuration options related to external references #--------------------------------------------------------------------------- -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. EXTERNAL_GROUPS = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. -PERL_PATH = /usr/bin/perl +EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more # powerful graphs. +# The default value is: YES. CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCGEN_PATH = +DIA_PATH = -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. HAVE_DOT = NO -# By default doxygen will write a font called FreeSans.ttf to the output -# directory and reference it in all dot files that doxygen generates. This -# font does not include all possible unicode characters however, so when you need -# these (or just want a differently looking font) you can specify the font name -# using DOT_FONTNAME. You need need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = FreeSans +DOT_FONTSIZE = 10 -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_FONTPATH = -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. UML_LOOK = NO -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. TEMPLATE_RELATIONS = NO -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. CALL_GRAPH = NO -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = png -# The tag DOT_PATH can be used to specify the path where the dot tool can be +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. -DOT_PATH = +PLANTUML_JAR_PATH = -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. -DOTFILE_DIRS = +PLANTUML_CFG_FILE = -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_GRAPH_MAX_NODES = 50 -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is enabled by default, which results in a transparent -# background. Warning: Depending on the platform used, enabling this option -# may lead to badly anti-aliased labels on the edges of a graph (i.e. they -# become hard to read). +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_TRANSPARENT = NO -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_MULTI_TARGETS = NO -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- - -# The SEARCHENGINE tag specifies whether or not a search engine should be -# used. If set to NO the values of all tags below this one will be ignored. - -SEARCHENGINE = NO diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..d868952 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,20 @@ +#!/bin/sh +SRCDIR=`dirname $0` +if test -n "$1"; then + VER=$1 +else + if test -r "${SRCDIR}/.git" && test -x "`which git`" ; then + git update-index -q --refresh + if ! VER=`git describe --tags --dirty 2>/dev/null`; then + COMMIT=`git rev-parse --short HEAD` + DIRTY=`git diff --quiet HEAD || echo "-dirty"` + VER=`sed -n '1,/RE/s/Version \(.*\)/\1/p' ${SRCDIR}/NEWS`-git-${COMMIT}${DIRTY} + fi + else + if test -f "${SRCDIR}/.tarball-version"; then + VER=`cat "${SRCDIR}/.tarball-version"` + fi + fi +fi +VER=`printf %s "$VER" | head -n1` +printf %s "$VER" diff --git a/include/Makefile.am b/include/Makefile.am index 44794c6..2abaf49 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,13 +1,31 @@ -nobase_include_HEADERS = libimobiledevice/libimobiledevice.h \ - libimobiledevice/lockdown.h \ - libimobiledevice/afc.h \ - libimobiledevice/file_relay.h \ - libimobiledevice/notification_proxy.h \ - libimobiledevice/installation_proxy.h \ - libimobiledevice/sbservices.h \ - libimobiledevice/mobile_image_mounter.h \ - libimobiledevice/screenshotr.h \ - libimobiledevice/mobilesync.h \ - libimobiledevice/mobilebackup.h \ - libimobiledevice/house_arrest.h \ - libimobiledevice/restore.h +EXTRA_DIST = \ + asprintf.h \ + endianness.h + +nobase_include_HEADERS = \ + libimobiledevice/libimobiledevice.h \ + libimobiledevice/lockdown.h \ + libimobiledevice/afc.h \ + libimobiledevice/file_relay.h \ + libimobiledevice/notification_proxy.h \ + libimobiledevice/installation_proxy.h \ + libimobiledevice/sbservices.h \ + libimobiledevice/mobile_image_mounter.h \ + libimobiledevice/screenshotr.h \ + libimobiledevice/mobilesync.h \ + libimobiledevice/mobilebackup.h \ + libimobiledevice/house_arrest.h \ + libimobiledevice/mobilebackup2.h \ + libimobiledevice/misagent.h \ + libimobiledevice/restore.h \ + libimobiledevice/webinspector.h \ + libimobiledevice/heartbeat.h \ + libimobiledevice/diagnostics_relay.h \ + libimobiledevice/debugserver.h \ + libimobiledevice/syslog_relay.h \ + libimobiledevice/mobileactivation.h \ + libimobiledevice/preboard.h \ + libimobiledevice/companion_proxy.h \ + libimobiledevice/reverse_proxy.h \ + libimobiledevice/property_list_service.h \ + libimobiledevice/service.h diff --git a/include/asprintf.h b/include/asprintf.h new file mode 100644 index 0000000..3ed35be --- /dev/null +++ b/include/asprintf.h @@ -0,0 +1,36 @@ +#ifndef __ASPRINTF_H +#define __ASPRINTF_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#ifndef HAVE_VASPRINTF +static inline int vasprintf(char **PTR, const char *TEMPLATE, va_list AP) +{ + int res; + char buf[16]; + res = vsnprintf(buf, 16, TEMPLATE, AP); + if (res > 0) { + *PTR = (char*)malloc(res+1); + res = vsnprintf(*PTR, res+1, TEMPLATE, AP); + } + return res; +} +#endif + +#ifndef HAVE_ASPRINTF +static inline int asprintf(char **PTR, const char *TEMPLATE, ...) +{ + int res; + va_list AP; + va_start(AP, TEMPLATE); + res = vasprintf(PTR, TEMPLATE, AP); + va_end(AP); + return res; +} +#endif + +#endif /* ASPRINTF_H */ diff --git a/include/endianness.h b/include/endianness.h new file mode 100644 index 0000000..88b63db --- /dev/null +++ b/include/endianness.h @@ -0,0 +1,123 @@ +#ifndef ENDIANNESS_H +#define ENDIANNESS_H + +#ifndef __LITTLE_ENDIAN +#define __LITTLE_ENDIAN 1234 +#endif + +#ifndef __BIG_ENDIAN +#define __BIG_ENDIAN 4321 +#endif + +#ifndef __BYTE_ORDER +#ifdef __LITTLE_ENDIAN__ +#define __BYTE_ORDER __LITTLE_ENDIAN +#else +#ifdef __BIG_ENDIAN__ +#define __BYTE_ORDER __BIG_ENDIAN +#endif +#endif +#endif + +#ifndef be16toh +#if __BYTE_ORDER == __BIG_ENDIAN +#define be16toh(x) (x) +#else +#define be16toh(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8)) +#endif +#endif + +#ifndef htobe16 +#define htobe16 be16toh +#endif + +#ifndef le16toh +#if __BYTE_ORDER == __BIG_ENDIAN +#define le16toh(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8)) +#else +#define le16toh(x) (x) +#endif +#endif + +#ifndef htole16 +#define htole16 le16toh +#endif + +#ifndef __bswap_32 +#define __bswap_32(x) ((((x) & 0xFF000000) >> 24) \ + | (((x) & 0x00FF0000) >> 8) \ + | (((x) & 0x0000FF00) << 8) \ + | (((x) & 0x000000FF) << 24)) +#endif + +#ifndef be32toh +#if __BYTE_ORDER == __BIG_ENDIAN +#define be32toh(x) (x) +#else +#define be32toh(x) __bswap_32(x) +#endif +#endif + +#ifndef htobe32 +#define htobe32 be32toh +#endif + +#ifndef le32toh +#if __BYTE_ORDER == __BIG_ENDIAN +#define le32toh(x) __bswap_32(x) +#else +#define le32toh(x) (x) +#endif +#endif + +#ifndef htole32 +#define htole32 le32toh +#endif + +#ifndef __bswap_64 +#define __bswap_64(x) ((((x) & 0xFF00000000000000ull) >> 56) \ + | (((x) & 0x00FF000000000000ull) >> 40) \ + | (((x) & 0x0000FF0000000000ull) >> 24) \ + | (((x) & 0x000000FF00000000ull) >> 8) \ + | (((x) & 0x00000000FF000000ull) << 8) \ + | (((x) & 0x0000000000FF0000ull) << 24) \ + | (((x) & 0x000000000000FF00ull) << 40) \ + | (((x) & 0x00000000000000FFull) << 56)) +#endif + +#ifndef htobe64 +#if __BYTE_ORDER == __BIG_ENDIAN +#define htobe64(x) (x) +#else +#define htobe64(x) __bswap_64(x) +#endif +#endif + +#ifndef be64toh +#define be64toh htobe64 +#endif + +#ifndef le64toh +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define le64toh(x) (x) +#else +#define le64toh(x) __bswap_64(x) +#endif +#endif + +#ifndef htole64 +#define htole64 le64toh +#endif + +#if (defined(__BIG_ENDIAN__) \ + && !defined(__FLOAT_WORD_ORDER__)) \ + || (defined(__FLOAT_WORD_ORDER__) \ + && __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__) +#define float_bswap64(x) __bswap_64(x) +#define float_bswap32(x) __bswap_32(x) +#else +#define float_bswap64(x) (x) +#define float_bswap32(x) (x) +#endif + +#endif /* ENDIANNESS_H */ diff --git a/include/libimobiledevice/afc.h b/include/libimobiledevice/afc.h index 8d47696..4ad3dbd 100644 --- a/include/libimobiledevice/afc.h +++ b/include/libimobiledevice/afc.h @@ -1,9 +1,10 @@ /** * @file libimobiledevice/afc.h - * @brief Access the filesystem. + * @brief Access the filesystem on the device. * \internal * - * Copyright (c) 2009 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2009-2010 Nikias Bassen All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,50 +21,51 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef AFC_H -#define AFC_H +#ifndef IAFC_H +#define IAFC_H #ifdef __cplusplus extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define AFC_E_SUCCESS 0 -#define AFC_E_UNKNOWN_ERROR 1 -#define AFC_E_OP_HEADER_INVALID 2 -#define AFC_E_NO_RESOURCES 3 -#define AFC_E_READ_ERROR 4 -#define AFC_E_WRITE_ERROR 5 -#define AFC_E_UNKNOWN_PACKET_TYPE 6 -#define AFC_E_INVALID_ARG 7 -#define AFC_E_OBJECT_NOT_FOUND 8 -#define AFC_E_OBJECT_IS_DIR 9 -#define AFC_E_PERM_DENIED 10 -#define AFC_E_SERVICE_NOT_CONNECTED 11 -#define AFC_E_OP_TIMEOUT 12 -#define AFC_E_TOO_MUCH_DATA 13 -#define AFC_E_END_OF_DATA 14 -#define AFC_E_OP_NOT_SUPPORTED 15 -#define AFC_E_OBJECT_EXISTS 16 -#define AFC_E_OBJECT_BUSY 17 -#define AFC_E_NO_SPACE_LEFT 18 -#define AFC_E_OP_WOULD_BLOCK 19 -#define AFC_E_IO_ERROR 20 -#define AFC_E_OP_INTERRUPTED 21 -#define AFC_E_OP_IN_PROGRESS 22 -#define AFC_E_INTERNAL_ERROR 23 - -#define AFC_E_MUX_ERROR 30 -#define AFC_E_NO_MEM 31 -#define AFC_E_NOT_ENOUGH_DATA 32 -#define AFC_E_DIR_NOT_EMPTY 33 -/*@}*/ - -/** Represents an error code. */ -typedef int16_t afc_error_t; +/** Service identifier passed to lockdownd_start_service() to start the AFC service */ +#define AFC_SERVICE_NAME "com.apple.afc" + +/** Error Codes */ +typedef enum { + AFC_E_SUCCESS = 0, + AFC_E_UNKNOWN_ERROR = 1, + AFC_E_OP_HEADER_INVALID = 2, + AFC_E_NO_RESOURCES = 3, + AFC_E_READ_ERROR = 4, + AFC_E_WRITE_ERROR = 5, + AFC_E_UNKNOWN_PACKET_TYPE = 6, + AFC_E_INVALID_ARG = 7, + AFC_E_OBJECT_NOT_FOUND = 8, + AFC_E_OBJECT_IS_DIR = 9, + AFC_E_PERM_DENIED = 10, + AFC_E_SERVICE_NOT_CONNECTED = 11, + AFC_E_OP_TIMEOUT = 12, + AFC_E_TOO_MUCH_DATA = 13, + AFC_E_END_OF_DATA = 14, + AFC_E_OP_NOT_SUPPORTED = 15, + AFC_E_OBJECT_EXISTS = 16, + AFC_E_OBJECT_BUSY = 17, + AFC_E_NO_SPACE_LEFT = 18, + AFC_E_OP_WOULD_BLOCK = 19, + AFC_E_IO_ERROR = 20, + AFC_E_OP_INTERRUPTED = 21, + AFC_E_OP_IN_PROGRESS = 22, + AFC_E_INTERNAL_ERROR = 23, + AFC_E_MUX_ERROR = 30, + AFC_E_NO_MEM = 31, + AFC_E_NOT_ENOUGH_DATA = 32, + AFC_E_DIR_NOT_EMPTY = 33, + AFC_E_FORCE_SIGNED_TYPE = -1 +} afc_error_t; /** Flags for afc_file_open */ typedef enum { @@ -88,33 +90,293 @@ typedef enum { AFC_LOCK_UN = 8 | 4 /**< unlock */ } afc_lock_op_t; -typedef struct afc_client_private afc_client_private; +typedef struct afc_client_private afc_client_private; /**< \private */ typedef afc_client_private *afc_client_t; /**< The client handle. */ /* Interface */ -afc_error_t afc_client_new_from_connection(idevice_connection_t connection, afc_client_t *client); -afc_error_t afc_client_new(idevice_t device, uint16_t port, afc_client_t *client); -afc_error_t afc_client_free(afc_client_t client); -afc_error_t afc_get_device_info(afc_client_t client, char ***infos); -afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***list); -afc_error_t afc_get_file_info(afc_client_t client, const char *filename, char ***infolist); -afc_error_t afc_file_open(afc_client_t client, const char *filename, afc_file_mode_t file_mode, uint64_t *handle); -afc_error_t afc_file_close(afc_client_t client, uint64_t handle); -afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation); -afc_error_t afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read); -afc_error_t afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written); -afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence); -afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position); -afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize); -afc_error_t afc_remove_path(afc_client_t client, const char *path); -afc_error_t afc_rename_path(afc_client_t client, const char *from, const char *to); -afc_error_t afc_make_directory(afc_client_t client, const char *dir); -afc_error_t afc_truncate(afc_client_t client, const char *path, uint64_t newsize); -afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, const char *target, const char *linkname); -afc_error_t afc_set_file_time(afc_client_t client, const char *path, uint64_t mtime); + +/** + * Makes a connection to the AFC service on the device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated afc_client_t + * upon successful return. + * + * @return AFC_E_SUCCESS on success, AFC_E_INVALID_ARG if device or service is + * invalid, AFC_E_MUX_ERROR if the connection cannot be established, + * or AFC_E_NO_MEM if there is a memory allocation problem. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_client_new(idevice_t device, lockdownd_service_descriptor_t service, afc_client_t *client); + +/** + * Starts a new AFC service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated afc_client_t upon + * successful return. Must be freed using afc_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return AFC_E_SUCCESS on success, or an AFC_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_client_start_service(idevice_t device, afc_client_t* client, const char* label); + +/** + * Frees up an AFC client. If the connection was created by the client itself, + * the connection will be closed. + * + * @param client The client to free. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_client_free(afc_client_t client); + +/** + * Get device information for a connected client. The device information + * returned is the device model as well as the free space, the total capacity + * and blocksize on the accessed disk partition. + * + * @param client The client to get device info for. + * @param device_information A char list of device information terminated by an + * empty string or NULL if there was an error. Free with + * afc_dictionary_free(). + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_get_device_info(afc_client_t client, char ***device_information); + +/** + * Gets a directory listing of the directory requested. + * + * @param client The client to get a directory listing from. + * @param path The directory for listing. (must be a fully-qualified path) + * @param directory_information A char list of files in the directory + * terminated by an empty string or NULL if there was an error. Free with + * afc_dictionary_free(). + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_read_directory(afc_client_t client, const char *path, char ***directory_information); + +/** + * Gets information about a specific file. + * + * @param client The client to use to get the information of the file. + * @param path The fully-qualified path to the file. + * @param file_information Pointer to a buffer that will be filled with a + * NULL-terminated list of strings with the file information. Set to NULL + * before calling this function. Free with afc_dictionary_free(). + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***file_information); + +/** + * Opens a file on the device. + * + * @param client The client to use to open the file. + * @param filename The file to open. (must be a fully-qualified path) + * @param file_mode The mode to use to open the file. + * @param handle Pointer to a uint64_t that will hold the handle of the file + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_open(afc_client_t client, const char *filename, afc_file_mode_t file_mode, uint64_t *handle); + +/** + * Closes a file on the device. + * + * @param client The client to close the file with. + * @param handle File handle of a previously opened file. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_close(afc_client_t client, uint64_t handle); + +/** + * Locks or unlocks a file on the device. + * + * Makes use of flock on the device. + * @see http://developer.apple.com/documentation/Darwin/Reference/ManPages/man2/flock.2.html + * + * @param client The client to lock the file with. + * @param handle File handle of a previously opened file. + * @param operation the lock or unlock operation to perform, this is one of + * AFC_LOCK_SH (shared lock), AFC_LOCK_EX (exclusive lock), or + * AFC_LOCK_UN (unlock). + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation); + +/** + * Attempts to the read the given number of bytes from the given file. + * + * @param client The relevant AFC client + * @param handle File handle of a previously opened file + * @param data The pointer to the memory region to store the read data + * @param length The number of bytes to read + * @param bytes_read The number of bytes actually read. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read); + +/** + * Writes a given number of bytes to a file. + * + * @param client The client to use to write to the file. + * @param handle File handle of previously opened file. + * @param data The data to write to the file. + * @param length How much data to write. + * @param bytes_written The number of bytes actually written to the file. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written); + +/** + * Seeks to a given position of a pre-opened file on the device. + * + * @param client The client to use to seek to the position. + * @param handle File handle of a previously opened. + * @param offset Seek offset. + * @param whence Seeking direction, one of SEEK_SET, SEEK_CUR, or SEEK_END. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence); + +/** + * Returns current position in a pre-opened file on the device. + * + * @param client The client to use. + * @param handle File handle of a previously opened file. + * @param position Position in bytes of indicator + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position); + +/** + * Sets the size of a file on the device. + * + * @param client The client to use to set the file size. + * @param handle File handle of a previously opened file. + * @param newsize The size to set the file to. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + * + * @note This function is more akin to ftruncate than truncate, and truncate + * calls would have to open the file before calling this, sadly. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize); + +/** + * Deletes a file or directory. + * + * @param client The client to use. + * @param path The path to delete. (must be a fully-qualified path) + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_remove_path(afc_client_t client, const char *path); + +/** + * Renames a file or directory on the device. + * + * @param client The client to have rename. + * @param from The name to rename from. (must be a fully-qualified path) + * @param to The new name. (must also be a fully-qualified path) + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_rename_path(afc_client_t client, const char *from, const char *to); + +/** + * Creates a directory on the device. + * + * @param client The client to use to make a directory. + * @param path The directory's path. (must be a fully-qualified path, I assume + * all other mkdir restrictions apply as well) + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_make_directory(afc_client_t client, const char *path); + +/** + * Sets the size of a file on the device without prior opening it. + * + * @param client The client to use to set the file size. + * @param path The path of the file to be truncated. + * @param newsize The size to set the file to. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_truncate(afc_client_t client, const char *path, uint64_t newsize); + +/** + * Creates a hard link or symbolic link on the device. + * + * @param client The client to use for making a link + * @param linktype 1 = hard link, 2 = symlink + * @param target The file to be linked. + * @param linkname The name of link. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, const char *target, const char *linkname); + +/** + * Sets the modification time of a file on the device. + * + * @param client The client to use to set the file size. + * @param path Path of the file for which the modification time should be set. + * @param mtime The modification time to set in nanoseconds since epoch. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_set_file_time(afc_client_t client, const char *path, uint64_t mtime); + +/** + * Deletes a file or directory including possible contents. + * + * @param client The client to use. + * @param path The path to delete. (must be a fully-qualified path) + * @since libimobiledevice 1.1.7 + * @note Only available in iOS 6 and later. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_remove_path_and_contents(afc_client_t client, const char *path); /* Helper functions */ -afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char **value); + +/** + * Get a specific key of the device info list for a client connection. + * Known key values are: Model, FSTotalBytes, FSFreeBytes and FSBlockSize. + * This is a helper function for afc_get_device_info(). + * + * @param client The client to get device info for. + * @param key The key to get the value of. + * @param value The value for the key if successful or NULL otherwise. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char **value); + +/** + * Frees up a char dictionary as returned by some AFC functions. + * + * @param dictionary The char array terminated by an empty string. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +LIBIMOBILEDEVICE_API afc_error_t afc_dictionary_free(char **dictionary); + +/** + * Gets a readable error string for a given AFC error code. + * + * @param err An AFC error code + * + * @returns A readable error string + */ +LIBIMOBILEDEVICE_API const char* afc_strerror(afc_error_t err); #ifdef __cplusplus } diff --git a/include/libimobiledevice/bt_packet_logger.h b/include/libimobiledevice/bt_packet_logger.h new file mode 100644 index 0000000..590e5c1 --- /dev/null +++ b/include/libimobiledevice/bt_packet_logger.h @@ -0,0 +1,156 @@ +/** + * @file libimobiledevice/bt_packet_logger.h + * @brief Capture the Bluetooth HCI trace from a device + * \internal + * + * Copyright (c) 2021 Geoffrey Kruse, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IBT_PACKET_LOGGER_H +#define IBT_PACKET_LOGGER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +#define BT_PACKETLOGGER_SERVICE_NAME "com.apple.bluetooth.BTPacketLogger" +#define BT_MAX_PACKET_SIZE 65535 + +/** Error Codes */ +typedef enum { + BT_PACKET_LOGGER_E_SUCCESS = 0, + BT_PACKET_LOGGER_E_INVALID_ARG = -1, + BT_PACKET_LOGGER_E_MUX_ERROR = -2, + BT_PACKET_LOGGER_E_SSL_ERROR = -3, + BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA = -4, + BT_PACKET_LOGGER_E_TIMEOUT = -5, + BT_PACKET_LOGGER_E_UNKNOWN_ERROR = -256 +} bt_packet_logger_error_t; + +typedef struct { + uint32_t length; + uint32_t ts_secs; + uint32_t ts_usecs; +} bt_packet_logger_header_t; + +typedef struct bt_packet_logger_client_private bt_packet_logger_client_private; +typedef bt_packet_logger_client_private *bt_packet_logger_client_t; /**< The client handle. */ + +/** Receives each hci packet received from the device. */ +typedef void (*bt_packet_logger_receive_cb_t)(uint8_t * data, uint16_t len, void *user_data); + +/* Interface */ + +/** + * Connects to the bt_packet_logger service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * bt_packet_logger_client_t upon successful return. Must be freed using + * bt_packet_logger_client_free() after use. + * + * @return BT_PACKET_LOGGER_E_SUCCESS on success, BT_PACKET_LOGGER_E_INVALID_ARG when + * client is NULL, or an BT_PACKET_LOGGER_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API bt_packet_logger_error_t bt_packet_logger_client_new(idevice_t device, lockdownd_service_descriptor_t service, bt_packet_logger_client_t * client); + +/** + * Starts a new bt_packet_logger service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * bt_packet_logger_client_t upon successful return. Must be freed using + * bt_packet_logger_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return BT_PACKET_LOGGER_E_SUCCESS on success, or an BT_PACKET_LOGGER_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API bt_packet_logger_error_t bt_packet_logger_client_start_service(idevice_t device, bt_packet_logger_client_t * client, const char* label); + +/** + * Disconnects a bt_packet_logger client from the device and frees up the + * bt_packet_logger client data. + * + * @param client The bt_packet_logger client to disconnect and free. + * + * @return BT_PACKET_LOGGER_E_SUCCESS on success, BT_PACKET_LOGGER_E_INVALID_ARG when + * client is NULL, or an BT_PACKET_LOGGER_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API bt_packet_logger_error_t bt_packet_logger_client_free(bt_packet_logger_client_t client); + + +/** + * Starts capturing the hci interface from the device using a callback. + * + * Use bt_packet_logger_stop_capture() to stop receiving hci data. + * + * @param client The bt_packet_logger client to use + * @param callback Callback to receive each packet from the hci interface. + * @param user_data Custom pointer passed to the callback function. + * + * @return BT_PACKET_LOGGER_E_SUCCESS on success, + * BT_PACKET_LOGGER_E_INVALID_ARG when one or more parameters are + * invalid or BT_PACKET_LOGGER_E_UNKNOWN_ERROR when an unspecified + * error occurs or an hci capture has already been started. + */ +LIBIMOBILEDEVICE_API bt_packet_logger_error_t bt_packet_logger_start_capture(bt_packet_logger_client_t client, bt_packet_logger_receive_cb_t callback, void* user_data); + +/** + * Stops capturing the hci interface from the device. + * + * Use bt_packet_logger_start_capture() to start receiving the hci data. + * + * @param client The bt_packet_logger client to use + * + * @return BT_PACKET_LOGGER_E_SUCCESS on success, + * BT_PACKET_LOGGER_E_INVALID_ARG when one or more parameters are + * invalid or BT_PACKET_LOGGER_E_UNKNOWN_ERROR when an unspecified + * error occurs or an hci capture has already been started. + */ +LIBIMOBILEDEVICE_API bt_packet_logger_error_t bt_packet_logger_stop_capture(bt_packet_logger_client_t client); + +/* Receiving */ + +/** + * Receives data using the given bt_packet_logger client with specified timeout. + * + * @param client The bt_packet_logger client to use for receiving + * @param data Buffer that will be filled with the data received + * @param size Number of bytes to receive + * @param received Number of bytes received (can be NULL to ignore) + * @param timeout Maximum time in milliseconds to wait for data. + * + * @return BT_PACKET_LOGGER_E_SUCCESS on success, + * BT_PACKET_LOGGER_E_INVALID_ARG when one or more parameters are + * invalid, BT_PACKET_LOGGER_E_MUX_ERROR when a communication error + * occurs, or BT_PACKET_LOGGER_E_UNKNOWN_ERROR when an unspecified + * error occurs. + */ +LIBIMOBILEDEVICE_API bt_packet_logger_error_t bt_packet_logger_receive_with_timeout(bt_packet_logger_client_t client, char *data, uint32_t size, uint32_t *received, unsigned int timeout); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/companion_proxy.h b/include/libimobiledevice/companion_proxy.h new file mode 100644 index 0000000..544322a --- /dev/null +++ b/include/libimobiledevice/companion_proxy.h @@ -0,0 +1,212 @@ +/** + * @file libimobiledevice/companion_proxy.h + * @brief Companion proxy support. + * \internal + * + * Copyright (c) 2019-2020 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ICOMPANION_PROXY_H +#define ICOMPANION_PROXY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the companion proxy service */ +#define COMPANION_PROXY_SERVICE_NAME "com.apple.companion_proxy" + +/** Error Codes */ +typedef enum { + COMPANION_PROXY_E_SUCCESS = 0, + COMPANION_PROXY_E_INVALID_ARG = -1, + COMPANION_PROXY_E_PLIST_ERROR = -2, + COMPANION_PROXY_E_MUX_ERROR = -3, + COMPANION_PROXY_E_SSL_ERROR = -4, + COMPANION_PROXY_E_NOT_ENOUGH_DATA = -5, + COMPANION_PROXY_E_TIMEOUT = -6, + COMPANION_PROXY_E_OP_IN_PROGRESS = -7, + COMPANION_PROXY_E_NO_DEVICES = -100, + COMPANION_PROXY_E_UNSUPPORTED_KEY = -101, + COMPANION_PROXY_E_TIMEOUT_REPLY = -102, + COMPANION_PROXY_E_UNKNOWN_ERROR = -256 +} companion_proxy_error_t; + +typedef struct companion_proxy_client_private companion_proxy_client_private; /**< \private */ +typedef companion_proxy_client_private *companion_proxy_client_t; /**< The client handle. */ + +/** Callback for companion device events */ +typedef void (*companion_proxy_device_event_cb_t) (plist_t event, void* userdata); + +/** + * Connects to the companion_proxy service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * companion_proxy_client_t upon successful return. Must be freed using + * companion_proxy_client_free() after use. + * + * @return COMPANION_PROXY_E_SUCCESS on success, COMPANION_PROXY_E_INVALID_ARG when + * the arguments are invalid, or an COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, companion_proxy_client_t* client); + +/** + * Starts a new companion_proxy service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * companion_proxy_client_t upon successful return. Must be freed using + * companion_proxy_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return COMPANION_PROXY_E_SUCCESS on success, or an COMPANION_PROXY_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_client_start_service(idevice_t device, companion_proxy_client_t* client, const char* label); + +/** + * Disconnects a companion_proxy client from the device and frees up the + * companion_proxy client data. + * + * @param client The companion_proxy client to disconnect and free. + * + * @return COMPANION_PROXY_E_SUCCESS on success, COMPANION_PROXY_E_INVALID_ARG when + * client is NULL, or an COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_client_free(companion_proxy_client_t client); + +/** + * Sends a plist to the service. + * + * @param client The companion_proxy client + * @param plist The plist to send + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * COMPANION_PROXY_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_send(companion_proxy_client_t client, plist_t plist); + +/** + * Receives a plist from the service. + * + * @param client The companion_proxy client + * @param plist The plist to store the received data + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * COMPANION_PROXY_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_receive(companion_proxy_client_t client, plist_t * plist); + +/** + * Retrieves a list of paired devices. + * + * @param client The companion_proxy client + * @param paired_devices Point that will receive a PLIST_ARRAY with paired device UDIDs + * + * @note The device closes the connection after sending the reply. + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * COMPANION_PROXY_E_NO_DEVICES if no devices are paired, + * or a COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_get_device_registry(companion_proxy_client_t client, plist_t* paired_devices); + +/** + * Starts listening for paired devices. + * + * @param client The companion_proxy client + * @param callback Callback function that will be called when a new device is detected + * @param userdata Pointer that that will be passed to the callback function + * + * @note The event parameter that gets passed to the callback function is + * freed internally after returning from the callback. The consumer needs + * to make a copy if required. + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * or a COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_start_listening_for_devices(companion_proxy_client_t client, companion_proxy_device_event_cb_t callback, void* userdata); + +/** + * Stops listening for paired devices + * + * @param client The companion_proxy client + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * or a COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_stop_listening_for_devices(companion_proxy_client_t client); + +/** + * Returns a value for the given key. + * + * @param client The companion_proxy client + * @param companion_udid UDID of the (paired) companion device + * @param key The key to retrieve the value for + * @param value A pointer to a plist_t that will receive the value for the given key. + * The consumer is responsible for freeing the value with plist_free() when no longer needed. + * + * @note The device closes the connection after sending the reply. + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * COMPANION_PROXY_E_INVALID_ARG when client or paired_devices is invalid, + * COMPANION_PROXY_E_UNSUPPORTED_KEY if the companion device doesn't support the given key, + * or a COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_get_value_from_registry(companion_proxy_client_t client, const char* companion_udid, const char* key, plist_t* value); + +/** + * Start forwarding a service port on the companion device to a port on the idevice. + * + * @see companion_proxy_stop_forwarding_service_port + * + * @param client The companion_proxy client + * @param remote_port remote port + * @param service_name The name of the service that shall be forwarded + * @param forward_port Pointer that will receive the newly-assigned port accessible via USB/Network on the idevice + * @param options PLIST_DICT with additional options. Currently known are + * IsServiceLowPriority (boolean) and PreferWifi (boolean). + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * or a COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_start_forwarding_service_port(companion_proxy_client_t client, uint16_t remote_port, const char* service_name, uint16_t* forward_port, plist_t options); + +/** + * Stop forwarding a service port between companion device and idevice. + * + * @see companion_proxy_start_forwarding_service_port + * + * @param client The companion_proxy client + * @param remote_port remote port + * + * @return COMPANION_PROXY_E_SUCCESS on success, + * or a COMPANION_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API companion_proxy_error_t companion_proxy_stop_forwarding_service_port(companion_proxy_client_t client, uint16_t remote_port); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/debugserver.h b/include/libimobiledevice/debugserver.h new file mode 100644 index 0000000..809b97f --- /dev/null +++ b/include/libimobiledevice/debugserver.h @@ -0,0 +1,272 @@ +/** + * @file libimobiledevice/debugserver.h + * @brief Communicate with debugserver on the device. + * \internal + * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IDEBUGSERVER_H +#define IDEBUGSERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the debugserver service */ +#define DEBUGSERVER_SERVICE_NAME "com.apple.debugserver" +/** Service identifier passed to lockdownd_start_service() to start the secure debugserver service */ +#define DEBUGSERVER_SECURE_SERVICE_NAME DEBUGSERVER_SERVICE_NAME ".DVTSecureSocketProxy" + +/** Error Codes */ +typedef enum { + DEBUGSERVER_E_SUCCESS = 0, + DEBUGSERVER_E_INVALID_ARG = -1, + DEBUGSERVER_E_MUX_ERROR = -2, + DEBUGSERVER_E_SSL_ERROR = -3, + DEBUGSERVER_E_RESPONSE_ERROR = -4, + DEBUGSERVER_E_TIMEOUT = -5, + DEBUGSERVER_E_UNKNOWN_ERROR = -256 +} debugserver_error_t; + +typedef struct debugserver_client_private debugserver_client_private; /**< \private */ +typedef debugserver_client_private *debugserver_client_t; /**< The client handle. */ + +typedef struct debugserver_command_private debugserver_command_private; /**< \private */ +typedef debugserver_command_private *debugserver_command_t; /**< The command handle. */ + +/* Interface */ + +/** + * Connects to the debugserver service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * debugserver_client_t upon successful return. Must be freed using + * debugserver_client_free() after use. + * + * @return DEBUGSERVER_E_SUCCESS on success, DEBUGSERVER_E_INVALID_ARG when + * client is NULL, or an DEBUGSERVER_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_new(idevice_t device, lockdownd_service_descriptor_t service, debugserver_client_t * client); + +/** + * Starts a new debugserver service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * debugserver_client_t upon successful return. Must be freed using + * debugserver_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return DEBUGSERVER_E_SUCCESS on success, or an DEBUGSERVER_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_start_service(idevice_t device, debugserver_client_t * client, const char* label); + +/** + * Disconnects a debugserver client from the device and frees up the + * debugserver client data. + * + * @param client The debugserver client to disconnect and free. + * + * @return DEBUGSERVER_E_SUCCESS on success, DEBUGSERVER_E_INVALID_ARG when + * client is NULL, or an DEBUGSERVER_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_free(debugserver_client_t client); + +/** + * Sends raw data using the given debugserver service client. + * + * @param client The debugserver client to use for sending + * @param data Data to send + * @param size Size of the data to send + * @param sent Number of bytes sent (can be NULL to ignore) + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when one or more parameters are + * invalid, or DEBUGSERVER_E_UNKNOWN_ERROR when an unspecified + * error occurs. + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_send(debugserver_client_t client, const char* data, uint32_t size, uint32_t *sent); + +/** + * Receives raw data using the given debugserver client with specified timeout. + * + * @param client The debugserver client to use for receiving + * @param data Buffer that will be filled with the data received + * @param size Number of bytes to receive + * @param received Number of bytes received (can be NULL to ignore) + * @param timeout Maximum time in milliseconds to wait for data. + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when one or more parameters are + * invalid, DEBUGSERVER_E_MUX_ERROR when a communication error + * occurs, DEBUGSERVER_E_TIMEOUT when the timeout is reached, + * or DEBUGSERVER_E_UNKNOWN_ERROR when an unspecified + * error occurs. + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_receive_with_timeout(debugserver_client_t client, char *data, uint32_t size, uint32_t *received, unsigned int timeout); + +/** + * Receives raw data from the debugserver service. + * + * @param client The debugserver client + * @param data Buffer that will be filled with the data received + * @param size Number of bytes to receive + * @param received Number of bytes received (can be NULL to ignore) + * @note The default read timeout is 10 seconds. + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_receive(debugserver_client_t client, char *data, uint32_t size, uint32_t *received); + +/** + * Sends a command to the debugserver service. + * + * @param client The debugserver client + * @param command Command to process and send + * @param response Response received for the command (can be NULL to ignore) + * @param response_size Pointer to receive response size. Set to NULL to ignore. + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when client or command is NULL + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_send_command(debugserver_client_t client, debugserver_command_t command, char** response, size_t* response_size); + +/** + * Receives and parses response of debugserver service. + * + * @param client The debugserver client + * @param response Response received for last command (can be NULL to ignore) + * @param response_size Pointer to receive response size. Set to NULL to ignore. + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_receive_response(debugserver_client_t client, char** response, size_t* response_size); + +/** + * Controls status of ACK mode when sending commands or receiving responses. + * + * @see debugserver_client_send_command, debugserver_client_receive_response + * + * @param client The debugserver client + * @param enabled A boolean flag indicating whether the internal ACK mode + * handling should be enabled or disabled. + * + * @return DEBUGSERVER_E_SUCCESS on success, or an DEBUGSERVER_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_set_ack_mode(debugserver_client_t client, int enabled); + +/** + * Sets behavior when awaiting a response from the server. + * + * @see debugserver_client_send_command, debugserver_client_receive_response, + * debugserver_client_receive + * + * @param client The debugserver client + * @param cancel_receive A function pointer that will be called approximately + * every receive_loop_timeout milliseconds; the function should return a + * boolean flag specifying whether to stop waiting for a response. If NULL, + * behaves as if it always returns true. + * @param receive_loop_timeout Time in milliseconds between calls to + * cancel_receive. + * + * @return DEBUGSERVER_E_SUCCESS on success, or an DEBUGSERVER_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_set_receive_params(debugserver_client_t client, int (*cancel_receive)(), int receive_loop_timeout); + +/** + * Sets the argv which launches an app. + * + * @param client The debugserver client + * @param argc Number of arguments + * @param argv Array starting with the executable to be run followed by it's arguments + * @param response Response received for the command (can be NULL to ignore) + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_set_argv(debugserver_client_t client, int argc, char* argv[], char** response); + +/** + * Adds or sets an environment variable. + * + * @param client The debugserver client + * @param env The environment variable in "KEY=VALUE" notation + * @param response Response received for the command (can be NULL to ignore) + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_client_set_environment_hex_encoded(debugserver_client_t client, const char* env, char** response); + +/** + * Creates and initializes a new command object. + * + * @param name The name of the command which is sent in plain text + * @param argv Array of tokens for the command ment to be encoded + * @param argc Number of items in the token array + * @param command New command object + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when name or command is NULL + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_command_new(const char* name, int argc, char* argv[], debugserver_command_t* command); + +/** + * Frees memory of command object. + * + * @param command The command object + * + * @return DEBUGSERVER_E_SUCCESS on success, + * DEBUGSERVER_E_INVALID_ARG when command is NULL + */ +LIBIMOBILEDEVICE_API debugserver_error_t debugserver_command_free(debugserver_command_t command); + +/** + * Encodes a string into hex notation. + * + * @param buffer String to encode into hex notiation + * @param encoded_buffer The buffer receives a hex encoded string + * @param encoded_length Length of the hex encoded string + */ +LIBIMOBILEDEVICE_API void debugserver_encode_string(const char* buffer, char** encoded_buffer, uint32_t* encoded_length); + +/** + * Decodes a hex encoded string. + * + * @param encoded_buffer The buffer with a hex encoded string + * @param encoded_length Length of the encoded buffer + * @param buffer Decoded string to be freed by the caller + */ +LIBIMOBILEDEVICE_API void debugserver_decode_string(const char *encoded_buffer, size_t encoded_length, char** buffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/diagnostics_relay.h b/include/libimobiledevice/diagnostics_relay.h new file mode 100644 index 0000000..6ab47a9 --- /dev/null +++ b/include/libimobiledevice/diagnostics_relay.h @@ -0,0 +1,228 @@ +/** + * @file libimobiledevice/diagnostics_relay.h + * @brief Request iOS diagnostic information from device. + * \internal + * + * Copyright (c) 2012-2014 Martin Szulecki, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IDIAGNOSTICS_RELAY_H +#define IDIAGNOSTICS_RELAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the diagnostics relay service */ +#define DIAGNOSTICS_RELAY_SERVICE_NAME "com.apple.mobile.diagnostics_relay" + +/** Error Codes */ +typedef enum { + DIAGNOSTICS_RELAY_E_SUCCESS = 0, + DIAGNOSTICS_RELAY_E_INVALID_ARG = -1, + DIAGNOSTICS_RELAY_E_PLIST_ERROR = -2, + DIAGNOSTICS_RELAY_E_MUX_ERROR = -3, + DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST = -4, + DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR = -256 +} diagnostics_relay_error_t; + +/** Action type for #diagnostics_relay_restart and #diagnostics_relay_shutdown */ +typedef enum { + DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT = 1 << 1, + DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_PASS = 1 << 2, + DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_FAIL = 1 << 3 +} diagnostics_relay_action_t; + +#define DIAGNOSTICS_RELAY_REQUEST_TYPE_ALL "All" /**< Query all available diagnostics */ +#define DIAGNOSTICS_RELAY_REQUEST_TYPE_WIFI "WiFi" /**< Query WiFi diagnostics */ +#define DIAGNOSTICS_RELAY_REQUEST_TYPE_GAS_GAUGE "GasGauge" /**< Query GasGauge diagnostics */ +#define DIAGNOSTICS_RELAY_REQUEST_TYPE_NAND "NAND" /**< Query NAND diagnostics */ + +typedef struct diagnostics_relay_client_private diagnostics_relay_client_private; /**< \private */ +typedef diagnostics_relay_client_private *diagnostics_relay_client_t; /**< The client handle. */ + +/** + * Connects to the diagnostics_relay service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Reference that will point to a newly allocated + * diagnostics_relay_client_t upon successful return. + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when one of the parameters is invalid, + * or DIAGNOSTICS_RELAY_E_MUX_ERROR when the connection failed. + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, diagnostics_relay_client_t *client); + +/** + * Starts a new diagnostics_relay service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * diagnostics_relay_client_t upon successful return. Must be freed using + * diagnostics_relay_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, or an DIAGNOSTICS_RELAY_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_client_start_service(idevice_t device, diagnostics_relay_client_t* client, const char* label); + +/** + * Disconnects a diagnostics_relay client from the device and frees up the + * diagnostics_relay client data. + * + * @param client The diagnostics_relay client to disconnect and free. + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when one of client or client->parent + * is invalid, or DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR when the was an + * error freeing the parent property_list_service client. + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_client_free(diagnostics_relay_client_t client); + + +/** + * Sends the Goodbye request signaling the end of communication. + * + * @param client The diagnostics_relay client + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_goodbye(diagnostics_relay_client_t client); + +/** + * Puts the device into deep sleep mode and disconnects from host. + * + * @param client The diagnostics_relay client + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_sleep(diagnostics_relay_client_t client); + +/** + * Restart the device and optionally show a user notification. + * + * @param client The diagnostics_relay client + * @param flags A binary flag combination of + * DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT to wait until + * diagnostics_relay_client_free() disconnects before execution and + * DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_FAIL to show a "FAIL" dialog + * or DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_PASS to show an "OK" dialog + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_restart(diagnostics_relay_client_t client, diagnostics_relay_action_t flags); + +/** + * Shutdown of the device and optionally show a user notification. + * + * @param client The diagnostics_relay client + * @param flags A binary flag combination of + * DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT to wait until + * diagnostics_relay_client_free() disconnects before execution and + * DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_FAIL to show a "FAIL" dialog + * or DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_PASS to show an "OK" dialog + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_shutdown(diagnostics_relay_client_t client, diagnostics_relay_action_t flags); + +/** + * Request diagnostics information for a given type. + * + * @param client The diagnostics_relay client + * @param type The type or domain to query for diagnostics. Some known values + * are "All", "WiFi", "GasGauge", and "NAND". + * @param diagnostics A pointer to plist_t that will receive the diagnostics information. + * The consumer has to free the allocated memory with plist_free() when no longer needed. + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_request_diagnostics(diagnostics_relay_client_t client, const char* type, plist_t* diagnostics); + +/** + * Query one or multiple MobileGestalt keys. + * + * @param client The diagnostics_relay client + * @param keys A PLIST_ARRAY with the keys to query. + * @param result A pointer to plist_t that will receive the result. The consumer + * has to free the allocated memory with plist_free() when no longer needed. + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_query_mobilegestalt(diagnostics_relay_client_t client, plist_t keys, plist_t* result); + +/** + * Query an IORegistry entry of a given class. + * + * @param client The diagnostics_relay client + * @param entry_name The IORegistry entry name to query. + * @param entry_class The IORegistry class to query. + * @param result A pointer to plist_t that will receive the result. The consumer + * has to free the allocated memory with plist_free() when no longer needed. + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_query_ioregistry_entry(diagnostics_relay_client_t client, const char* entry_name, const char* entry_class, plist_t* result); + +/** + * Query an IORegistry plane. + * + * @param client The diagnostics_relay client + * @param plane The IORegistry plane name to query. + * @param result A pointer to plist_t that will receive the result. The consumer + * has to free the allocated memory with plist_free() when no longer needed. + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client is NULL, + * DIAGNOSTICS_RELAY_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API diagnostics_relay_error_t diagnostics_relay_query_ioregistry_plane(diagnostics_relay_client_t client, const char* plane, plist_t* result); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/file_relay.h b/include/libimobiledevice/file_relay.h index 52d4758..00773b8 100644 --- a/include/libimobiledevice/file_relay.h +++ b/include/libimobiledevice/file_relay.h @@ -3,6 +3,8 @@ * @brief Retrieve compressed CPIO archives. * \internal * + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2014 Aaron Burghardt All Rights Reserved. * Copyright (c) 2010 Nikias Bassen All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -28,29 +30,134 @@ extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define FILE_RELAY_E_SUCCESS 0 -#define FILE_RELAY_E_INVALID_ARG -1 -#define FILE_RELAY_E_PLIST_ERROR -2 -#define FILE_RELAY_E_MUX_ERROR -3 -#define FILE_RELAY_E_INVALID_SOURCE -4 -#define FILE_RELAY_E_STAGING_EMPTY -5 +/** Service identifier passed to lockdownd_start_service() to start the file relay service */ +#define FILE_RELAY_SERVICE_NAME "com.apple.mobile.file_relay" -#define FILE_RELAY_E_UNKNOWN_ERROR -256 -/*@}*/ +/** Error Codes */ +typedef enum { + FILE_RELAY_E_SUCCESS = 0, + FILE_RELAY_E_INVALID_ARG = -1, + FILE_RELAY_E_PLIST_ERROR = -2, + FILE_RELAY_E_MUX_ERROR = -3, + FILE_RELAY_E_INVALID_SOURCE = -4, + FILE_RELAY_E_STAGING_EMPTY = -5, + FILE_RELAY_E_PERMISSION_DENIED = -6, + FILE_RELAY_E_UNKNOWN_ERROR = -256 +} file_relay_error_t; -/** Represents an error code. */ -typedef int16_t file_relay_error_t; - -typedef struct file_relay_client_private file_relay_client_private; +typedef struct file_relay_client_private file_relay_client_private; /**< \private */ typedef file_relay_client_private *file_relay_client_t; /**< The client handle. */ -file_relay_error_t file_relay_client_new(idevice_t device, uint16_t port, file_relay_client_t *client); -file_relay_error_t file_relay_client_free(file_relay_client_t client); +/** + * Connects to the file_relay service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Reference that will point to a newly allocated + * file_relay_client_t upon successful return. + * + * @return FILE_RELAY_E_SUCCESS on success, + * FILE_RELAY_E_INVALID_ARG when one of the parameters is invalid, + * or FILE_RELAY_E_MUX_ERROR when the connection failed. + */ +LIBIMOBILEDEVICE_API file_relay_error_t file_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, file_relay_client_t *client); + +/** + * Starts a new file_relay service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * file_relay_client_t upon successful return. Must be freed using + * file_relay_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return FILE_RELAY_E_SUCCESS on success, or an FILE_RELAY_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API file_relay_error_t file_relay_client_start_service(idevice_t device, file_relay_client_t* client, const char* label); + +/** + * Disconnects a file_relay client from the device and frees up the file_relay + * client data. + * + * @param client The file_relay client to disconnect and free. + * + * @return FILE_RELAY_E_SUCCESS on success, + * FILE_RELAY_E_INVALID_ARG when one of client or client->parent + * is invalid, or FILE_RELAY_E_UNKNOWN_ERROR when the was an error + * freeing the parent property_list_service client. + */ +LIBIMOBILEDEVICE_API file_relay_error_t file_relay_client_free(file_relay_client_t client); + -file_relay_error_t file_relay_request_sources(file_relay_client_t client, const char **sources, idevice_connection_t *connection); +/** + * Request data for the given sources. + * + * @param client The connected file_relay client. + * @param sources A NULL-terminated list of sources to retrieve. + * Valid sources are: + * - AppleSupport + * - Network + * - VPN + * - WiFi + * - UserDatabases + * - CrashReporter + * - tmp + * - SystemConfiguration + * @param connection The connection that has to be used for receiving the + * data using idevice_connection_receive(). The connection will be closed + * automatically by the device, but use file_relay_client_free() to clean + * up properly. + * + * @note WARNING: Don't call this function without reading the data afterwards. + * A directory mobile_file_relay.XXXX used for creating the archive will + * remain in the /tmp directory otherwise. + * + * @return FILE_RELAY_E_SUCCESS on succes, FILE_RELAY_E_INVALID_ARG when one or + * more parameters are invalid, FILE_RELAY_E_MUX_ERROR if a communication + * error occurs, FILE_RELAY_E_PLIST_ERROR when the received result is NULL + * or is not a valid plist, FILE_RELAY_E_INVALID_SOURCE if one or more + * sources are invalid, FILE_RELAY_E_STAGING_EMPTY if no data is available + * for the given sources, or FILE_RELAY_E_UNKNOWN_ERROR otherwise. + */ +LIBIMOBILEDEVICE_API file_relay_error_t file_relay_request_sources(file_relay_client_t client, const char **sources, idevice_connection_t *connection); + +/** + * Request data for the given sources. Calls file_relay_request_sources_timeout() with + * a timeout of 60000 milliseconds (60 seconds). + * + * @param client The connected file_relay client. + * @param sources A NULL-terminated list of sources to retrieve. + * Valid sources are: + * - AppleSupport + * - Network + * - VPN + * - WiFi + * - UserDatabases + * - CrashReporter + * - tmp + * - SystemConfiguration + * @param connection The connection that has to be used for receiving the + * data using idevice_connection_receive(). The connection will be closed + * automatically by the device, but use file_relay_client_free() to clean + * up properly. + * @param timeout Maximum time in milliseconds to wait for data. + * + * @note WARNING: Don't call this function without reading the data afterwards. + * A directory mobile_file_relay.XXXX used for creating the archive will + * remain in the /tmp directory otherwise. + * + * @return FILE_RELAY_E_SUCCESS on succes, FILE_RELAY_E_INVALID_ARG when one or + * more parameters are invalid, FILE_RELAY_E_MUX_ERROR if a communication + * error occurs, FILE_RELAY_E_PLIST_ERROR when the received result is NULL + * or is not a valid plist, FILE_RELAY_E_INVALID_SOURCE if one or more + * sources are invalid, FILE_RELAY_E_STAGING_EMPTY if no data is available + * for the given sources, or FILE_RELAY_E_UNKNOWN_ERROR otherwise. + */ +LIBIMOBILEDEVICE_API file_relay_error_t file_relay_request_sources_timeout(file_relay_client_t client, const char **sources, idevice_connection_t *connection, unsigned int timeout); #ifdef __cplusplus } diff --git a/include/libimobiledevice/heartbeat.h b/include/libimobiledevice/heartbeat.h new file mode 100644 index 0000000..4074b8b --- /dev/null +++ b/include/libimobiledevice/heartbeat.h @@ -0,0 +1,137 @@ +/** + * @file libimobiledevice/heartbeat.h + * @brief Send "heartbeat" to device to allow service connections over network. + * \internal + * + * Copyright (c) 2013-2014 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IHEARTBEAT_H +#define IHEARTBEAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the heartbeat service */ +#define HEARTBEAT_SERVICE_NAME "com.apple.mobile.heartbeat" + +/** Error Codes */ +typedef enum { + HEARTBEAT_E_SUCCESS = 0, + HEARTBEAT_E_INVALID_ARG = -1, + HEARTBEAT_E_PLIST_ERROR = -2, + HEARTBEAT_E_MUX_ERROR = -3, + HEARTBEAT_E_SSL_ERROR = -4, + HEARTBEAT_E_NOT_ENOUGH_DATA = -5, + HEARTBEAT_E_TIMEOUT = -6, + HEARTBEAT_E_UNKNOWN_ERROR = -256 +} heartbeat_error_t; + +typedef struct heartbeat_client_private heartbeat_client_private; /**< \private */ +typedef heartbeat_client_private *heartbeat_client_t; /**< The client handle. */ + +/** + * Connects to the heartbeat service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * heartbeat_client_t upon successful return. Must be freed using + * heartbeat_client_free() after use. + * + * @return HEARTBEAT_E_SUCCESS on success, HEARTBEAT_E_INVALID_ARG when + * client is NULL, or an HEARTBEAT_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API heartbeat_error_t heartbeat_client_new(idevice_t device, lockdownd_service_descriptor_t service, heartbeat_client_t * client); + +/** + * Starts a new heartbeat service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * heartbeat_client_t upon successful return. Must be freed using + * heartbeat_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return HEARTBEAT_E_SUCCESS on success, or an HEARTBEAT_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API heartbeat_error_t heartbeat_client_start_service(idevice_t device, heartbeat_client_t * client, const char* label); + +/** + * Disconnects a heartbeat client from the device and frees up the + * heartbeat client data. + * + * @param client The heartbeat client to disconnect and free. + * + * @return HEARTBEAT_E_SUCCESS on success, HEARTBEAT_E_INVALID_ARG when + * client is NULL, or an HEARTBEAT_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API heartbeat_error_t heartbeat_client_free(heartbeat_client_t client); + + +/** + * Sends a plist to the service. + * + * @param client The heartbeat client + * @param plist The plist to send + * + * @return HEARTBEAT_E_SUCCESS on success, + * HEARTBEAT_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API heartbeat_error_t heartbeat_send(heartbeat_client_t client, plist_t plist); + +/** + * Receives a plist from the service. + * + * @param client The heartbeat client + * @param plist The plist to store the received data + * + * @return HEARTBEAT_E_SUCCESS on success, + * HEARTBEAT_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API heartbeat_error_t heartbeat_receive(heartbeat_client_t client, plist_t * plist); + +/** + * Receives a plist using the given heartbeat client. + * + * @param client The heartbeat client to use for receiving + * @param plist pointer to a plist_t that will point to the received plist + * upon successful return + * @param timeout_ms Maximum time in milliseconds to wait for data. + * + * @return HEARTBEAT_E_SUCCESS on success, + * HEARTBEAT_E_INVALID_ARG when client or *plist is NULL, + * HEARTBEAT_E_NOT_ENOUGH_DATA when not enough data + * received, HEARTBEAT_E_TIMEOUT when the connection times out, + * HEARTBEAT_E_PLIST_ERROR when the received data cannot be + * converted to a plist, HEARTBEAT_E_MUX_ERROR when a + * communication error occurs, or HEARTBEAT_E_UNKNOWN_ERROR + * when an unspecified error occurs. + */ +LIBIMOBILEDEVICE_API heartbeat_error_t heartbeat_receive_with_timeout(heartbeat_client_t client, plist_t * plist, uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/house_arrest.h b/include/libimobiledevice/house_arrest.h index 04290f1..f9ba68a 100644 --- a/include/libimobiledevice/house_arrest.h +++ b/include/libimobiledevice/house_arrest.h @@ -1,8 +1,9 @@ /** * @file libimobiledevice/house_arrest.h - * @brief Access AppStore application folders and their contents. + * @brief Access app folders and their contents. * \internal * + * Copyright (c) 2013-2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -20,42 +21,157 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef HOUSE_ARREST_H -#define HOUSE_ARREST_H +#ifndef IHOUSE_ARREST_H +#define IHOUSE_ARREST_H #ifdef __cplusplus extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> #include <libimobiledevice/afc.h> -/** @name Error Codes */ -/*@{*/ -#define HOUSE_ARREST_E_SUCCESS 0 -#define HOUSE_ARREST_E_INVALID_ARG -1 -#define HOUSE_ARREST_E_PLIST_ERROR -2 -#define HOUSE_ARREST_E_CONN_FAILED -3 -#define HOUSE_ARREST_E_INVALID_MODE -4 +/** Service identifier passed to lockdownd_start_service() to start the house arrest service */ +#define HOUSE_ARREST_SERVICE_NAME "com.apple.mobile.house_arrest" -#define HOUSE_ARREST_E_UNKNOWN_ERROR -256 -/*@}*/ +/** Error Codes */ +typedef enum { + HOUSE_ARREST_E_SUCCESS = 0, + HOUSE_ARREST_E_INVALID_ARG = -1, + HOUSE_ARREST_E_PLIST_ERROR = -2, + HOUSE_ARREST_E_CONN_FAILED = -3, + HOUSE_ARREST_E_INVALID_MODE = -4, + HOUSE_ARREST_E_UNKNOWN_ERROR = -256 +} house_arrest_error_t; -/** Represents an error code. */ -typedef int16_t house_arrest_error_t; - -typedef struct house_arrest_client_private house_arrest_client_private; +typedef struct house_arrest_client_private house_arrest_client_private; /**< \private */ typedef house_arrest_client_private *house_arrest_client_t; /**< The client handle. */ /* Interface */ -house_arrest_error_t house_arrest_client_new(idevice_t device, uint16_t port, house_arrest_client_t *client); -house_arrest_error_t house_arrest_client_free(house_arrest_client_t client); -house_arrest_error_t house_arrest_send_request(house_arrest_client_t client, plist_t dict); -house_arrest_error_t house_arrest_send_command(house_arrest_client_t client, const char *command, const char *appid); -house_arrest_error_t house_arrest_get_result(house_arrest_client_t client, plist_t *dict); +/** + * Connects to the house_arrest service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * housearrest_client_t upon successful return. + * + * @return HOUSE_ARREST_E_SUCCESS on success, HOUSE_ARREST_E_INVALID_ARG when + * client is NULL, or an HOUSE_ARREST_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API house_arrest_error_t house_arrest_client_new(idevice_t device, lockdownd_service_descriptor_t service, house_arrest_client_t *client); + +/** + * Starts a new house_arrest service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * house_arrest_client_t upon successful return. Must be freed using + * house_arrest_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return HOUSE_ARREST_E_SUCCESS on success, or an HOUSE_ARREST_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API house_arrest_error_t house_arrest_client_start_service(idevice_t device, house_arrest_client_t* client, const char* label); + +/** + * Disconnects an house_arrest client from the device and frees up the + * house_arrest client data. + * + * @note After using afc_client_new_from_house_arrest_client(), make sure + * you call afc_client_free() before calling this function to ensure + * a proper cleanup. Do not call this function if you still need to + * perform AFC operations since it will close the connection. + * + * @param client The house_arrest client to disconnect and free. + * + * @return HOUSE_ARREST_E_SUCCESS on success, HOUSE_ARREST_E_INVALID_ARG when + * client is NULL, or an HOUSE_ARREST_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API house_arrest_error_t house_arrest_client_free(house_arrest_client_t client); + + +/** + * Sends a generic request to the connected house_arrest service. + * + * @param client The house_arrest client to use. + * @param dict The request to send as a plist of type PLIST_DICT. + * + * @note If this function returns HOUSE_ARREST_E_SUCCESS it does not mean + * that the request was successful. To check for success or failure you + * need to call house_arrest_get_result(). + * @see house_arrest_get_result + * + * @return HOUSE_ARREST_E_SUCCESS if the request was successfully sent, + * HOUSE_ARREST_E_INVALID_ARG if client or dict is invalid, + * HOUSE_ARREST_E_PLIST_ERROR if dict is not a plist of type PLIST_DICT, + * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, + * or HOUSE_ARREST_E_CONN_FAILED if a connection error occurred. + */ +LIBIMOBILEDEVICE_API house_arrest_error_t house_arrest_send_request(house_arrest_client_t client, plist_t dict); + +/** + * Send a command to the connected house_arrest service. + * Calls house_arrest_send_request() internally. + * + * @param client The house_arrest client to use. + * @param command The command to send. Currently, only VendContainer and + * VendDocuments are known. + * @param appid The application identifier to pass along with the . + * + * @note If this function returns HOUSE_ARREST_E_SUCCESS it does not mean + * that the command was successful. To check for success or failure you + * need to call house_arrest_get_result(). + * @see house_arrest_get_result + * + * @return HOUSE_ARREST_E_SUCCESS if the command was successfully sent, + * HOUSE_ARREST_E_INVALID_ARG if client, command, or appid is invalid, + * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, + * or HOUSE_ARREST_E_CONN_FAILED if a connection error occurred. + */ +LIBIMOBILEDEVICE_API house_arrest_error_t house_arrest_send_command(house_arrest_client_t client, const char *command, const char *appid); + +/** + * Retrieves the result of a previously sent house_arrest_request_* request. + * + * @param client The house_arrest client to use + * @param dict Pointer that will be set to a plist containing the result to + * the last performed operation. It holds a key 'Status' with the value + * 'Complete' on success or a key 'Error' with an error description as + * value. The caller is responsible for freeing the returned plist. + * + * @return HOUSE_ARREST_E_SUCCESS if a result plist was retrieved, + * HOUSE_ARREST_E_INVALID_ARG if client is invalid, + * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, + * or HOUSE_ARREST_E_CONN_FAILED if a connection error occurred. + */ +LIBIMOBILEDEVICE_API house_arrest_error_t house_arrest_get_result(house_arrest_client_t client, plist_t *dict); + -afc_error_t afc_client_new_from_house_arrest_client(house_arrest_client_t client, afc_client_t *afc_client); +/** + * Creates an AFC client using the given house_arrest client's connection + * allowing file access to a specific application directory requested by + * functions like house_arrest_request_vendor_documents(). + * + * @param client The house_arrest client to use. + * @param afc_client Pointer that will be set to a newly allocated afc_client_t + * upon successful return. + * + * @note After calling this function the house_arrest client will go in an + * AFC mode that will only allow calling house_arrest_client_free(). + * Only call house_arrest_client_free() if all AFC operations have + * completed since it will close the connection. + * + * @return AFC_E_SUCCESS if the afc client was successfully created, + * AFC_E_INVALID_ARG if client is invalid or was already used to create + * an afc client, or an AFC_E_* error code returned by + * afc_client_new_with_service_client(). + */ +LIBIMOBILEDEVICE_API afc_error_t afc_client_new_from_house_arrest_client(house_arrest_client_t client, afc_client_t *afc_client); #ifdef __cplusplus } diff --git a/include/libimobiledevice/installation_proxy.h b/include/libimobiledevice/installation_proxy.h index f5f00e8..44331aa 100644 --- a/include/libimobiledevice/installation_proxy.h +++ b/include/libimobiledevice/installation_proxy.h @@ -3,7 +3,10 @@ * @brief Manage applications on a device. * \internal * - * Copyright (c) 2009 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2014 Christophe Fergeau All Rights Reserved. + * Copyright (c) 2009-2012 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,54 +23,480 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef INSTALLATION_PROXY_H -#define INSTALLATION_PROXY_H +#ifndef IINSTALLATION_PROXY_H +#define IINSTALLATION_PROXY_H #ifdef __cplusplus extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> -#include <glib.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define INSTPROXY_E_SUCCESS 0 -#define INSTPROXY_E_INVALID_ARG -1 -#define INSTPROXY_E_PLIST_ERROR -2 -#define INSTPROXY_E_CONN_FAILED -3 -#define INSTPROXY_E_OP_IN_PROGRESS -4 -#define INSTPROXY_E_OP_FAILED -5 +/** Service identifier passed to lockdownd_start_service() to start the installation proxy service */ +#define INSTPROXY_SERVICE_NAME "com.apple.mobile.installation_proxy" -#define INSTPROXY_E_UNKNOWN_ERROR -256 -/*@}*/ +/** Error Codes */ +typedef enum { + /* custom */ + INSTPROXY_E_SUCCESS = 0, + INSTPROXY_E_INVALID_ARG = -1, + INSTPROXY_E_PLIST_ERROR = -2, + INSTPROXY_E_CONN_FAILED = -3, + INSTPROXY_E_OP_IN_PROGRESS = -4, + INSTPROXY_E_OP_FAILED = -5, + INSTPROXY_E_RECEIVE_TIMEOUT = -6, + /* native */ + INSTPROXY_E_ALREADY_ARCHIVED = -7, + INSTPROXY_E_API_INTERNAL_ERROR = -8, + INSTPROXY_E_APPLICATION_ALREADY_INSTALLED = -9, + INSTPROXY_E_APPLICATION_MOVE_FAILED = -10, + INSTPROXY_E_APPLICATION_SINF_CAPTURE_FAILED = -11, + INSTPROXY_E_APPLICATION_SANDBOX_FAILED = -12, + INSTPROXY_E_APPLICATION_VERIFICATION_FAILED = -13, + INSTPROXY_E_ARCHIVE_DESTRUCTION_FAILED = -14, + INSTPROXY_E_BUNDLE_VERIFICATION_FAILED = -15, + INSTPROXY_E_CARRIER_BUNDLE_COPY_FAILED = -16, + INSTPROXY_E_CARRIER_BUNDLE_DIRECTORY_CREATION_FAILED = -17, + INSTPROXY_E_CARRIER_BUNDLE_MISSING_SUPPORTED_SIMS = -18, + INSTPROXY_E_COMM_CENTER_NOTIFICATION_FAILED = -19, + INSTPROXY_E_CONTAINER_CREATION_FAILED = -20, + INSTPROXY_E_CONTAINER_P0WN_FAILED = -21, + INSTPROXY_E_CONTAINER_REMOVAL_FAILED = -22, + INSTPROXY_E_EMBEDDED_PROFILE_INSTALL_FAILED = -23, + INSTPROXY_E_EXECUTABLE_TWIDDLE_FAILED = -24, + INSTPROXY_E_EXISTENCE_CHECK_FAILED = -25, + INSTPROXY_E_INSTALL_MAP_UPDATE_FAILED = -26, + INSTPROXY_E_MANIFEST_CAPTURE_FAILED = -27, + INSTPROXY_E_MAP_GENERATION_FAILED = -28, + INSTPROXY_E_MISSING_BUNDLE_EXECUTABLE = -29, + INSTPROXY_E_MISSING_BUNDLE_IDENTIFIER = -30, + INSTPROXY_E_MISSING_BUNDLE_PATH = -31, + INSTPROXY_E_MISSING_CONTAINER = -32, + INSTPROXY_E_NOTIFICATION_FAILED = -33, + INSTPROXY_E_PACKAGE_EXTRACTION_FAILED = -34, + INSTPROXY_E_PACKAGE_INSPECTION_FAILED = -35, + INSTPROXY_E_PACKAGE_MOVE_FAILED = -36, + INSTPROXY_E_PATH_CONVERSION_FAILED = -37, + INSTPROXY_E_RESTORE_CONTAINER_FAILED = -38, + INSTPROXY_E_SEATBELT_PROFILE_REMOVAL_FAILED = -39, + INSTPROXY_E_STAGE_CREATION_FAILED = -40, + INSTPROXY_E_SYMLINK_FAILED = -41, + INSTPROXY_E_UNKNOWN_COMMAND = -42, + INSTPROXY_E_ITUNES_ARTWORK_CAPTURE_FAILED = -43, + INSTPROXY_E_ITUNES_METADATA_CAPTURE_FAILED = -44, + INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW = -45, + INSTPROXY_E_DEVICE_FAMILY_NOT_SUPPORTED = -46, + INSTPROXY_E_PACKAGE_PATCH_FAILED = -47, + INSTPROXY_E_INCORRECT_ARCHITECTURE = -48, + INSTPROXY_E_PLUGIN_COPY_FAILED = -49, + INSTPROXY_E_BREADCRUMB_FAILED = -50, + INSTPROXY_E_BREADCRUMB_UNLOCK_FAILED = -51, + INSTPROXY_E_GEOJSON_CAPTURE_FAILED = -52, + INSTPROXY_E_NEWSSTAND_ARTWORK_CAPTURE_FAILED = -53, + INSTPROXY_E_MISSING_COMMAND = -54, + INSTPROXY_E_NOT_ENTITLED = -55, + INSTPROXY_E_MISSING_PACKAGE_PATH = -56, + INSTPROXY_E_MISSING_CONTAINER_PATH = -57, + INSTPROXY_E_MISSING_APPLICATION_IDENTIFIER = -58, + INSTPROXY_E_MISSING_ATTRIBUTE_VALUE = -59, + INSTPROXY_E_LOOKUP_FAILED = -60, + INSTPROXY_E_DICT_CREATION_FAILED = -61, + INSTPROXY_E_INSTALL_PROHIBITED = -62, + INSTPROXY_E_UNINSTALL_PROHIBITED = -63, + INSTPROXY_E_MISSING_BUNDLE_VERSION = -64, + INSTPROXY_E_UNKNOWN_ERROR = -256 +} instproxy_error_t; -/** Represents an error code. */ -typedef int16_t instproxy_error_t; - -typedef struct instproxy_client_private instproxy_client_private; +typedef struct instproxy_client_private instproxy_client_private; /**< \private */ typedef instproxy_client_private *instproxy_client_t; /**< The client handle. */ -/** Reports the status of the given operation */ -typedef void (*instproxy_status_cb_t) (const char *operation, plist_t status, void *user_data); +/** Reports the status response of the given command */ +typedef void (*instproxy_status_cb_t) (plist_t command, plist_t status, void *user_data); /* Interface */ -instproxy_error_t instproxy_client_new(idevice_t device, uint16_t port, instproxy_client_t *client); -instproxy_error_t instproxy_client_free(instproxy_client_t client); - -instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result); -instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); -instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); -instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); - -instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result); -instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); -instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); -instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); - -plist_t instproxy_client_options_new(); -void instproxy_client_options_add(plist_t client_options, ...) G_GNUC_NULL_TERMINATED; -void instproxy_client_options_free(plist_t client_options); + +/** + * Connects to the installation_proxy service on the specified device. + * + * @param device The device to connect to + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * instproxy_client_t upon successful return. + * + * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error value + * when an error occurred. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, instproxy_client_t *client); + +/** + * Starts a new installation_proxy service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * instproxy_client_t upon successful return. Must be freed using + * instproxy_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_start_service(idevice_t device, instproxy_client_t * client, const char* label); + +/** + * Disconnects an installation_proxy client from the device and frees up the + * installation_proxy client data. + * + * @param client The installation_proxy client to disconnect and free. + * + * @return INSTPROXY_E_SUCCESS on success + * or INSTPROXY_E_INVALID_ARG if client is NULL. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_free(instproxy_client_t client); + +/** + * List installed applications. This function runs synchronously. + * + * @param client The connected installation_proxy client + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Valid client options include: + * "ApplicationType" -> "System" + * "ApplicationType" -> "User" + * "ApplicationType" -> "Internal" + * "ApplicationType" -> "Any" + * @param result Pointer that will be set to a plist that will hold an array + * of PLIST_DICT holding information about the applications found. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result); + +/** + * List pages of installed applications in a callback. + * + * @param client The connected installation_proxy client + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Valid client options include: + * "ApplicationType" -> "System" + * "ApplicationType" -> "User" + * "ApplicationType" -> "Internal" + * "ApplicationType" -> "Any" + * @param status_cb Callback function to process each page of application + * information. Passing a callback is required. + * @param user_data Callback data passed to status_cb. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_browse_with_callback(instproxy_client_t client, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * Lookup information about specific applications from the device. + * + * @param client The connected installation_proxy client + * @param appids An array of bundle identifiers that MUST have a terminating + * NULL entry or NULL to lookup all. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Currently there are no known client options, so pass NULL here. + * @param result Pointer that will be set to a plist containing a PLIST_DICT + * holding requested information about the application or NULL on errors. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_lookup(instproxy_client_t client, const char** appids, plist_t client_options, plist_t *result); + +/** + * Install an application on the device. + * + * @param client The connected installation_proxy client + * @param pkg_path Path of the installation package (inside the AFC jail) + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Valid options include: + * "iTunesMetadata" -> PLIST_DATA + * "ApplicationSINF" -> PLIST_DATA + * "PackageType" -> "Developer" + * If PackageType -> Developer is specified, then pkg_path points to + * an .app directory instead of an install package. + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * @param user_data Callback data passed to status_cb. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occurring during the command has to be + * handled inside the specified callback function. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * Upgrade an application on the device. This function is nearly the same as + * instproxy_install; the difference is that the installation progress on the + * device is faster if the application is already installed. + * + * @param client The connected installation_proxy client + * @param pkg_path Path of the installation package (inside the AFC jail) + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Valid options include: + * "iTunesMetadata" -> PLIST_DATA + * "ApplicationSINF" -> PLIST_DATA + * "PackageType" -> "Developer" + * If PackageType -> Developer is specified, then pkg_path points to + * an .app directory instead of an install package. + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * @param user_data Callback data passed to status_cb. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occurring during the command has to be + * handled inside the specified callback function. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * Uninstall an application from the device. + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the app to uninstall + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Currently there are no known client options, so pass NULL here. + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * @param user_data Callback data passed to status_cb. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occurring during the command has to be + * handled inside the specified callback function. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * List archived applications. This function runs synchronously. + * + * @see instproxy_archive + * + * @param client The connected installation_proxy client + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Currently there are no known client options, so pass NULL here. + * @param result Pointer that will be set to a plist containing a PLIST_DICT + * holding information about the archived applications found. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result); + +/** + * Archive an application on the device. + * This function tells the device to make an archive of the specified + * application. This results in the device creating a ZIP archive in the + * 'ApplicationArchives' directory and uninstalling the application. + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the app to archive. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Valid options include: + * "SkipUninstall" -> Boolean + * "ArchiveType" -> "ApplicationOnly" + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * @param user_data Callback data passed to status_cb. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occurring during the command has to be + * handled inside the specified callback function. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * Restore a previously archived application on the device. + * This function is the counterpart to instproxy_archive. + * @see instproxy_archive + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the app to restore. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Valid options include: + * "ArchiveType" -> "DocumentsOnly" + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * @param user_data Callback data passed to status_cb. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occurring during the command has to be + * handled inside the specified callback function. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * Removes a previously archived application from the device. + * This function removes the ZIP archive from the 'ApplicationArchives' + * directory. + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the archived app to remove. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Currently there are no known client options, so passing NULL is fine. + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * @param user_data Callback data passed to status_cb. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occurring during the command has to be + * handled inside the specified callback function. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * Checks a device for certain capabilities. + * + * @param client The connected installation_proxy client + * @param capabilities An array of char* with capability names that MUST have a + * terminating NULL entry. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + * Currently there are no known client options, so pass NULL here. + * @param result Pointer that will be set to a plist containing a PLIST_DICT + * holding information if the capabilities matched or NULL on errors. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occurred. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, const char** capabilities, plist_t client_options, plist_t *result); + +/* Helper */ + +/** + * Gets the name from a command dictionary. + * + * @param command The dictionary describing the command. + * @param name Pointer to store the name of the command. + */ +LIBIMOBILEDEVICE_API void instproxy_command_get_name(plist_t command, char** name); + +/** + * Gets the name of a status. + * + * @param status The dictionary status response to use. + * @param name Pointer to store the name of the status. + */ +LIBIMOBILEDEVICE_API void instproxy_status_get_name(plist_t status, char **name); + +/** + * Gets error name, code and description from a response if available. + * + * @param status The dictionary status response to use. + * @param name Pointer to store the name of an error. + * @param description Pointer to store error description text if available. + * The caller is reponsible for freeing the allocated buffer after use. + * If NULL is passed no description will be returned. + * @param code Pointer to store the returned error code if available. + * If NULL is passed no error code will be returned. + * + * @return INSTPROXY_E_SUCCESS if no error is found or an INSTPROXY_E_* error + * value matching the error that ẃas found in the status. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code); + +/** + * Gets total and current item information from a browse response if available. + * + * @param status The dictionary status response to use. + * @param total Pointer to store the total number of items. + * @param current_index Pointer to store the current index of all browsed items. + * @param current_amount Pointer to store the amount of items in the + * current list. + * @param list Pointer to store a newly allocated plist with items. + * The caller is reponsible for freeing the list after use. + * If NULL is passed no list will be returned. If NULL is returned no + * list was found in the status. + */ +LIBIMOBILEDEVICE_API void instproxy_status_get_current_list(plist_t status, uint64_t* total, uint64_t* current_index, uint64_t* current_amount, plist_t* list); + + +/** + * Gets progress in percentage from a status if available. + * + * @param status The dictionary status response to use. + * @param percent Pointer to an int to store the progress in percent (0-100) + * or -1 if no progress was found in the status. + */ +LIBIMOBILEDEVICE_API void instproxy_status_get_percent_complete(plist_t status, int *percent); + +/** + * Creates a new client_options plist. + * + * @return A new plist_t of type PLIST_DICT. + */ +LIBIMOBILEDEVICE_API plist_t instproxy_client_options_new(void); + +/** + * Adds one or more new key:value pairs to the given client_options. + * + * @param client_options The client options to modify. + * @param ... KEY, VALUE, [KEY, VALUE], NULL + * + * @note The keys and values passed are expected to be strings, except for the + * keys "ApplicationSINF", "iTunesMetadata", "ReturnAttributes" which are + * expecting a plist_t node as value and "SkipUninstall" expects int. + */ +LIBIMOBILEDEVICE_API void instproxy_client_options_add(plist_t client_options, ...); + +/** + * Adds attributes to the given client_options to filter browse results. + * + * @param client_options The client options to modify. + * @param ... VALUE, VALUE, [VALUE], NULL + * + * @note The values passed are expected to be strings. + */ +LIBIMOBILEDEVICE_API void instproxy_client_options_set_return_attributes(plist_t client_options, ...); + +/** + * Frees client_options plist. + * + * @param client_options The client options plist to free. Does nothing if NULL + * is passed. + */ +LIBIMOBILEDEVICE_API void instproxy_client_options_free(plist_t client_options); + +/** + * Queries the device for the path of an application. + * + * @param client The connected installation proxy client. + * @param bundle_id ApplicationIdentifier of app to retrieve the path for. + * @param path Pointer to store the device path for the application + * which is set to NULL if it could not be determined. + * + * @return INSTPROXY_E_SUCCESS on success, INSTPROXY_E_OP_FAILED if + * the path could not be determined or an INSTPROXY_E_* error + * value if an error occurred. + */ +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_get_path_for_bundle_identifier(instproxy_client_t client, const char* bundle_id, char** path); #ifdef __cplusplus } diff --git a/include/libimobiledevice/libimobiledevice.h b/include/libimobiledevice/libimobiledevice.h index d0923d6..a9d270b 100644 --- a/include/libimobiledevice/libimobiledevice.h +++ b/include/libimobiledevice/libimobiledevice.h @@ -3,25 +3,28 @@ * @brief Device/Connection handling and communication * \internal * + * Copyright (c) 2010-2019 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2014 Christophe Fergeau All Rights Reserved. * Copyright (c) 2008 Jonathan Beck All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef LIBIMOBILEDEVICE_H -#define LIBIMOBILEDEVICE_H +#ifndef IMOBILEDEVICE_H +#define IMOBILEDEVICE_H #ifdef __cplusplus extern "C" { @@ -32,72 +35,377 @@ extern "C" { #include <sys/stat.h> #include <plist/plist.h> -/** @name Error Codes */ -/*@{*/ -#define IDEVICE_E_SUCCESS 0 -#define IDEVICE_E_INVALID_ARG -1 -#define IDEVICE_E_UNKNOWN_ERROR -2 -#define IDEVICE_E_NO_DEVICE -3 -#define IDEVICE_E_NOT_ENOUGH_DATA -4 -#define IDEVICE_E_BAD_HEADER -5 -#define IDEVICE_E_SSL_ERROR -6 -/*@}*/ - -/** Represents an error code. */ -typedef int16_t idevice_error_t; - -typedef struct idevice_private idevice_private; +#ifndef LIBIMOBILEDEVICE_API + #ifdef LIBIMOBILEDEVICE_STATIC + #define LIBIMOBILEDEVICE_API + #elif defined(_WIN32) + #define LIBIMOBILEDEVICE_API __declspec(dllimport) + #else + #define LIBIMOBILEDEVICE_API + #endif +#endif + +/** Error Codes */ +typedef enum { + IDEVICE_E_SUCCESS = 0, + IDEVICE_E_INVALID_ARG = -1, + IDEVICE_E_UNKNOWN_ERROR = -2, + IDEVICE_E_NO_DEVICE = -3, + IDEVICE_E_NOT_ENOUGH_DATA = -4, + IDEVICE_E_CONNREFUSED = -5, + IDEVICE_E_SSL_ERROR = -6, + IDEVICE_E_TIMEOUT = -7 +} idevice_error_t; + +typedef struct idevice_private idevice_private; /**< \private */ typedef idevice_private *idevice_t; /**< The device handle. */ -typedef struct idevice_connection_private idevice_connection_private; +typedef struct idevice_connection_private idevice_connection_private; /**< \private */ typedef idevice_connection_private *idevice_connection_t; /**< The connection handle. */ -/* generic */ -void idevice_set_debug_level(int level); +/** Options for idevice_new_with_options() */ +enum idevice_options { + IDEVICE_LOOKUP_USBMUX = 1 << 1, /**< include USBMUX devices during lookup */ + IDEVICE_LOOKUP_NETWORK = 1 << 2, /**< include network devices during lookup */ + IDEVICE_LOOKUP_PREFER_NETWORK = 1 << 3 /**< prefer network connection if device is available via USBMUX *and* network */ +}; + +/** Type of connection a device is available on */ +enum idevice_connection_type { + CONNECTION_USBMUXD = 1, /**< device is available via USBMUX */ + CONNECTION_NETWORK /**< device is available via network */ +}; + +/** Device information returned by #idevice_get_device_list_extended API */ +struct idevice_info { + char *udid; /**< UDID of the device */ + enum idevice_connection_type conn_type; /**< Type of connection the device is available on */ + void* conn_data; /**< Connection data, depending on the connection type */ +}; +typedef struct idevice_info* idevice_info_t; /* discovery (events/asynchronous) */ /** The event type for device add or removal */ enum idevice_event_type { - IDEVICE_DEVICE_ADD = 1, - IDEVICE_DEVICE_REMOVE + IDEVICE_DEVICE_ADD = 1, /**< device was added */ + IDEVICE_DEVICE_REMOVE, /**< device was removed */ + IDEVICE_DEVICE_PAIRED /**< device completed pairing process */ }; /* event data structure */ -/** Provides information about the occured event. */ +/** Provides information about the occurred event. */ typedef struct { enum idevice_event_type event; /**< The event type. */ - const char *uuid; /**< The device unique id. */ - int conn_type; /**< The connection type. Currently only 1 for usbmuxd. */ + const char *udid; /**< The device unique id. */ + enum idevice_connection_type conn_type; /**< The connection type. */ } idevice_event_t; /* event callback function prototype */ /** Callback to notifiy if a device was added or removed. */ typedef void (*idevice_event_cb_t) (const idevice_event_t *event, void *user_data); +/** Event subscription context type */ +typedef struct idevice_subscription_context* idevice_subscription_context_t; + /* functions */ -idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data); -idevice_error_t idevice_event_unsubscribe(); + +/** + * Set the level of debugging. + * + * @param level Set to 0 for no debug output or 1 to enable debug output. + */ +LIBIMOBILEDEVICE_API void idevice_set_debug_level(int level); + +/** + * Subscribe a callback function that will be called when device add/remove + * events occur. + * + * @param context A pointer to a idevice_subscription_context_t that will be + * set upon creation of the subscription. The returned context must be + * passed to idevice_events_unsubscribe() to unsubscribe the callback. + * @param callback Callback function to call. + * @param user_data Application-specific data passed as parameter + * to the registered callback function. + * + * @return IDEVICE_E_SUCCESS on success or an error value when an error occurred. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_events_subscribe(idevice_subscription_context_t *context, idevice_event_cb_t callback, void *user_data); + +/** + * Unsubscribe the event callback function that has been registered with + * idevice_events_subscribe(). + * + * @param context A valid context as returned from idevice_events_subscribe(). + * + * @return IDEVICE_E_SUCCESS on success or an error value when an error occurred. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_events_unsubscribe(idevice_subscription_context_t context); + +/** + * (DEPRECATED) Register a callback function that will be called when device add/remove + * events occur. + * + * @deprecated Use idevice_events_subscribe() instead. + * + * @param callback Callback function to call. + * @param user_data Application-specific data passed as parameter + * to the registered callback function. + * + * @return IDEVICE_E_SUCCESS on success or an error value when an error occurred. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data); + +/** + * (DEPRECATED) Release the event callback function that has been registered with + * idevice_event_subscribe(). + * + * @deprecated Use idevice_events_unsubscribe() instead. + * + * @return IDEVICE_E_SUCCESS on success or an error value when an error occurred. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_event_unsubscribe(void); /* discovery (synchronous) */ -idevice_error_t idevice_get_device_list(char ***devices, int *count); -idevice_error_t idevice_device_list_free(char **devices); + +/** + * Get a list of UDIDs of currently available devices (USBMUX devices only). + * + * @param devices List of UDIDs of devices that are currently available. + * This list is terminated by a NULL pointer. + * @param count Number of devices found. + * + * @return IDEVICE_E_SUCCESS on success or an error value when an error occurred. + * + * @note This function only returns the UDIDs of USBMUX devices. To also include + * network devices in the list, use idevice_get_device_list_extended(). + * @see idevice_get_device_list_extended + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_get_device_list(char ***devices, int *count); + +/** + * Free a list of device UDIDs. + * + * @param devices List of UDIDs to free. + * + * @return Always returnes IDEVICE_E_SUCCESS. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_device_list_free(char **devices); + +/** + * Get a list of currently available devices + * + * @param devices List of idevice_info_t records with device information. + * This list is terminated by a NULL pointer. + * @param count Number of devices included in the list. + * + * @return IDEVICE_E_SUCCESS on success or an error value when an error occurred. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_get_device_list_extended(idevice_info_t **devices, int *count); + +/** + * Free an extended device list retrieved through idevice_get_device_list_extended(). + * + * @param devices Device list to free. + * + * @return IDEVICE_E_SUCCESS on success or an error value when an error occurred. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_device_list_extended_free(idevice_info_t *devices); /* device structure creation and destruction */ -idevice_error_t idevice_new(idevice_t *device, const char *uuid); -idevice_error_t idevice_free(idevice_t device); + +/** + * Creates an idevice_t structure for the device specified by UDID, + * if the device is available (USBMUX devices only). + * + * @note The resulting idevice_t structure has to be freed with + * idevice_free() if it is no longer used. + * If you need to connect to a device available via network, use + * idevice_new_with_options() and include IDEVICE_LOOKUP_NETWORK in options. + * + * @see idevice_new_with_options + * + * @param device Upon calling this function, a pointer to a location of type + * idevice_t. On successful return, this location will be populated. + * @param udid The UDID to match. + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_new(idevice_t *device, const char *udid); + +/** + * Creates an idevice_t structure for the device specified by UDID, + * if the device is available, with the given lookup options. + * + * @note The resulting idevice_t structure has to be freed with + * idevice_free() if it is no longer used. + * + * @param device Upon calling this function, a pointer to a location of type + * idevice_t. On successful return, this location will be populated. + * @param udid The UDID to match. + * @param options Specifies what connection types should be considered + * when looking up devices. Accepts bitwise or'ed values of idevice_options. + * If 0 (no option) is specified it will default to IDEVICE_LOOKUP_USBMUX. + * To lookup both USB and network-connected devices, pass + * IDEVICE_LOOKUP_USBMUX | IDEVICE_LOOKUP_NETWORK. If a device is available + * both via USBMUX *and* network, it will select the USB connection. + * This behavior can be changed by adding IDEVICE_LOOKUP_PREFER_NETWORK + * to the options in which case it will select the network connection. + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_new_with_options(idevice_t *device, const char *udid, enum idevice_options options); + +/** + * Cleans up an idevice structure, then frees the structure itself. + * + * @param device idevice_t to free. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_free(idevice_t device); /* connection/disconnection */ -idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connection_t *connection); -idevice_error_t idevice_disconnect(idevice_connection_t connection); + +/** + * Set up a connection to the given device. + * + * @param device The device to connect to. + * @param port The destination port to connect to. + * @param connection Pointer to an idevice_connection_t that will be filled + * with the necessary data of the connection. + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connection_t *connection); + +/** + * Disconnect from the device and clean up the connection structure. + * + * @param connection The connection to close. + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_disconnect(idevice_connection_t connection); /* communication */ -idevice_error_t idevice_connection_send(idevice_connection_t connection, const char *data, uint32_t len, uint32_t *sent_bytes); -idevice_error_t idevice_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout); -idevice_error_t idevice_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes); + +/** + * Send data to a device via the given connection. + * + * @param connection The connection to send data over. + * @param data Buffer with data to send. + * @param len Size of the buffer to send. + * @param sent_bytes Pointer to an uint32_t that will be filled + * with the number of bytes actually sent. + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connection_send(idevice_connection_t connection, const char *data, uint32_t len, uint32_t *sent_bytes); + +/** + * Receive data from a device via the given connection. + * This function will return after the given timeout even if no data has been + * received. + * + * @param connection The connection to receive data from. + * @param data Buffer that will be filled with the received data. + * This buffer has to be large enough to hold len bytes. + * @param len Buffer size or number of bytes to receive. + * @param recv_bytes Number of bytes actually received. + * @param timeout Timeout in milliseconds after which this function should + * return even if no data has been received. + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout); + +/** + * Receive data from a device via the given connection. + * This function is like idevice_connection_receive_timeout, but with a + * predefined reasonable timeout. + * + * @param connection The connection to receive data from. + * @param data Buffer that will be filled with the received data. + * This buffer has to be large enough to hold len bytes. + * @param len Buffer size or number of bytes to receive. + * @param recv_bytes Number of bytes actually received. + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes); + +/** + * Enables SSL for the given connection. + * + * @param connection The connection to enable SSL for. + * + * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection + * is NULL or connection->ssl_data is non-NULL, or IDEVICE_E_SSL_ERROR when + * SSL initialization, setup, or handshake fails. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection); + +/** + * Disable SSL for the given connection. + * + * @param connection The connection to disable SSL for. + * + * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection + * is NULL. This function also returns IDEVICE_E_SUCCESS when SSL is not + * enabled and does no further error checking on cleanup. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection); + +/** + * Disable bypass SSL for the given connection without sending out terminate messages. + * + * @param connection The connection to disable SSL for. + * @param sslBypass if true ssl connection will not be terminated but just cleaned up, allowing + * plain text data going on underlying connection + * + * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection + * is NULL. This function also returns IDEVICE_E_SUCCESS when SSL is not + * enabled and does no further error checking on cleanup. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connection_disable_bypass_ssl(idevice_connection_t connection, uint8_t sslBypass); + + +/** + * Get the underlying file descriptor for a connection + * + * @param connection The connection to get fd of + * @param fd Pointer to an int where the fd is stored + * + * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_connection_get_fd(idevice_connection_t connection, int *fd); /* misc */ -idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle); -idevice_error_t idevice_get_uuid(idevice_t device, char **uuid); + +/** + * Gets the handle or (USBMUX device id) of the device. + * + * @param device The device to get the USBMUX device id for. + * @param handle Pointer to a uint32_t that will be set to the USBMUX handle value. + * + * @return IDEVICE_E_SUCCESS on success, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle); + +/** + * Gets the Unique Device ID for the device. + * + * @param device The device to get the Unique Device ID for. + * @param udid Pointer that will be set to an allocated buffer with the device UDID. The consumer is responsible for releasing the allocated memory. + * + * @return IDEVICE_E_SUCCESS on success, otherwise an error code. + */ +LIBIMOBILEDEVICE_API idevice_error_t idevice_get_udid(idevice_t device, char **udid); + +/** + * Returns a static string of the libimobiledevice version. + * + * @return The libimobiledevice version as static ascii string + */ +LIBIMOBILEDEVICE_API const char* libimobiledevice_version(); #ifdef __cplusplus } diff --git a/include/libimobiledevice/lockdown.h b/include/libimobiledevice/lockdown.h index 97df6b0..21669ef 100644 --- a/include/libimobiledevice/lockdown.h +++ b/include/libimobiledevice/lockdown.h @@ -3,8 +3,10 @@ * @brief Manage device preferences, start services, pairing and activation. * \internal * + * Copyright (c) 2009-2014 Martin S. All Rights Reserved. + * Copyright (c) 2014 Koby Boyango All Rights Reserved. + * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. - * Copyright (c) 2009 Martin S. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,8 +23,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef LOCKDOWN_H -#define LOCKDOWN_H +#ifndef ILOCKDOWN_H +#define ILOCKDOWN_H #ifdef __cplusplus extern "C" { @@ -30,74 +32,543 @@ extern "C" { #include <libimobiledevice/libimobiledevice.h> -/** @name Error Codes */ -/*@{*/ -#define LOCKDOWN_E_SUCCESS 0 -#define LOCKDOWN_E_INVALID_ARG -1 -#define LOCKDOWN_E_INVALID_CONF -2 -#define LOCKDOWN_E_PLIST_ERROR -3 -#define LOCKDOWN_E_PAIRING_FAILED -4 -#define LOCKDOWN_E_SSL_ERROR -5 -#define LOCKDOWN_E_DICT_ERROR -6 -#define LOCKDOWN_E_START_SERVICE_FAILED -7 -#define LOCKDOWN_E_NOT_ENOUGH_DATA -8 -#define LOCKDOWN_E_SET_VALUE_PROHIBITED -9 -#define LOCKDOWN_E_GET_VALUE_PROHIBITED -10 -#define LOCKDOWN_E_REMOVE_VALUE_PROHIBITED -11 -#define LOCKDOWN_E_MUX_ERROR -12 -#define LOCKDOWN_E_ACTIVATION_FAILED -13 -#define LOCKDOWN_E_PASSWORD_PROTECTED -14 -#define LOCKDOWN_E_NO_RUNNING_SESSION -15 -#define LOCKDOWN_E_INVALID_HOST_ID -16 -#define LOCKDOWN_E_INVALID_SERVICE -17 -#define LOCKDOWN_E_INVALID_ACTIVATION_RECORD -18 - -#define LOCKDOWN_E_UNKNOWN_ERROR -256 -/*@}*/ - -/** Represents an error code. */ -typedef int16_t lockdownd_error_t; - -typedef struct lockdownd_client_private lockdownd_client_private; +/** Error Codes */ +typedef enum { + /* custom */ + LOCKDOWN_E_SUCCESS = 0, + LOCKDOWN_E_INVALID_ARG = -1, + LOCKDOWN_E_INVALID_CONF = -2, + LOCKDOWN_E_PLIST_ERROR = -3, + LOCKDOWN_E_PAIRING_FAILED = -4, + LOCKDOWN_E_SSL_ERROR = -5, + LOCKDOWN_E_DICT_ERROR = -6, + LOCKDOWN_E_RECEIVE_TIMEOUT = -7, + LOCKDOWN_E_MUX_ERROR = -8, + LOCKDOWN_E_NO_RUNNING_SESSION = -9, + /* native */ + LOCKDOWN_E_INVALID_RESPONSE = -10, + LOCKDOWN_E_MISSING_KEY = -11, + LOCKDOWN_E_MISSING_VALUE = -12, + LOCKDOWN_E_GET_PROHIBITED = -13, + LOCKDOWN_E_SET_PROHIBITED = -14, + LOCKDOWN_E_REMOVE_PROHIBITED = -15, + LOCKDOWN_E_IMMUTABLE_VALUE = -16, + LOCKDOWN_E_PASSWORD_PROTECTED = -17, + LOCKDOWN_E_USER_DENIED_PAIRING = -18, + LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING = -19, + LOCKDOWN_E_MISSING_HOST_ID = -20, + LOCKDOWN_E_INVALID_HOST_ID = -21, + LOCKDOWN_E_SESSION_ACTIVE = -22, + LOCKDOWN_E_SESSION_INACTIVE = -23, + LOCKDOWN_E_MISSING_SESSION_ID = -24, + LOCKDOWN_E_INVALID_SESSION_ID = -25, + LOCKDOWN_E_MISSING_SERVICE = -26, + LOCKDOWN_E_INVALID_SERVICE = -27, + LOCKDOWN_E_SERVICE_LIMIT = -28, + LOCKDOWN_E_MISSING_PAIR_RECORD = -29, + LOCKDOWN_E_SAVE_PAIR_RECORD_FAILED = -30, + LOCKDOWN_E_INVALID_PAIR_RECORD = -31, + LOCKDOWN_E_INVALID_ACTIVATION_RECORD = -32, + LOCKDOWN_E_MISSING_ACTIVATION_RECORD = -33, + LOCKDOWN_E_SERVICE_PROHIBITED = -34, + LOCKDOWN_E_ESCROW_LOCKED = -35, + LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION = -36, + LOCKDOWN_E_FMIP_PROTECTED = -37, + LOCKDOWN_E_MC_PROTECTED = -38, + LOCKDOWN_E_MC_CHALLENGE_REQUIRED = -39, + LOCKDOWN_E_UNKNOWN_ERROR = -256 +} lockdownd_error_t; + +typedef struct lockdownd_client_private lockdownd_client_private; /**< \private */ typedef lockdownd_client_private *lockdownd_client_t; /**< The client handle. */ struct lockdownd_pair_record { char *device_certificate; /**< The device certificate */ char *host_certificate; /**< The host certificate */ - char *host_id; /**< A unique HostID for the host computer */ char *root_certificate; /**< The root certificate */ + char *host_id; /**< A unique HostID for the host computer */ + char *system_buid; /**< A unique system id */ }; -/** A pair record holding device, host and root certificates along the host_id */ -typedef struct lockdownd_pair_record *lockdownd_pair_record_t; +/** pair record holding device, host and root certificates along the host_id */ +typedef struct lockdownd_pair_record *lockdownd_pair_record_t; /**< pair record */ + +/** service descriptor */ +struct lockdownd_service_descriptor { + uint16_t port; /**< port number the service was started on */ + uint8_t ssl_enabled; /**< an indicator if the service requires SSL */ + char* identifier; /**< identifier of the service */ +}; +typedef struct lockdownd_service_descriptor *lockdownd_service_descriptor_t; + +/** Callback types used in #lockdownd_cu_pairing_cb_t */ +typedef enum { + LOCKDOWN_CU_PAIRING_PIN_REQUESTED, /**< PIN requested: data_ptr is a char* buffer, and data_size points to the size of this buffer that must not be exceeded and has to be updated to the actual number of characters filled into the buffer. */ + LOCKDOWN_CU_PAIRING_DEVICE_INFO, /**< device information available: data_ptr is a plist_t, and data_size is ignored. The plist_t has to be copied if required, since it is freed when the callback function returns. */ + LOCKDOWN_CU_PAIRING_ERROR /**< pairing error message available: data_ptr is a NULL-terminated char* buffer containing the error message, and data_size is ignored. Buffer needs to be copied if it shall persist outside the callback. */ +} lockdownd_cu_pairing_cb_type_t; + +/* CU pairing callback function prototype */ +/** Callback used to supply the pairing PIN during a CU pairing session, + * and to report device information and pairing error messages. */ +typedef void (*lockdownd_cu_pairing_cb_t) (lockdownd_cu_pairing_cb_type_t cb_type, void *user_data, void* data_ptr, unsigned int* data_size); + /* Interface */ -lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *client, const char *label); -lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdownd_client_t *client, const char *label); -lockdownd_error_t lockdownd_client_free(lockdownd_client_t client); - -lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type); -lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *domain, const char *key, plist_t *value); -lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *domain, const char *key, plist_t value); -lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char *domain, const char *key); -lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *service, uint16_t *port); -lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char *host_id, char **session_id, int *ssl_enabled); -lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char *session_id); -lockdownd_error_t lockdownd_send(lockdownd_client_t client, plist_t plist); -lockdownd_error_t lockdownd_receive(lockdownd_client_t client, plist_t *plist); -lockdownd_error_t lockdownd_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record); -lockdownd_error_t lockdownd_validate_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record); -lockdownd_error_t lockdownd_unpair(lockdownd_client_t client, lockdownd_pair_record_t pair_record); -lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activation_record); -lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client); -lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client); -lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client); + +/** + * Creates a new lockdownd client for the device. + * + * @note This function does not pair with the device or start a session. This + * has to be done manually by the caller after the client is created. + * The device disconnects automatically if the lockdown connection idles + * for more than 10 seconds. Make sure to call lockdownd_client_free() as soon + * as the connection is no longer needed. + * + * @param device The device to create a lockdownd client for + * @param client The pointer to the location of the new lockdownd_client + * @param label The label to use for communication. Usually the program name. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *client, const char *label); + +/** + * Creates a new lockdownd client for the device and starts initial handshake. + * The handshake consists out of query_type, validate_pair, pair and + * start_session calls. It uses the internal pairing record management. + * + * @note The device disconnects automatically if the lockdown connection idles + * for more than 10 seconds. Make sure to call lockdownd_client_free() as soon + * as the connection is no longer needed. + * + * @param device The device to create a lockdownd client for + * @param client The pointer to the location of the new lockdownd_client + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL, + * LOCKDOWN_E_INVALID_CONF if configuration data is wrong + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdownd_client_t *client, const char *label); + +/** + * Closes the lockdownd client session if one is running and frees up the + * lockdownd_client struct. + * + * @param client The lockdown client + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_client_free(lockdownd_client_t client); + + +/** + * Query the type of the service daemon. Depending on whether the device is + * queried in normal mode or restore mode, different types will be returned. + * + * @param client The lockdownd client + * @param type The type returned by the service daemon. Pass NULL to ignore. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type); + +/** + * Retrieves a preferences plist using an optional domain and/or key name. + * + * @param client An initialized lockdownd client. + * @param domain The domain to query on or NULL for global domain + * @param key The key name to request or NULL to query for all keys + * @param value A plist node representing the result value node + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *domain, const char *key, plist_t *value); + +/** + * Sets a preferences value using a plist and optional by domain and/or key name. + * + * @param client an initialized lockdownd client. + * @param domain the domain to query on or NULL for global domain + * @param key the key name to set the value or NULL to set a value dict plist + * @param value a plist node of any node type representing the value to set + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client or + * value is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *domain, const char *key, plist_t value); + +/** + * Removes a preference node by domain and/or key name. + * + * @note: Use with caution as this could remove vital information on the device + * + * @param client An initialized lockdownd client. + * @param domain The domain to query on or NULL for global domain + * @param key The key name to remove or NULL remove all keys for the current domain + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char *domain, const char *key); + +/** + * Requests to start a service and retrieve it's port on success. + * + * @param client The lockdownd client + * @param identifier The identifier of the service to start + * @param service The service descriptor on success or NULL on failure + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if a parameter + * is NULL, LOCKDOWN_E_INVALID_SERVICE if the requested service is not known + * by the device, LOCKDOWN_E_START_SERVICE_FAILED if the service could not be + * started by the device + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service); + +/** + * Requests to start a service and retrieve it's port on success. + * Sends the escrow bag from the device's pair record. + * + * @param client The lockdownd client + * @param identifier The identifier of the service to start + * @param service The service descriptor on success or NULL on failure + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if a parameter + * is NULL, LOCKDOWN_E_INVALID_SERVICE if the requested service is not known + * by the device, LOCKDOWN_E_START_SERVICE_FAILED if the service could not because + * started by the device, LOCKDOWN_E_INVALID_CONF if the host id or escrow bag are + * missing from the device record. + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_start_service_with_escrow_bag(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service); + +/** + * Opens a session with lockdownd and switches to SSL mode if device wants it. + * + * @param client The lockdownd client + * @param host_id The HostID of the computer + * @param session_id The new session_id of the created session + * @param ssl_enabled Whether SSL communication is used in the session + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when a client + * or host_id is NULL, LOCKDOWN_E_PLIST_ERROR if the response plist had errors, + * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the supplied HostID, + * LOCKDOWN_E_SSL_ERROR if enabling SSL communication failed + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char *host_id, char **session_id, int *ssl_enabled); + +/** + * Closes the lockdownd session by sending the StopSession request. + * + * @see lockdownd_start_session + * + * @param client The lockdown client + * @param session_id The id of a running session + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char *session_id); + +/** + * Sends a plist to lockdownd. + * + * @note This function is low-level and should only be used if you need to send + * a new type of message. + * + * @param client The lockdownd client + * @param plist The plist to send + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client or + * plist is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_send(lockdownd_client_t client, plist_t plist); + +/** + * Receives a plist from lockdownd. + * + * @param client The lockdownd client + * @param plist The plist to store the received data + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client or + * plist is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_receive(lockdownd_client_t client, plist_t *plist); + +/** + * Pairs the device using the supplied pair record. + * + * @param client The lockdown client + * @param pair_record The pair record to use for pairing. If NULL is passed, then + * the pair records from the current machine are used. New records will be + * generated automatically when pairing is done for the first time. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL, + * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, + * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, + * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, + * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record); + + /** + * Pairs the device using the supplied pair record and passing the given options. + * + * @param client The lockdown client + * @param pair_record The pair record to use for pairing. If NULL is passed, then + * the pair records from the current machine are used. New records will be + * generated automatically when pairing is done for the first time. + * @param options The pairing options to pass. Can be NULL for no options. + * @param response If non-NULL a pointer to lockdownd's response dictionary is returned. + * The caller is responsible to free the response dictionary with plist_free(). + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL, + * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, + * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, + * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, + * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_pair_with_options(lockdownd_client_t client, lockdownd_pair_record_t pair_record, plist_t options, plist_t *response); + +/** + * Validates if the device is paired with the given HostID. If successful the + * specified host will become trusted host of the device indicated by the + * lockdownd preference named TrustedHostAttached. Otherwise the host must be + * paired using lockdownd_pair() first. + * + * @param client The lockdown client + * @param pair_record The pair record to validate pairing with. If NULL is + * passed, then the pair record is read from the internal pairing record + * management. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL, + * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, + * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, + * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, + * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_validate_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record); + +/** + * Unpairs the device with the given HostID and removes the pairing records + * from the device and host if the internal pairing record management is used. + * + * @param client The lockdown client + * @param pair_record The pair record to use for unpair. If NULL is passed, then + * the pair records from the current machine are used. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL, + * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, + * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, + * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, + * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_unpair(lockdownd_client_t client, lockdownd_pair_record_t pair_record); + +/** + * Activates the device. Only works within an open session. + * The ActivationRecord plist dictionary must be obtained using the + * activation protocol requesting from Apple's https webservice. + * + * @param client The lockdown client + * @param activation_record The activation record plist dictionary + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client or + * activation_record is NULL, LOCKDOWN_E_NO_RUNNING_SESSION if no session is + * open, LOCKDOWN_E_PLIST_ERROR if the received plist is broken, + * LOCKDOWN_E_ACTIVATION_FAILED if the activation failed, + * LOCKDOWN_E_INVALID_ACTIVATION_RECORD if the device reports that the + * activation_record is invalid + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activation_record); + +/** + * Deactivates the device, returning it to the locked “Activate with iTunes” + * screen. + * + * @param client The lockdown client + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL, + * LOCKDOWN_E_NO_RUNNING_SESSION if no session is open, + * LOCKDOWN_E_PLIST_ERROR if the received plist is broken + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client); + +/** + * Tells the device to immediately enter recovery mode. + * + * @param client The lockdown client + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client); + +/** + * Sends the Goodbye request to lockdownd signaling the end of communication. + * + * @param client The lockdown client + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client + * is NULL, LOCKDOWN_E_PLIST_ERROR if the device did not acknowledge the + * request + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client); + +/** + * Creates a CU pairing session for the current lockdown client. + * This is required to allow lockdownd_cu_send_request_and_get_reply(), + * lockdownd_get_value_cu() and lockdonwd_pair_cu() requests, and eventually + * allows to perform an actual wireless pairing. + * + * Through the callback function, the PIN displayed on the device has to be + * supplied during the process. Currently, only AppleTV devices have this + * capability. + * + * @param client The lockdown client to perform the CU pairing for + * @param pairing_callback Callback function that is used to supply the PIN + * for the pairing process, but also to receive device information or + * pairing error messages. + * @param cb_user_data User data that will be passed as additional argument + * to the callback function. + * @param host_info (Optional) A dictionary containing host information to + * send to the device when finalizing the CU pairing. The supplied + * values will override the default values gathered for the current host. + * @param acl (Optional) A dictionary containing ACL information. Currently + * only com.apple.ScreenCapture:true and com.apple.developer:true are known + * valid ACL values, which are used as default when NULL is passed. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if one of the + * parameters is invalid, LOCKDOWN_E_PAIRING_FAILED if the pairing failed, + * or a LOCKDOWN_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_cu_pairing_create(lockdownd_client_t client, lockdownd_cu_pairing_cb_t pairing_callback, void* cb_user_data, plist_t host_info, plist_t acl); + +/** + * Sends a request via lockdown client with established CU pairing session + * and attempts to retrieve a reply. This function is used internally + * by lockdownd_get_value_cu() and lockdownd_pair_cu(), but exposed here to + * allow custom requests being sent and their replies being received. + * + * @param client A lockdown client with an established CU pairing. + * @param request The request to perform. + * @param request_payload The payload for the request. + * @param reply (Optional) If not NULL, the plist_t will be set to the reply + * dictionary that has been received. Consumer is responsible to free it + * using plist_free() when no longer required. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if one of the + * parameters is invalid, LOCKDOWN_E_NO_RUNNING_SESSION if the current + * lockdown client does not have an established CU pairing session, + * or a LOCKDOWN_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_cu_send_request_and_get_reply(lockdownd_client_t client, const char* request, plist_t request_payload, plist_t* reply); + +/** + * Retrieves a value using an optional domain and/or key name from a lockdown + * client with established CU pairing session. + * + * This is used to retrieve values that are only accessible after a CU pairing + * has been established, and would otherwise only be accessible with a valid + * device pairing. + * + * @param client A lockdown client with an established CU pairing. + * @param domain The domain to query on or NULL for global domain + * @param key The key name to request or NULL to query for all keys + * @param value A plist node representing the result value node + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if one of the + * parameters is invalid, LOCKDOWN_E_NO_RUNNING_SESSION if the current + * lockdown client does not have an established CU pairing session, + * or a LOCKDOWN_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_value_cu(lockdownd_client_t client, const char* domain, const char* key, plist_t* value); + +/** + * Perform a device pairing with a lockdown client that has an established + * CU pairing session. + * + * @param client A lockdown client with an established CU pairing. + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG when client + * is NULL, LOCKDOWN_E_NO_RUNNING_SESSION if the current lockdown client + * does not have an established CU pairing session, or a LOCKDOWN_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_pair_cu(lockdownd_client_t client); + /* Helper */ -void lockdownd_client_set_label(lockdownd_client_t client, const char *label); -lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t control, char **uuid); -lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **device_name); -lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, char ***classes, int *count); -lockdownd_error_t lockdownd_data_classes_free(char **classes); + +/** + * Sets the label to send for requests to lockdownd. + * + * @param client The lockdown client + * @param label The label to set or NULL to disable sending a label + * + */ +LIBIMOBILEDEVICE_API void lockdownd_client_set_label(lockdownd_client_t client, const char *label); + +/** + * Returns the unique id of the device from lockdownd. + * + * @param client An initialized lockdownd client. + * @param udid Holds the unique id of the device. The caller is responsible + * for freeing the memory. + * + * @return LOCKDOWN_E_SUCCESS on success + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_device_udid(lockdownd_client_t client, char **udid); + +/** + * Retrieves the name of the device from lockdownd set by the user. + * + * @param client An initialized lockdownd client. + * @param device_name Holds the name of the device. The caller is + * responsible for freeing the memory. + * + * @return LOCKDOWN_E_SUCCESS on success + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **device_name); + +/** + * Calculates and returns the data classes the device supports from lockdownd. + * + * @param client An initialized lockdownd client. + * @param classes A pointer to store an array of class names. The caller is responsible + * for freeing the memory which can be done using mobilesync_data_classes_free(). + * @param count The number of items in the classes array. + * + * @return LOCKDOWN_E_SUCCESS on success, + * LOCKDOWN_E_INVALID_ARG when client is NULL, + * LOCKDOWN_E_NO_RUNNING_SESSION if no session is open, + * LOCKDOWN_E_PLIST_ERROR if the received plist is broken + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, char ***classes, int *count); + +/** + * Frees memory of an allocated array of data classes as returned by lockdownd_get_sync_data_classes() + * + * @param classes An array of class names to free. + * + * @return LOCKDOWN_E_SUCCESS on success + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_data_classes_free(char **classes); + +/** + * Frees memory of a service descriptor as returned by lockdownd_start_service() + * + * @param service A service descriptor instance to free. + * + * @return LOCKDOWN_E_SUCCESS on success + */ +LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_service_descriptor_free(lockdownd_service_descriptor_t service); + +/** + * Gets a readable error string for a given lockdown error code. + * + * @param err A lockdownd error code + * + * @returns A readable error string + */ +LIBIMOBILEDEVICE_API const char* lockdownd_strerror(lockdownd_error_t err); #ifdef __cplusplus } diff --git a/include/libimobiledevice/misagent.h b/include/libimobiledevice/misagent.h new file mode 100644 index 0000000..7981a8b --- /dev/null +++ b/include/libimobiledevice/misagent.h @@ -0,0 +1,168 @@ +/** + * @file libimobiledevice/misagent.h + * @brief Manage provisioning profiles. + * \internal + * + * Copyright (c) 2013-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2012 Nikias Bassen All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IMISAGENT_H +#define IMISAGENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the misagent service */ +#define MISAGENT_SERVICE_NAME "com.apple.misagent" + +/** Error Codes */ +typedef enum { + MISAGENT_E_SUCCESS = 0, + MISAGENT_E_INVALID_ARG = -1, + MISAGENT_E_PLIST_ERROR = -2, + MISAGENT_E_CONN_FAILED = -3, + MISAGENT_E_REQUEST_FAILED = -4, + MISAGENT_E_UNKNOWN_ERROR = -256 +} misagent_error_t; + +typedef struct misagent_client_private misagent_client_private; /**< \private */ +typedef misagent_client_private *misagent_client_t; /**< The client handle. */ + +/* Interface */ + +/** + * Connects to the misagent service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * misagent_client_t upon successful return. + * + * @return MISAGENT_E_SUCCESS on success, MISAGENT_E_INVALID_ARG when + * client is NULL, or an MISAGENT_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API misagent_error_t misagent_client_new(idevice_t device, lockdownd_service_descriptor_t service, misagent_client_t *client); + +/** + * Starts a new misagent service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * misagent_client_t upon successful return. Must be freed using + * misagent_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return MISAGENT_E_SUCCESS on success, or an MISAGENT_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API misagent_error_t misagent_client_start_service(idevice_t device, misagent_client_t* client, const char* label); + +/** + * Disconnects an misagent client from the device and frees up the + * misagent client data. + * + * @param client The misagent client to disconnect and free. + * + * @return MISAGENT_E_SUCCESS on success, MISAGENT_E_INVALID_ARG when + * client is NULL, or an MISAGENT_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API misagent_error_t misagent_client_free(misagent_client_t client); + + +/** + * Installs the given provisioning profile. Only works with valid profiles. + * + * @param client The connected misagent to use for installation + * @param profile The valid provisioning profile to install. This has to be + * passed as a PLIST_DATA, otherwise the function will fail. + * + * @return MISAGENT_E_SUCCESS on success, MISAGENT_E_INVALID_ARG when + * client is invalid, or an MISAGENT_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API misagent_error_t misagent_install(misagent_client_t client, plist_t profile); + +/** + * Retrieves all installed provisioning profiles (iOS 9.2.1 or below). + * + * @param client The connected misagent to use. + * @param profiles Pointer to a plist_t that will be set to a PLIST_ARRAY + * if the function is successful. + * + * @return MISAGENT_E_SUCCESS on success, MISAGENT_E_INVALID_ARG when + * client is invalid, or an MISAGENT_E_* error code otherwise. + * + * @note This API call only works with iOS 9.2.1 or below. + * For newer iOS versions use misagent_copy_all() instead. + * + * @note If no provisioning profiles are installed on the device, this function + * still returns MISAGENT_E_SUCCESS and profiles will just point to an + * empty array. + */ +LIBIMOBILEDEVICE_API misagent_error_t misagent_copy(misagent_client_t client, plist_t* profiles); + +/** + * Retrieves all installed provisioning profiles (iOS 9.3 or higher). + * + * @param client The connected misagent to use. + * @param profiles Pointer to a plist_t that will be set to a PLIST_ARRAY + * if the function is successful. + * + * @return MISAGENT_E_SUCCESS on success, MISAGENT_E_INVALID_ARG when + * client is invalid, or an MISAGENT_E_* error code otherwise. + * + * @note This API call only works with iOS 9.3 or higher. + * For older iOS versions use misagent_copy() instead. + * + * @note If no provisioning profiles are installed on the device, this function + * still returns MISAGENT_E_SUCCESS and profiles will just point to an + * empty array. + */ +LIBIMOBILEDEVICE_API misagent_error_t misagent_copy_all(misagent_client_t client, plist_t* profiles); + +/** + * Removes a given provisioning profile. + * + * @param client The connected misagent to use. + * @param profileID Identifier of the provisioning profile to remove. + * This is a UUID that can be obtained from the provisioning profile data. + * @see misagent_copy + * + * @return MISAGENT_E_SUCCESS on success, MISAGENT_E_INVALID_ARG when + * client is invalid, or an MISAGENT_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API misagent_error_t misagent_remove(misagent_client_t client, const char* profileID); + +/** + * Retrieves the status code from the last operation. + * + * @param client The misagent to use. + * + * @return -1 if client is invalid, or the status code from the last operation + */ +LIBIMOBILEDEVICE_API int misagent_get_status_code(misagent_client_t client); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/mobile_image_mounter.h b/include/libimobiledevice/mobile_image_mounter.h index 04c65d5..76bb61a 100644 --- a/include/libimobiledevice/mobile_image_mounter.h +++ b/include/libimobiledevice/mobile_image_mounter.h @@ -3,7 +3,8 @@ * @brief Mount developer/debug disk images on the device. * \internal * - * Copyright (c) 2010 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2014 Nikias Bassen All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,37 +21,252 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef MOBILE_IMAGE_MOUNTER_H -#define MOBILE_IMAGE_MOUNTER_H +#ifndef IMOBILE_IMAGE_MOUNTER_H +#define IMOBILE_IMAGE_MOUNTER_H #ifdef __cplusplus extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define MOBILE_IMAGE_MOUNTER_E_SUCCESS 0 -#define MOBILE_IMAGE_MOUNTER_E_INVALID_ARG -1 -#define MOBILE_IMAGE_MOUNTER_E_PLIST_ERROR -2 -#define MOBILE_IMAGE_MOUNTER_E_CONN_FAILED -3 +/** Service identifier passed to lockdownd_start_service() to start the mobile image mounter service */ +#define MOBILE_IMAGE_MOUNTER_SERVICE_NAME "com.apple.mobile.mobile_image_mounter" -#define MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR -256 -/*@}*/ +/** Error Codes */ +typedef enum { + MOBILE_IMAGE_MOUNTER_E_SUCCESS = 0, + MOBILE_IMAGE_MOUNTER_E_INVALID_ARG = -1, + MOBILE_IMAGE_MOUNTER_E_PLIST_ERROR = -2, + MOBILE_IMAGE_MOUNTER_E_CONN_FAILED = -3, + MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED = -4, + MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED = -5, + MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED = -6, + MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR = -256 +} mobile_image_mounter_error_t; -/** Represents an error code. */ -typedef int16_t mobile_image_mounter_error_t; - -typedef struct mobile_image_mounter_client_private mobile_image_mounter_client_private; +typedef struct mobile_image_mounter_client_private mobile_image_mounter_client_private; /**< \private */ typedef mobile_image_mounter_client_private *mobile_image_mounter_client_t; /**< The client handle. */ +/** callback for image upload */ +typedef ssize_t (*mobile_image_mounter_upload_cb_t) (void* buffer, size_t length, void *user_data); + /* Interface */ -mobile_image_mounter_error_t mobile_image_mounter_new(idevice_t device, uint16_t port, mobile_image_mounter_client_t *client); -mobile_image_mounter_error_t mobile_image_mounter_free(mobile_image_mounter_client_t client); -mobile_image_mounter_error_t mobile_image_mounter_lookup_image(mobile_image_mounter_client_t client, const char *image_type, plist_t *result); -mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const char *image_signature, uint16_t signature_length, const char *image_type, plist_t *result); -mobile_image_mounter_error_t mobile_image_mounter_hangup(mobile_image_mounter_client_t client); + +/** + * Connects to the mobile_image_mounter service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * mobile_image_mounter_client_t upon successful return. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if device is NULL, + * or MOBILE_IMAGE_MOUNTER_E_CONN_FAILED if the connection to the + * device could not be established. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_new(idevice_t device, lockdownd_service_descriptor_t service, mobile_image_mounter_client_t *client); + +/** + * Starts a new mobile_image_mounter service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * mobile_image_mounter_t upon successful return. Must be freed using + * mobile_image_mounter_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, or an MOBILE_IMAGE_MOUNTER_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_start_service(idevice_t device, mobile_image_mounter_client_t* client, const char* label); + +/** + * Disconnects a mobile_image_mounter client from the device and frees up the + * mobile_image_mounter client data. + * + * @param client The mobile_image_mounter client to disconnect and free. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if client is NULL. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_free(mobile_image_mounter_client_t client); + + +/** + * Tells if the image of ImageType is already mounted. + * + * @param client The client use + * @param image_type The type of the image to look up + * @param result Pointer to a plist that will receive the result of the + * operation. + * + * @note This function may return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the + * operation has failed. Check the resulting plist for further information. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, or an error code on error + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_lookup_image(mobile_image_mounter_client_t client, const char *image_type, plist_t *result); + +/** + * Uploads an image with an optional signature to the device. + * + * @param client The connected mobile_image_mounter client. + * @param image_type Type of image that is being uploaded. + * @param image_size Total size of the image. + * @param signature Buffer with a signature of the image being uploaded. If + * NULL, no signature will be used. + * @param signature_size Total size of the image signature buffer. If 0, no + * signature will be used. + * @param upload_cb Callback function that gets the data chunks for uploading + * the image. + * @param userdata User defined data for the upload callback function. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on succes, or a + * MOBILE_IMAGE_MOUNTER_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_upload_image(mobile_image_mounter_client_t client, const char *image_type, size_t image_size, const unsigned char *signature, unsigned int signature_size, mobile_image_mounter_upload_cb_t upload_cb, void* userdata); + +/** + * Mounts an image on the device. + * + * @param client The connected mobile_image_mounter client. + * @param image_path The absolute path of the image to mount. The image must + * be present before calling this function. + * @param signature Pointer to a buffer holding the images' signature + * @param signature_size Length of the signature image_signature points to + * @param image_type Type of image to mount + * @param options A dictionary containing additional key/value pairs to add + * @param result Pointer to a plist that will receive the result of the + * operation. + * + * @note This function may return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the + * operation has failed. Check the resulting plist for further information. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if on ore more parameters are + * invalid, or another error code otherwise. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_mount_image_with_options(mobile_image_mounter_client_t client, const char *image_path, const unsigned char *signature, unsigned int signature_size, const char *image_type, plist_t options, plist_t *result); + +/** + * Mounts an image on the device. + * + * @param client The connected mobile_image_mounter client. + * @param image_path The absolute path of the image to mount. The image must + * be present before calling this function. + * @param signature Pointer to a buffer holding the images' signature + * @param signature_size Length of the signature image_signature points to + * @param image_type Type of image to mount + * @param result Pointer to a plist that will receive the result of the + * operation. + * + * @note This function may return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the + * operation has failed. Check the resulting plist for further information. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if on ore more parameters are + * invalid, or another error code otherwise. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const unsigned char *signature, unsigned int signature_size, const char *image_type, plist_t *result); + +/** + * Unmount a mounted image at given path on the device. + * + * @param client The connected mobile_image_mounter client. + * @param mount_path The mount path of the mounted image on the device. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or a MOBILE_IMAGE_MOUNTER_E_* error code on error. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_unmount_image(mobile_image_mounter_client_t client, const char *mount_path); + +/** + * Hangs up the connection to the mobile_image_mounter service. + * This functions has to be called before freeing up a mobile_image_mounter + * instance. If not, errors appear in the device's syslog. + * + * @param client The client to hang up + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if client is invalid, + * or another error code otherwise. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_hangup(mobile_image_mounter_client_t client); + +/** + * Query the developer mode status of the given device. + * + * @param client The connected mobile_image_mounter client. + * @param result A pointer to a plist_t that will be set to the resulting developer mode status dictionary. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or a MOBILE_IMAGE_MOUNTER_E_* error code on error. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_query_developer_mode_status(mobile_image_mounter_client_t client, plist_t *result); + +/** + * Query a personalization nonce for the given image type, used for personalized disk images (iOS 17+). + * This nonce is supposed to be added to the TSS request for the corresponding image. + * + * @param client The connected mobile_image_mounter client. + * @param image_type The image_type to get the personalization nonce for, usually `DeveloperDiskImage`. + * @param nonce Pointer that will be set to an allocated buffer with the nonce value. + * @param nonce_size Pointer to an unsigned int that will receive the size of the nonce value. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or a MOBILE_IMAGE_MOUNTER_E_* error code on error. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_query_nonce(mobile_image_mounter_client_t client, const char* image_type, unsigned char** nonce, unsigned int* nonce_size); + +/** + * Query personalization identitifers for the given image_type. + * + * @param client The connected mobile_image_mounter client. + * @param image_type The image_type to get the personalization identifiers for. Can be NULL. + * @param result A pointer to a plist_t that will be set to the resulting identifier dictionary. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or a MOBILE_IMAGE_MOUNTER_E_* error code on error. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_query_personalization_identifiers(mobile_image_mounter_client_t client, const char* image_type, plist_t *result); + +/** + * + * @param client The connected mobile_image_mounter client. + * @param image_type The image_type to get the personalization identifiers for. Can be NULL. + * @param signature The signature of the corresponding personalized image. + * @param signature_size The size of signature. + * @param manifest Pointer that will be set to an allocated buffer with the manifest data. + * @param manifest_size Pointer to an unsigned int that will be set to the size of the manifest data. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or a MOBILE_IMAGE_MOUNTER_E_* error code on error. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_query_personalization_manifest(mobile_image_mounter_client_t client, const char* image_type, const unsigned char* signature, unsigned int signature_size, unsigned char** manifest, unsigned int* manifest_size); + +/** + * Roll the personalization nonce. + * + * @param client The connected mobile_image_mounter client. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or a MOBILE_IMAGE_MOUNTER_E_* error code on error. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_roll_personalization_nonce(mobile_image_mounter_client_t client); + +/** + * Roll the Cryptex nonce. + * + * @param client The connected mobile_image_mounter client. + * + * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, + * or a MOBILE_IMAGE_MOUNTER_E_* error code on error. + */ +LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_roll_cryptex_nonce(mobile_image_mounter_client_t client); #ifdef __cplusplus } diff --git a/include/libimobiledevice/mobileactivation.h b/include/libimobiledevice/mobileactivation.h new file mode 100644 index 0000000..8e036a8 --- /dev/null +++ b/include/libimobiledevice/mobileactivation.h @@ -0,0 +1,192 @@ +/** + * @file libimobiledevice/mobileactivation.h + * @brief Handle device activation and deactivation. + * \internal + * + * Copyright (c) 2016-2017 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IMOBILEACTIVATION_H +#define IMOBILEACTIVATION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the mobile activation service */ +#define MOBILEACTIVATION_SERVICE_NAME "com.apple.mobileactivationd" + +/** Error Codes */ +typedef enum { + MOBILEACTIVATION_E_SUCCESS = 0, + MOBILEACTIVATION_E_INVALID_ARG = -1, + MOBILEACTIVATION_E_PLIST_ERROR = -2, + MOBILEACTIVATION_E_MUX_ERROR = -3, + MOBILEACTIVATION_E_UNKNOWN_REQUEST = -4, + MOBILEACTIVATION_E_REQUEST_FAILED = -5, + MOBILEACTIVATION_E_UNKNOWN_ERROR = -256 +} mobileactivation_error_t; + +typedef struct mobileactivation_client_private mobileactivation_client_private; /**< \private */ +typedef mobileactivation_client_private *mobileactivation_client_t; /**< The client handle. */ + +/** + * Connects to the mobileactivation service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Reference that will point to a newly allocated + * mobileactivation_client_t upon successful return. + * + * @return MOBILEACTIVATION_E_SUCCESS on success, + * MOBILEACTIVATION_E_INVALID_ARG when one of the parameters is invalid, + * or MOBILEACTIVATION_E_MUX_ERROR when the connection failed. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobileactivation_client_t *client); + +/** + * Starts a new mobileactivation service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * mobileactivation_client_t upon successful return. Must be freed using + * mobileactivation_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return MOBILEACTIVATION_E_SUCCESS on success, or an MOBILEACTIVATION_E_* + * error code otherwise. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_client_start_service(idevice_t device, mobileactivation_client_t* client, const char* label); + +/** + * Disconnects a mobileactivation client from the device and frees up the + * mobileactivation client data. + * + * @param client The mobileactivation client to disconnect and free. + * + * @return MOBILEACTIVATION_E_SUCCESS on success, + * MOBILEACTIVATION_E_INVALID_ARG when one of client or client->parent + * is invalid, or MOBILEACTIVATION_E_UNKNOWN_ERROR when the was an + * error freeing the parent property_list_service client. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_client_free(mobileactivation_client_t client); + + +/** + * Retrieves the device's activation state. + * + * @param client The mobileactivation client. + * @param state Pointer to a plist_t variable that will be set to the + * activation state reported by the mobileactivation service. The + * consumer is responsible for freeing the returned object using + * plist_free(). + * + * @return MOBILEACTIVATION_E_SUCCESS on success, or an MOBILEACTIVATION_E_* + * error code otherwise. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_get_activation_state(mobileactivation_client_t client, plist_t *state); + +/** + * Retrieves a session blob required for 'drmHandshake' via albert.apple.com. + * + * @param client The mobileactivation client + * @param blob Pointer to a plist_t variable that will be set to the + * session blob created by the mobielactivation service. The + * consumer is responsible for freeing the returned object using + * plist_free(). + * + * @return MOBILEACTIVATION_E_SUCCESS on success, or an MOBILEACTIVATION_E_* + * error code otherwise. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_create_activation_session_info(mobileactivation_client_t client, plist_t *blob); + +/** + * Retrieves the activation info required for device activation. + * + * @param client The mobileactivation client + * @param info Pointer to a plist_t variable that will be set to the + * activation info created by the mobileactivation service. The + * consumer is responsible for freeing the returned object using + * plist_free(). + * + * @return MOBILEACTIVATION_E_SUCCESS on success, or an MOBILEACTIVATION_E_* + * error code otherwise. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_create_activation_info(mobileactivation_client_t client, plist_t *info); + +/** + * Retrieves the activation info required for device activation in 'session' + * mode. This function expects a handshake result retrieved from + * https://albert.apple.com/deviceservies/drmHandshake with a blob + * provided by mobileactivation_create_activation_session_info(). + * + * @param client The mobileactivation client + * @param handshake_response The handshake response returned from drmHandshake + * @param info Pointer to a plist_t variable that will be set to the + * activation info created by the mobileactivation service. The + * consumer is responsible for freeing the returned object using + * plist_free(). + * + * @return MOBILEACTIVATION_E_SUCCESS on success, or an MOBILEACTIVATION_E_* + * error code otherwise. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_create_activation_info_with_session(mobileactivation_client_t client, plist_t handshake_response, plist_t *info); + +/** + * Activates the device with the given activation record. + * The activation record plist dictionary must be obtained using the + * activation protocol requesting from Apple's https webservice. + * + * @param client The mobileactivation client + * @param activation_record The activation record plist dictionary + * + * @return MOBILEACTIVATION_E_SUCCESS on success, or an MOBILEACTIVATION_E_* + * error code otherwise. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_activate(mobileactivation_client_t client, plist_t activation_record); + +/** + * Activates the device with the given activation record in 'session' mode. + * The activation record plist must be obtained using the + * activation protocol requesting from Apple's https webservice. + * + * @param client The mobileactivation client + * @param activation_record The activation record in plist format + * @param headers A plist dictionary with the activation response headers + * as returned from Apple's https webservice with the activation record + * + * @return MOBILEACTIVATION_E_SUCCESS on success, or an MOBILEACTIVATION_E_* + * error code otherwise. + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_activate_with_session(mobileactivation_client_t client, plist_t activation_record, plist_t headers); + +/** + * Deactivates the device. + * + * @param client The mobileactivation client + */ +LIBIMOBILEDEVICE_API mobileactivation_error_t mobileactivation_deactivate(mobileactivation_client_t client); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/mobilebackup.h b/include/libimobiledevice/mobilebackup.h index 8f31cc4..2ecb60c 100644 --- a/include/libimobiledevice/mobilebackup.h +++ b/include/libimobiledevice/mobilebackup.h @@ -3,7 +3,8 @@ * @brief Backup and restore of all device data. * \internal * - * Copyright (c) 2009 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2009-2014 Martin Szulecki, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -28,42 +29,215 @@ extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define MOBILEBACKUP_E_SUCCESS 0 -#define MOBILEBACKUP_E_INVALID_ARG -1 -#define MOBILEBACKUP_E_PLIST_ERROR -2 -#define MOBILEBACKUP_E_MUX_ERROR -3 -#define MOBILEBACKUP_E_BAD_VERSION -4 -#define MOBILEBACKUP_E_REPLY_NOT_OK -5 +/** Service identifier passed to lockdownd_start_service() to start the mobilebackup service */ +#define MOBILEBACKUP_SERVICE_NAME "com.apple.mobilebackup" -#define MOBILEBACKUP_E_UNKNOWN_ERROR -256 -/*@}*/ - -/** Represents an error code. */ -typedef int16_t mobilebackup_error_t; +/** Error Codes */ +typedef enum { + MOBILEBACKUP_E_SUCCESS = 0, + MOBILEBACKUP_E_INVALID_ARG = -1, + MOBILEBACKUP_E_PLIST_ERROR = -2, + MOBILEBACKUP_E_MUX_ERROR = -3, + MOBILEBACKUP_E_SSL_ERROR = -4, + MOBILEBACKUP_E_RECEIVE_TIMEOUT = -5, + MOBILEBACKUP_E_BAD_VERSION = -6, + MOBILEBACKUP_E_REPLY_NOT_OK = -7, + MOBILEBACKUP_E_UNKNOWN_ERROR = -256 +} mobilebackup_error_t; -typedef struct mobilebackup_client_private mobilebackup_client_private; +typedef struct mobilebackup_client_private mobilebackup_client_private; /**< \private */ typedef mobilebackup_client_private *mobilebackup_client_t; /**< The client handle. */ +/** Available flags passed to #mobilebackup_request_restore */ typedef enum { MB_RESTORE_NOTIFY_SPRINGBOARD = 1 << 0, MB_RESTORE_PRESERVE_SETTINGS = 1 << 1, MB_RESTORE_PRESERVE_CAMERA_ROLL = 1 << 2 } mobilebackup_flags_t; -mobilebackup_error_t mobilebackup_client_new(idevice_t device, uint16_t port, mobilebackup_client_t * client); -mobilebackup_error_t mobilebackup_client_free(mobilebackup_client_t client); -mobilebackup_error_t mobilebackup_receive(mobilebackup_client_t client, plist_t *plist); -mobilebackup_error_t mobilebackup_send(mobilebackup_client_t client, plist_t plist); -mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, plist_t backup_manifest, const char *base_path, const char *proto_version); -mobilebackup_error_t mobilebackup_send_backup_file_received(mobilebackup_client_t client); -mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, plist_t backup_manifest, mobilebackup_flags_t flags, const char *proto_version); -mobilebackup_error_t mobilebackup_receive_restore_file_received(mobilebackup_client_t client, plist_t *result); -mobilebackup_error_t mobilebackup_receive_restore_application_received(mobilebackup_client_t client, plist_t *result); -mobilebackup_error_t mobilebackup_send_restore_complete(mobilebackup_client_t client); -mobilebackup_error_t mobilebackup_send_error(mobilebackup_client_t client, const char *reason); +/** + * Connects to the mobilebackup service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * mobilebackup_client_t upon successful return. + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID ARG if one + * or more parameters are invalid, or DEVICE_LINK_SERVICE_E_BAD_VERSION if + * the mobilebackup version on the device is newer. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilebackup_client_t * client); + +/** + * Starts a new mobilebackup service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * mobilebackup_client_t upon successful return. Must be freed using + * mobilebackup_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return MOBILEBACKUP_E_SUCCESS on success, or an MOBILEBACKUP_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_client_start_service(idevice_t device, mobilebackup_client_t* client, const char* label); + +/** + * Disconnects a mobilebackup client from the device and frees up the + * mobilebackup client data. + * + * @param client The mobilebackup client to disconnect and free. + * + * @return MOBILEBACKUP_E_SUCCESS on success, or MOBILEBACKUP_E_INVALID_ARG + * if client is NULL. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_client_free(mobilebackup_client_t client); + + +/** + * Polls the device for mobilebackup data. + * + * @param client The mobilebackup client + * @param plist A pointer to the location where the plist should be stored + * + * @return an error code + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_receive(mobilebackup_client_t client, plist_t *plist); + +/** + * Sends mobilebackup data to the device + * + * @note This function is low-level and should only be used if you need to send + * a new type of message. + * + * @param client The mobilebackup client + * @param plist The location of the plist to send + * + * @return an error code + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_send(mobilebackup_client_t client, plist_t plist); + +/** + * Request a backup from the connected device. + * + * @param client The connected MobileBackup client to use. + * @param backup_manifest The backup manifest, a plist_t of type PLIST_DICT + * containing the backup state of the last backup. For a first-time backup + * set this parameter to NULL. + * @param base_path The base path on the device to use for the backup + * operation, usually "/". + * @param proto_version A string denoting the version of the backup protocol + * to use. Latest known version is "1.6" + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if + * one of the parameters is invalid, MOBILEBACKUP_E_PLIST_ERROR if + * backup_manifest is not of type PLIST_DICT, MOBILEBACKUP_E_MUX_ERROR + * if a communication error occurs, MOBILEBACKUP_E_REPLY_NOT_OK + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, plist_t backup_manifest, const char *base_path, const char *proto_version); + +/** + * Sends a confirmation to the device that a backup file has been received. + * + * @param client The connected MobileBackup client to use. + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if + * client is invalid, or MOBILEBACKUP_E_MUX_ERROR if a communication error + * occurs. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_send_backup_file_received(mobilebackup_client_t client); + +/** + * Request that a backup should be restored to the connected device. + * + * @param client The connected MobileBackup client to use. + * @param backup_manifest The backup manifest, a plist_t of type PLIST_DICT + * containing the backup state to be restored. + * @param flags Flags to send with the request. Currently this is a combination + * of the following mobilebackup_flags_t: + * MB_RESTORE_NOTIFY_SPRINGBOARD - let SpringBoard show a 'Restore' screen + * MB_RESTORE_PRESERVE_SETTINGS - do not overwrite any settings + * MB_RESTORE_PRESERVE_CAMERA_ROLL - preserve the photos of the camera roll + * @param proto_version A string denoting the version of the backup protocol + * to use. Latest known version is "1.6". Ideally this value should be + * extracted from the given manifest plist. + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if + * one of the parameters is invalid, MOBILEBACKUP_E_PLIST_ERROR if + * backup_manifest is not of type PLIST_DICT, MOBILEBACKUP_E_MUX_ERROR + * if a communication error occurs, or MOBILEBACKUP_E_REPLY_NOT_OK + * if the device did not accept the request. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, plist_t backup_manifest, mobilebackup_flags_t flags, const char *proto_version); + +/** + * Receive a confirmation from the device that it successfully received + * a restore file. + * + * @param client The connected MobileBackup client to use. + * @param result Pointer to a plist_t that will be set to the received plist + * for further processing. The caller has to free it using plist_free(). + * Note that it will be set to NULL if the operation itself fails due to + * a communication or plist error. + * If this parameter is NULL, it will be ignored. + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if + * client is invalid, MOBILEBACKUP_E_REPLY_NOT_OK if the expected + * 'BackupMessageRestoreFileReceived' message could not be received, + * MOBILEBACKUP_E_PLIST_ERROR if the received message is not a valid backup + * message plist, or MOBILEBACKUP_E_MUX_ERROR if a communication error + * occurs. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_receive_restore_file_received(mobilebackup_client_t client, plist_t *result); + +/** + * Receive a confirmation from the device that it successfully received + * application data file. + * + * @param client The connected MobileBackup client to use. + * @param result Pointer to a plist_t that will be set to the received plist + * for further processing. The caller has to free it using plist_free(). + * Note that it will be set to NULL if the operation itself fails due to + * a communication or plist error. + * If this parameter is NULL, it will be ignored. + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if + * client is invalid, MOBILEBACKUP_E_REPLY_NOT_OK if the expected + * 'BackupMessageRestoreApplicationReceived' message could not be received, + * MOBILEBACKUP_E_PLIST_ERROR if the received message is not a valid backup + * message plist, or MOBILEBACKUP_E_MUX_ERROR if a communication error + * occurs. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_receive_restore_application_received(mobilebackup_client_t client, plist_t *result); + +/** + * Tells the device that the restore process is complete and waits for the + * device to close the connection. After that, the device should reboot. + * + * @param client The connected MobileBackup client to use. + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if + * client is invalid, MOBILEBACKUP_E_PLIST_ERROR if the received disconnect + * message plist is invalid, or MOBILEBACKUP_E_MUX_ERROR if a communication + * error occurs. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_send_restore_complete(mobilebackup_client_t client); + +/** + * Sends a backup error message to the device. + * + * @param client The connected MobileBackup client to use. + * @param reason A string describing the reason for the error message. + * + * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if + * one of the parameters is invalid, or MOBILEBACKUP_E_MUX_ERROR if a + * communication error occurs. + */ +LIBIMOBILEDEVICE_API mobilebackup_error_t mobilebackup_send_error(mobilebackup_client_t client, const char *reason); #ifdef __cplusplus } diff --git a/include/libimobiledevice/mobilebackup2.h b/include/libimobiledevice/mobilebackup2.h new file mode 100644 index 0000000..2e9222d --- /dev/null +++ b/include/libimobiledevice/mobilebackup2.h @@ -0,0 +1,214 @@ +/** + * @file libimobiledevice/mobilebackup2.h + * @brief Backup and restore of all device data (mobilebackup2, iOS4+ only) + * \internal + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2011-2014 Martin Szulecki, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IMOBILEBACKUP2_H +#define IMOBILEBACKUP2_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the mobilebackup2 service */ +#define MOBILEBACKUP2_SERVICE_NAME "com.apple.mobilebackup2" + +/** Error Codes */ +typedef enum { + MOBILEBACKUP2_E_SUCCESS = 0, + MOBILEBACKUP2_E_INVALID_ARG = -1, + MOBILEBACKUP2_E_PLIST_ERROR = -2, + MOBILEBACKUP2_E_MUX_ERROR = -3, + MOBILEBACKUP2_E_SSL_ERROR = -4, + MOBILEBACKUP2_E_RECEIVE_TIMEOUT = -5, + MOBILEBACKUP2_E_BAD_VERSION = -6, + MOBILEBACKUP2_E_REPLY_NOT_OK = -7, + MOBILEBACKUP2_E_NO_COMMON_VERSION = -8, + MOBILEBACKUP2_E_UNKNOWN_ERROR = -256 +} mobilebackup2_error_t; + +typedef struct mobilebackup2_client_private mobilebackup2_client_private; /**< \private */ +typedef mobilebackup2_client_private *mobilebackup2_client_t; /**< The client handle. */ + + +/** + * Connects to the mobilebackup2 service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * mobilebackup2_client_t upon successful return. + * + * @return MOBILEBACKUP2_E_SUCCESS on success, MOBILEBACKUP2_E_INVALID ARG + * if one or more parameter is invalid, or MOBILEBACKUP2_E_BAD_VERSION + * if the mobilebackup2 version on the device is newer. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilebackup2_client_t * client); + +/** + * Starts a new mobilebackup2 service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * mobilebackup2_client_t upon successful return. Must be freed using + * mobilebackup2_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return MOBILEBACKUP2_E_SUCCESS on success, or an MOBILEBACKUP2_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_client_start_service(idevice_t device, mobilebackup2_client_t* client, const char* label); + +/** + * Disconnects a mobilebackup2 client from the device and frees up the + * mobilebackup2 client data. + * + * @param client The mobilebackup2 client to disconnect and free. + * + * @return MOBILEBACKUP2_E_SUCCESS on success, or MOBILEBACKUP2_E_INVALID_ARG + * if client is NULL. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_client_free(mobilebackup2_client_t client); + + +/** + * Sends a backup message plist. + * + * @param client The connected MobileBackup client to use. + * @param message The message to send. This will be inserted into the request + * plist as value for MessageName. If this parameter is NULL, + * the plist passed in the options parameter will be sent directly. + * @param options Additional options as PLIST_DICT to add to the request. + * The MessageName key with the value passed in the message parameter + * will be inserted into this plist before sending it. This parameter + * can be NULL if message is not NULL. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_send_message(mobilebackup2_client_t client, const char *message, plist_t options); + +/** + * Receives a DL* message plist from the device. + * This function is a wrapper around device_link_service_receive_message. + * + * @param client The connected MobileBackup client to use. + * @param msg_plist Pointer to a plist that will be set to the contents of the + * message plist upon successful return. + * @param dlmessage A pointer that will be set to a newly allocated char* + * containing the DL* string from the given plist. It is up to the caller + * to free the allocated memory. If this parameter is NULL + * it will be ignored. + * + * @return MOBILEBACKUP2_E_SUCCESS if a DL* message was received, + * MOBILEBACKUP2_E_INVALID_ARG if client or message is invalid, + * MOBILEBACKUP2_E_PLIST_ERROR if the received plist is invalid + * or is not a DL* message plist, or MOBILEBACKUP2_E_MUX_ERROR if + * receiving from the device failed. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_receive_message(mobilebackup2_client_t client, plist_t *msg_plist, char **dlmessage); + +/** + * Send binary data to the device. + * + * @note This function returns MOBILEBACKUP2_E_SUCCESS even if less than the + * requested length has been sent. The fourth parameter is required and + * must be checked to ensure if the whole data has been sent. + * + * @param client The MobileBackup client to send to. + * @param data Pointer to the data to send + * @param length Number of bytes to send + * @param bytes Number of bytes actually sent + * + * @return MOBILEBACKUP2_E_SUCCESS if any data was successfully sent, + * MOBILEBACKUP2_E_INVALID_ARG if one of the parameters is invalid, + * or MOBILEBACKUP2_E_MUX_ERROR if sending of the data failed. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_send_raw(mobilebackup2_client_t client, const char *data, uint32_t length, uint32_t *bytes); + +/** + * Receive binary from the device. + * + * @note This function returns MOBILEBACKUP2_E_SUCCESS even if no data + * has been received (unless a communication error occurred). + * The fourth parameter is required and must be checked to know how + * many bytes were actually received. + * + * @param client The MobileBackup client to receive from. + * @param data Pointer to a buffer that will be filled with the received data. + * @param length Number of bytes to receive. The data buffer needs to be large + * enough to store this amount of data. + * @param bytes Number of bytes actually received. + * + * @return MOBILEBACKUP2_E_SUCCESS if any or no data was received, + * MOBILEBACKUP2_E_INVALID_ARG if one of the parameters is invalid, + * or MOBILEBACKUP2_E_MUX_ERROR if receiving the data failed. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_receive_raw(mobilebackup2_client_t client, char *data, uint32_t length, uint32_t *bytes); + +/** + * Performs the mobilebackup2 protocol version exchange. + * + * @param client The MobileBackup client to use. + * @param local_versions An array of supported versions to send to the remote. + * @param count The number of items in local_versions. + * @param remote_version Holds the protocol version of the remote on success. + * + * @return MOBILEBACKUP2_E_SUCCESS on success, or a MOBILEBACKUP2_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_version_exchange(mobilebackup2_client_t client, double local_versions[], char count, double *remote_version); + +/** + * Send a request to the connected mobilebackup2 service. + * + * @param client + * @param request The request to send to the backup service. + * Currently, this is one of "Backup", "Restore", "Info", or "List". + * @param target_identifier UDID of the target device. + * @param source_identifier UDID of backup data? + * @param options Additional options in a plist of type PLIST_DICT. + * + * @return MOBILEBACKUP2_E_SUCCESS if the request was successfully sent, + * or a MOBILEBACKUP2_E_* error value otherwise. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_send_request(mobilebackup2_client_t client, const char *request, const char *target_identifier, const char *source_identifier, plist_t options); + +/** + * Sends a DLMessageStatusResponse to the device. + * + * @param client The MobileBackup client to use. + * @param status_code The status code to send. + * @param status1 A status message to send. Can be NULL if not required. + * @param status2 An additional status plist to attach to the response. + * Can be NULL if not required. + * + * @return MOBILEBACKUP2_E_SUCCESS on success, MOBILEBACKUP2_E_INVALID_ARG + * if client is invalid, or another MOBILEBACKUP2_E_* otherwise. + */ +LIBIMOBILEDEVICE_API mobilebackup2_error_t mobilebackup2_send_status_response(mobilebackup2_client_t client, int status_code, const char *status1, plist_t status2); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/mobilesync.h b/include/libimobiledevice/mobilesync.h index 7658b7d..c3bc53d 100644 --- a/include/libimobiledevice/mobilesync.h +++ b/include/libimobiledevice/mobilesync.h @@ -3,6 +3,9 @@ * @brief Synchronize data classes with a device and computer. * \internal * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2014 Christophe Fergeau All Rights Reserved. * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * @@ -29,22 +32,26 @@ extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> -#include <glib.h> - -/** @name Error Codes */ -/*@{*/ -#define MOBILESYNC_E_SUCCESS 0 -#define MOBILESYNC_E_INVALID_ARG -1 -#define MOBILESYNC_E_PLIST_ERROR -2 -#define MOBILESYNC_E_MUX_ERROR -3 -#define MOBILESYNC_E_BAD_VERSION -4 -#define MOBILESYNC_E_SYNC_REFUSED -5 -#define MOBILESYNC_E_CANCELLED -6 -#define MOBILESYNC_E_WRONG_DIRECTION -7 -#define MOBILESYNC_E_NOT_READY -8 - -#define MOBILESYNC_E_UNKNOWN_ERROR -256 -/*@}*/ +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the mobilesync service */ +#define MOBILESYNC_SERVICE_NAME "com.apple.mobilesync" + +/** Error Codes */ +typedef enum { + MOBILESYNC_E_SUCCESS = 0, + MOBILESYNC_E_INVALID_ARG = -1, + MOBILESYNC_E_PLIST_ERROR = -2, + MOBILESYNC_E_MUX_ERROR = -3, + MOBILESYNC_E_SSL_ERROR = -4, + MOBILESYNC_E_RECEIVE_TIMEOUT = -5, + MOBILESYNC_E_BAD_VERSION = -6, + MOBILESYNC_E_SYNC_REFUSED = -7, + MOBILESYNC_E_CANCELLED = -8, + MOBILESYNC_E_WRONG_DIRECTION = -9, + MOBILESYNC_E_NOT_READY = -10, + MOBILESYNC_E_UNKNOWN_ERROR = -256 +} mobilesync_error_t; /** The sync type of the current sync session. */ typedef enum { @@ -53,48 +60,297 @@ typedef enum { MOBILESYNC_SYNC_TYPE_RESET /**< Reset-sync signals that the computer should send all data again. */ } mobilesync_sync_type_t; -/** Represents an error code. */ -typedef int16_t mobilesync_error_t; - -typedef struct mobilesync_client_private mobilesync_client_private; +typedef struct mobilesync_client_private mobilesync_client_private; /**< \private */ typedef mobilesync_client_private *mobilesync_client_t; /**< The client handle */ +/** Anchors used by the device and computer (structure) */ typedef struct { - char *device_anchor; - char *computer_anchor; + char *device_anchor; /**< device anchor */ + char *computer_anchor; /**< computer anchor */ } mobilesync_anchors; -typedef mobilesync_anchors *mobilesync_anchors_t; /**< Anchors used by the device and computer. */ +/** Anchors used by the device and computer */ +typedef mobilesync_anchors *mobilesync_anchors_t; /* Interface */ -mobilesync_error_t mobilesync_client_new(idevice_t device, uint16_t port, mobilesync_client_t * client); -mobilesync_error_t mobilesync_client_free(mobilesync_client_t client); -mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist_t *plist); -mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist); +/** + * Connects to the mobilesync service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * #mobilesync_client_t upon successful return. + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one or more parameters are invalid + * @retval DEVICE_LINK_SERVICE_E_BAD_VERSION if the mobilesync version on + * the device is newer. + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilesync_client_t * client); + +/** + * Starts a new mobilesync service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * mobilesync_client_t upon successful return. Must be freed using + * mobilesync_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return MOBILESYNC_E_SUCCESS on success, or an MOBILESYNC_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_client_start_service(idevice_t device, mobilesync_client_t* client, const char* label); + +/** + * Disconnects a mobilesync client from the device and frees up the + * mobilesync client data. + * + * @param client The mobilesync client to disconnect and free. + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if \a client is NULL. + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_client_free(mobilesync_client_t client); + + +/** + * Polls the device for mobilesync data. + * + * @param client The mobilesync client + * @param plist A pointer to the location where the plist should be stored + * + * @return an error code + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist_t *plist); + +/** + * Sends mobilesync data to the device + * + * @note This function is low-level and should only be used if you need to send + * a new type of message. + * + * @param client The mobilesync client + * @param plist The location of the plist to send + * + * @return an error code + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist); + + +/** + * Requests starting synchronization of a data class with the device + * + * @param client The mobilesync client + * @param data_class The data class identifier to sync + * @param anchors The anchors required to exchange with the device. The anchors + * allow the device to tell if the synchronization information on the computer + * and device are consistent to determine what sync type is required. + * @param computer_data_class_version The version of the data class storage on the computer + * @param sync_type A pointer to store the sync type reported by the device_anchor + * @param device_data_class_version The version of the data class storage on the device + * @param error_description A pointer to store an error message if reported by the device + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form + * @retval MOBILESYNC_E_SYNC_REFUSED if the device refused to sync + * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the + * sync request + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, uint64_t computer_data_class_version, mobilesync_sync_type_t *sync_type, uint64_t *device_data_class_version, char** error_description); + +/** + * Cancels a running synchronization session with a device at any time. + * + * @param client The mobilesync client + * @param reason The reason to supply to the device for cancelling + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* reason); + +/** + * Finish a synchronization session of a data class on the device. + * A session must have previously been started using mobilesync_start(). + * + * @param client The mobilesync client + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid + * form + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_finish(mobilesync_client_t client); + + +/** + * Requests to receive all records of the currently set data class from the device. + * The actually changes are retrieved using mobilesync_receive_changes() after this + * request has been successful. + * + * @param client The mobilesync client + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_get_all_records_from_device(mobilesync_client_t client); + +/** + * Requests to receive only changed records of the currently set data class from the device. + * The actually changes are retrieved using mobilesync_receive_changes() after this + * request has been successful. + * + * @param client The mobilesync client + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_get_changes_from_device(mobilesync_client_t client); + +/** + * Requests the device to delete all records of the current data class + * + * @note The operation must be called after starting synchronization. + * + * @param client The mobilesync client + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t client); -mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, uint64_t computer_data_class_version, mobilesync_sync_type_t *sync_type, uint64_t *device_data_class_version); -mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* reason); -mobilesync_error_t mobilesync_finish(mobilesync_client_t client); -mobilesync_error_t mobilesync_get_all_records_from_device(mobilesync_client_t client); -mobilesync_error_t mobilesync_get_changes_from_device(mobilesync_client_t client); -mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t client); +/** + * Receives changed entitites of the currently set data class from the device + * + * @param client The mobilesync client + * @param entities A pointer to store the changed entity records as a PLIST_DICT + * @param is_last_record A pointer to store a flag indicating if this submission is the last one + * @param actions A pointer to additional flags the device is sending or NULL to ignore + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the + * session + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_t *entities, uint8_t *is_last_record, plist_t *actions); + +/** + * Acknowledges to the device that the changes have been merged on the computer + * + * @param client The mobilesync client + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_acknowledge_changes_from_device(mobilesync_client_t client); -mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_t *entities, uint8_t *is_last_record, plist_t *actions); -mobilesync_error_t mobilesync_acknowledge_changes_from_device(mobilesync_client_t client); -mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_client_t client); +/** + * Verifies if the device is ready to receive changes from the computer. + * This call changes the synchronization direction so that mobilesync_send_changes() + * can be used to send changes to the device. + * + * @param client The mobilesync client + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form + * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does + * not permit this call + * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the + * session + * @retval MOBILESYNC_E_NOT_READY if the device is not ready to start + * receiving any changes + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_client_t client); + + +/** + * Sends changed entities of the currently set data class to the device + * + * @param client The mobilesync client + * @param entities The changed entity records as a PLIST_DICT + * @param is_last_record A flag indicating if this submission is the last one + * @param actions Additional actions for the device created with mobilesync_actions_new() + * or NULL if no actions should be passed + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid, + * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does + * not permit this call + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t entities, uint8_t is_last_record, plist_t actions); -mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t entities, uint8_t is_last_record, plist_t actions); -mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plist_t *mapping); +/** + * Receives any remapped identifiers reported after the device merged submitted changes. + * + * @param client The mobilesync client + * @param mapping A pointer to an array plist containing a dict of identifier remappings + * + * @retval MOBILESYNC_E_SUCCESS on success + * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid + * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid + * form + * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does + * not permit this call + * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the + * session + */ +LIBIMOBILEDEVICE_API mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plist_t *mapping); /* Helper */ -mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const char *computer_anchor); -void mobilesync_anchors_free(mobilesync_anchors_t anchors); -plist_t mobilesync_actions_new(); -void mobilesync_actions_add(plist_t actions, ...) G_GNUC_NULL_TERMINATED; -void mobilesync_actions_free(plist_t actions); +/** + * Allocates memory for a new anchors struct initialized with the passed anchors. + * + * @param device_anchor An anchor the device reported the last time or NULL + * if none is known yet which for instance is true on first synchronization. + * @param computer_anchor An arbitrary string to use as anchor for the computer. + * + * @return A new #mobilesync_anchors_t struct. Must be freed using mobilesync_anchors_free(). + */ +LIBIMOBILEDEVICE_API mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const char *computer_anchor); + +/** + * Free memory used by anchors. + * + * @param anchors The anchors to free. + */ +LIBIMOBILEDEVICE_API void mobilesync_anchors_free(mobilesync_anchors_t anchors); + + +/** + * Create a new actions plist to use in mobilesync_send_changes(). + * + * @return A new plist_t of type PLIST_DICT. + */ +LIBIMOBILEDEVICE_API plist_t mobilesync_actions_new(void); + +/** + * Add one or more new key:value pairs to the given actions plist. + * + * @param actions The actions to modify. + * @param ... KEY, VALUE, [KEY, VALUE], NULL + * + * @note The known keys so far are "SyncDeviceLinkEntityNamesKey" which expects + * an array of entity names, followed by a count paramter as well as + * "SyncDeviceLinkAllRecordsOfPulledEntityTypeSentKey" which expects an + * integer to use as a boolean value indicating that the device should + * link submitted changes and report remapped identifiers. + */ +LIBIMOBILEDEVICE_API void mobilesync_actions_add(plist_t actions, ...); + +/** + * Free actions plist. + * + * @param actions The actions plist to free. Does nothing if NULL is passed. + */ +LIBIMOBILEDEVICE_API void mobilesync_actions_free(plist_t actions); #ifdef __cplusplus } diff --git a/include/libimobiledevice/notification_proxy.h b/include/libimobiledevice/notification_proxy.h index 43c479d..f4f090b 100644 --- a/include/libimobiledevice/notification_proxy.h +++ b/include/libimobiledevice/notification_proxy.h @@ -3,7 +3,8 @@ * @brief Observe and post notifications. * \internal * - * Copyright (c) 2009 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2009-2010 Nikias Bassen All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,45 +21,49 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef NOTIFICATION_PROXY_H -#define NOTIFICATION_PROXY_H +#ifndef INOTIFICATION_PROXY_H +#define INOTIFICATION_PROXY_H #ifdef __cplusplus extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define NP_E_SUCCESS 0 -#define NP_E_INVALID_ARG -1 -#define NP_E_PLIST_ERROR -2 -#define NP_E_CONN_FAILED -3 +/** Service identifier passed to lockdownd_start_service() to start the notification proxy service */ +#define NP_SERVICE_NAME "com.apple.mobile.notification_proxy" -#define NP_E_UNKNOWN_ERROR -256 -/*@}*/ - -/** Represents an error code. */ -typedef int16_t np_error_t; +/** Error Codes */ +typedef enum { + NP_E_SUCCESS = 0, + NP_E_INVALID_ARG = -1, + NP_E_PLIST_ERROR = -2, + NP_E_CONN_FAILED = -3, + NP_E_UNKNOWN_ERROR = -256 +} np_error_t; /** - * @name Notifications that can be send + * @name Notifications that can be sent * * For use with np_post_notification() (client --> device) */ +/**@{*/ +//! @cond #define NP_SYNC_WILL_START "com.apple.itunes-mobdev.syncWillStart" #define NP_SYNC_DID_START "com.apple.itunes-mobdev.syncDidStart" #define NP_SYNC_DID_FINISH "com.apple.itunes-mobdev.syncDidFinish" #define NP_SYNC_LOCK_REQUEST "com.apple.itunes-mobdev.syncLockRequest" -/*@}*/ +//! @endcond +/**@}*/ /** * @name Notifications that can be received * * For use with np_observe_notification() (device --> client) */ -/*@{*/ +/**@{*/ +//! @cond #define NP_SYNC_CANCEL_REQUEST "com.apple.itunes-client.syncCancelRequest" #define NP_SYNC_SUSPEND_REQUEST "com.apple.itunes-client.syncSuspendRequest" #define NP_SYNC_RESUME_REQUEST "com.apple.itunes-client.syncResumeRequest" @@ -81,21 +86,114 @@ typedef int16_t np_error_t; #define NP_ITDBPREP_DID_END "com.apple.itdbprep.notification.didEnd" #define NP_LANGUAGE_CHANGED "com.apple.language.changed" #define NP_ADDRESS_BOOK_PREF_CHANGED "com.apple.AddressBook.PreferenceChanged" -/*@}*/ +//! @endcond +/**@}*/ -typedef struct np_client_private np_client_private; +typedef struct np_client_private np_client_private; /**< \private */ typedef np_client_private *np_client_t; /**< The client handle. */ -/** Reports which notification was received. */ +/** Callback function that reports which notification was received. */ typedef void (*np_notify_cb_t) (const char *notification, void *user_data); /* Interface */ -np_error_t np_client_new(idevice_t device, uint16_t port, np_client_t *client); -np_error_t np_client_free(np_client_t client); -np_error_t np_post_notification(np_client_t client, const char *notification); -np_error_t np_observe_notification(np_client_t client, const char *notification); -np_error_t np_observe_notifications(np_client_t client, const char **notification_spec); -np_error_t np_set_notify_callback(np_client_t client, np_notify_cb_t notify_cb, void *userdata); + +/** + * Connects to the notification_proxy on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated np_client_t + * upon successful return. + * + * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when device is NULL, + * or NP_E_CONN_FAILED when the connection to the device could not be + * established. + */ +LIBIMOBILEDEVICE_API np_error_t np_client_new(idevice_t device, lockdownd_service_descriptor_t service, np_client_t *client); + +/** + * Starts a new notification proxy service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * np_client_t upon successful return. Must be freed using + * np_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return NP_E_SUCCESS on success, or an NP_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API np_error_t np_client_start_service(idevice_t device, np_client_t* client, const char* label); + +/** + * Disconnects a notification_proxy client from the device and frees up the + * notification_proxy client data. + * + * @param client The notification_proxy client to disconnect and free. + * + * @return NP_E_SUCCESS on success, or NP_E_INVALID_ARG when client is NULL. + */ +LIBIMOBILEDEVICE_API np_error_t np_client_free(np_client_t client); + + +/** + * Sends a notification to the device's notification_proxy. + * + * @param client The client to send to + * @param notification The notification message to send + * + * @return NP_E_SUCCESS on success, or an error returned by np_plist_send + */ +LIBIMOBILEDEVICE_API np_error_t np_post_notification(np_client_t client, const char *notification); + +/** + * Tells the device to send a notification on the specified event. + * + * @param client The client to send to + * @param notification The notifications that should be observed. + * + * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client or + * notification are NULL, or an error returned by np_plist_send. + */ +LIBIMOBILEDEVICE_API np_error_t np_observe_notification(np_client_t client, const char *notification); + +/** + * Tells the device to send a notification on specified events. + * + * @param client The client to send to + * @param notification_spec Specification of the notifications that should be + * observed. This is expected to be an array of const char* that MUST have a + * terminating NULL entry. + * + * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client is null, + * or an error returned by np_observe_notification. + */ +LIBIMOBILEDEVICE_API np_error_t np_observe_notifications(np_client_t client, const char **notification_spec); + +/** + * This function allows an application to define a callback function that will + * be called when a notification has been received. + * It will start a thread that polls for notifications and calls the callback + * function if a notification has been received. + * In case of an error condition when polling for notifications - e.g. device + * disconnect - the thread will call the callback function with an empty + * notification "" and terminate itself. + * + * @param client the NP client + * @param notify_cb pointer to a callback function or NULL to de-register a + * previously set callback function. + * @param user_data Pointer that will be passed to the callback function as + * user data. If notify_cb is NULL, this parameter is ignored. + * + * @note Only one callback function can be registered at the same time; + * any previously set callback function will be removed automatically. + * + * @return NP_E_SUCCESS when the callback was successfully registered, + * NP_E_INVALID_ARG when client is NULL, or NP_E_UNKNOWN_ERROR when + * the callback thread could no be created. + */ +LIBIMOBILEDEVICE_API np_error_t np_set_notify_callback(np_client_t client, np_notify_cb_t notify_cb, void *user_data); #ifdef __cplusplus } diff --git a/include/libimobiledevice/preboard.h b/include/libimobiledevice/preboard.h new file mode 100644 index 0000000..0d89eb4 --- /dev/null +++ b/include/libimobiledevice/preboard.h @@ -0,0 +1,187 @@ +/** + * @file libimobiledevice/preboard.h + * @brief Service to 'preboard' a device, which allows to ask for passcode during firmware updates. + * \internal + * + * Copyright (c) 2019 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IPREBOARD_H +#define IPREBOARD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the preboard service */ +#define PREBOARD_SERVICE_NAME "com.apple.preboardservice_v2" + +/** Error Codes */ +typedef enum { + PREBOARD_E_SUCCESS = 0, + PREBOARD_E_INVALID_ARG = -1, + PREBOARD_E_PLIST_ERROR = -2, + PREBOARD_E_MUX_ERROR = -3, + PREBOARD_E_SSL_ERROR = -4, + PREBOARD_E_NOT_ENOUGH_DATA = -5, + PREBOARD_E_TIMEOUT = -6, + PREBOARD_E_OP_IN_PROGRESS = -10, + PREBOARD_E_UNKNOWN_ERROR = -256 +} preboard_error_t; + +typedef struct preboard_client_private preboard_client_private; /**< \private */ +typedef preboard_client_private *preboard_client_t; /**< The client handle. */ + +/** Reports the status response of the given command */ +typedef void (*preboard_status_cb_t) (plist_t message, void *user_data); + +/** + * Connects to the preboard service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * preboard_client_t upon successful return. Must be freed using + * preboard_client_free() after use. + * + * @return PREBOARD_E_SUCCESS on success, PREBOARD_E_INVALID_ARG when + * client is NULL, or an PREBOARD_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_client_new(idevice_t device, lockdownd_service_descriptor_t service, preboard_client_t * client); + +/** + * Starts a new preboard service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * preboard_client_t upon successful return. Must be freed using + * preboard_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return PREBOARD_E_SUCCESS on success, or a PREBOARD_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_client_start_service(idevice_t device, preboard_client_t * client, const char* label); + +/** + * Disconnects a preboard client from the device and frees up the + * preboard client data. + * + * @param client The preboard client to disconnect and free. + * + * @return PREBOARD_E_SUCCESS on success, PREBOARD_E_INVALID_ARG when + * client is NULL, or a PREBOARD_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_client_free(preboard_client_t client); + +/** + * Sends a plist to the service. + * + * @param client The preboard client + * @param plist The plist to send + * + * @return PREBOARD_E_SUCCESS on success, + * PREBOARD_E_INVALID_ARG when client or plist is NULL, + * or a PREBOARD_E_* error code on error + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_send(preboard_client_t client, plist_t plist); + +/** + * Receives a plist from the service. + * + * @param client The preboard client + * @param plist Pointer to a plist_t what will be set to the received plist + * + * @return PREBOARD_E_SUCCESS on success, + * PREBOARD_E_INVALID_ARG when client or plist is NULL, + * PREBOARD_E_TIMEOUT when no data was received after 5 seconds, + * or a PREBOARD_E_* error code on error + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_receive(preboard_client_t client, plist_t * plist); + +/** + * Receives a plist from the service with the specified timeout. + * + * @param client The preboard client + * @param plist Pointer to a plist_t what will be set to the received plist + * @param timeout_ms Timeout in milliseconds + * + * @return PREBOARD_E_SUCCESS on success, + * PREBOARD_E_INVALID_ARG when client or plist is NULL, + * PREBOARD_E_TIMEOUT when no data was received after the given timeout, + * or a PREBOARD_E_* error code on error. + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_receive_with_timeout(preboard_client_t client, plist_t * plist, uint32_t timeout_ms); + +/** + * Tells the preboard service to create a stashbag. This will make the device + * show a passcode entry so it can generate and store a token that is later + * used during restore. + * + * @param client The preboard client + * @param manifest An optional manifest + * @param status_cb Callback function that will receive status and error messages. + * Can be NULL if you want to handle receiving messages in your own code. + * @param user_data User data for callback function or NULL. + * + * The callback or following preboard_receive* invocations will usually + * receive a dictionary with: + * { ShowDialog: true } + * If the user does not enter a passcode, after 2 minutes a timeout is reached + * and the device sends a dictionary with: + * { Timeout: true } + * followed by { HideDialog: true } + * If the user aborts the passcode entry, the device sends a dictionary: + * { Error: 1, ErrorString: \<error string\> } + * followed by { HideDialog: true } + * + * @return PREBOARD_E_SUCCESS if the command was successfully submitted, + * PREBOARD_E_INVALID_ARG when client is invalid, + * or a PREBOARD_E_* error code on error. + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_create_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data); + +/** + * Instructs the preboard service to commit a previously created stashbag. + * + * @param client The preboard client to use for receiving + * @param manifest An optional manifest + * @param status_cb Callback function that will receive status and error messages + * Can be NULL if you want to handle receiving messages in your own code. + * @param user_data User data for callback function or NULL. + * + * The callback or following preboard_receive* invocations will usually + * receive a dictionary with: + * { StashbagCommitComplete: true } + * or in case of an error: + * { StashbagCommitComplete: 0, Error: 1, \<optional\> ErrorString: \<error string\> } + * + * @return PREBOARD_E_SUCCESS if the command was successfully submitted, + * PREBOARD_E_INVALID_ARG when client is invalid, + * or a PREBOARD_E_* error code on error. + */ +LIBIMOBILEDEVICE_API preboard_error_t preboard_commit_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/property_list_service.h b/include/libimobiledevice/property_list_service.h new file mode 100644 index 0000000..e6b26a3 --- /dev/null +++ b/include/libimobiledevice/property_list_service.h @@ -0,0 +1,184 @@ +/** + * @file libimobiledevice/property_list_service.h + * @brief Definitions for the PropertyList service + * \internal + * + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2014 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IPROPERTY_LIST_SERVICE_H +#define IPROPERTY_LIST_SERVICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/service.h> + +/** Error Codes */ +typedef enum { + PROPERTY_LIST_SERVICE_E_SUCCESS = 0, + PROPERTY_LIST_SERVICE_E_INVALID_ARG = -1, + PROPERTY_LIST_SERVICE_E_PLIST_ERROR = -2, + PROPERTY_LIST_SERVICE_E_MUX_ERROR = -3, + PROPERTY_LIST_SERVICE_E_SSL_ERROR = -4, + PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT = -5, + PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA = -6, + PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR = -256 +} property_list_service_error_t; + +typedef struct property_list_service_client_private property_list_service_private; /**< \private */ +typedef property_list_service_private* property_list_service_client_t; /**< The client handle. */ + +/* Interface */ + +/** + * Creates a new property list service for the specified port. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * property_list_service_client_t upon successful return. + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG when one of the arguments is invalid, + * or PROPERTY_LIST_SERVICE_E_MUX_ERROR when connecting to the device failed. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, property_list_service_client_t *client); + +/** + * Frees a PropertyList service. + * + * @param client The property list service to free. + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client is invalid, or a + * PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when another error occurred. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_client_free(property_list_service_client_t client); + +/** + * Sends an XML plist. + * + * @param client The property list service client to use for sending. + * @param plist plist to send + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or plist is NULL, + * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid plist, + * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified error occurs. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_send_xml_plist(property_list_service_client_t client, plist_t plist); + +/** + * Sends a binary plist. + * + * @param client The property list service client to use for sending. + * @param plist plist to send + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or plist is NULL, + * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid plist, + * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified error occurs. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_send_binary_plist(property_list_service_client_t client, plist_t plist); + +/** + * Receives a plist using the given property list service client with specified + * timeout. + * Binary or XML plists are automatically handled. + * + * @param client The property list service client to use for receiving + * @param plist pointer to a plist_t that will point to the received plist + * upon successful return + * @param timeout Maximum time in milliseconds to wait for data. + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG when connection or *plist is NULL, + * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be + * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a + * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when + * an unspecified error occurs. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_receive_plist_with_timeout(property_list_service_client_t client, plist_t *plist, unsigned int timeout); + +/** + * Receives a plist using the given property list service client. + * Binary or XML plists are automatically handled. + * + * This function is like property_list_service_receive_plist_with_timeout + * using a timeout of 10 seconds. + * @see property_list_service_receive_plist_with_timeout + * + * @param client The property list service client to use for receiving + * @param plist pointer to a plist_t that will point to the received plist + * upon successful return + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or *plist is NULL, + * PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA when not enough data + * received, PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT when the connection times out, + * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be + * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a + * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when + * an unspecified error occurs. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_receive_plist(property_list_service_client_t client, plist_t *plist); + +/** + * Enable SSL for the given property list service client. + * + * @param client The connected property list service client for which SSL + * should be enabled. + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG if one or more of the arguments are invalid, + * PROPERTY_LIST_SERVICE_E_SSL_ERROR when SSL could not be enabled, + * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_enable_ssl(property_list_service_client_t client); + +/** + * Disable SSL for the given property list service client. + * + * @param client The connected property list service client for which SSL + * should be disabled. + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG if one or more of the arguments are invalid, + * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_disable_ssl(property_list_service_client_t client); + +/** + * Return a handle to the parent #service_client_t of the given property list service client. + * + * @param client The property list service client + * @param service_client Pointer to be assigned to the parent #service_client_t + * + * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, + * PROPERTY_LIST_SERVICE_E_INVALID_ARG if one or more of the arguments are invalid. + */ +LIBIMOBILEDEVICE_API property_list_service_error_t property_list_service_get_service_client(property_list_service_client_t client, service_client_t *service_client); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/restore.h b/include/libimobiledevice/restore.h index 9c30b03..859dc98 100644 --- a/include/libimobiledevice/restore.h +++ b/include/libimobiledevice/restore.h @@ -1,8 +1,10 @@ /** * @file libimobiledevice/restore.h * @brief Initiate restore process or reboot device. + * @note This service is only available if the device is in restore mode. * \internal * + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2010 Joshua Hill. All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -20,8 +22,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef RESTORE_H -#define RESTORE_H +#ifndef IRESTORE_H +#define IRESTORE_H #ifdef __cplusplus extern "C" { @@ -29,41 +31,146 @@ extern "C" { #include <libimobiledevice/libimobiledevice.h> -/** @name Error Codes */ -/*@{*/ -#define RESTORE_E_SUCCESS 0 -#define RESTORE_E_INVALID_ARG -1 -#define RESTORE_E_INVALID_CONF -2 -#define RESTORE_E_PLIST_ERROR -3 -#define RESTORE_E_DICT_ERROR -4 -#define RESTORE_E_NOT_ENOUGH_DATA -5 -#define RESTORE_E_MUX_ERROR -6 -#define RESTORE_E_START_RESTORE_FAILED -7 +/** Error Codes */ +typedef enum { + RESTORE_E_SUCCESS = 0, + RESTORE_E_INVALID_ARG = -1, + RESTORE_E_PLIST_ERROR = -2, + RESTORE_E_MUX_ERROR = -3, + RESTORE_E_NOT_ENOUGH_DATA = -4, + RESTORE_E_RECEIVE_TIMEOUT = -5, + RESTORE_E_UNKNOWN_ERROR = -256 +} restored_error_t; -#define RESTORE_E_UNKNOWN_ERROR -256 -/*@}*/ - -/** Represents an error code. */ -typedef int16_t restored_error_t; - -typedef struct restored_client_private restored_client_private; +typedef struct restored_client_private restored_client_private; /**< \private */ typedef restored_client_private *restored_client_t; /**< The client handle. */ /* Interface */ -restored_error_t restored_client_new(idevice_t device, restored_client_t *client, const char *label); -restored_error_t restored_client_free(restored_client_t client); -restored_error_t restored_query_type(restored_client_t client, char **type, uint64_t *version); -restored_error_t restored_get_value(restored_client_t client, const char *key, plist_t *value) ; -restored_error_t restored_send(restored_client_t client, plist_t plist); -restored_error_t restored_receive(restored_client_t client, plist_t *plist); -restored_error_t restored_goodbye(restored_client_t client); +/** + * Creates a new restored client for the device. + * + * @param device The device to create a restored client for + * @param client The pointer to the location of the new restored_client + * @param label The label to use for communication. Usually the program name. + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API restored_error_t restored_client_new(idevice_t device, restored_client_t *client, const char *label); + +/** + * Closes the restored client session if one is running and frees up the + * restored_client struct. + * + * @param client The restore client + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API restored_error_t restored_client_free(restored_client_t client); + + +/** + * Query the type of the service daemon. Depending on whether the device is + * queried in normal mode or restore mode, different types will be returned. + * + * @param client The restored client + * @param type The type returned by the service daemon. Pass NULL to ignore. + * @param version The restore protocol version. Pass NULL to ignore. + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client is NULL + */ +LIBIMOBILEDEVICE_API restored_error_t restored_query_type(restored_client_t client, char **type, uint64_t *version); + +/** + * Queries a value from the device specified by a key. + * + * @param client An initialized restored client. + * @param key The key name to request + * @param value A plist node representing the result value node + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client is NULL, RESTORE_E_PLIST_ERROR if value for key can't be found + */ +LIBIMOBILEDEVICE_API restored_error_t restored_query_value(restored_client_t client, const char *key, plist_t *value); + +/** + * Retrieves a value from information plist specified by a key. + * + * @param client An initialized restored client. + * @param key The key name to request or NULL to query for all keys + * @param value A plist node representing the result value node + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client is NULL, RESTORE_E_PLIST_ERROR if value for key can't be found + */ +LIBIMOBILEDEVICE_API restored_error_t restored_get_value(restored_client_t client, const char *key, plist_t *value) ; + +/** + * Sends a plist to restored. + * + * @note This function is low-level and should only be used if you need to send + * a new type of message. + * + * @param client The restored client + * @param plist The plist to send + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client or + * plist is NULL + */ +LIBIMOBILEDEVICE_API restored_error_t restored_send(restored_client_t client, plist_t plist); + +/** + * Receives a plist from restored. + * + * @param client The restored client + * @param plist The plist to store the received data + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client or + * plist is NULL + */ +LIBIMOBILEDEVICE_API restored_error_t restored_receive(restored_client_t client, plist_t *plist); + +/** + * Sends the Goodbye request to restored signaling the end of communication. + * + * @param client The restore client + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG when client is NULL, + * RESTORE_E_PLIST_ERROR if the device did not acknowledge the request + */ +LIBIMOBILEDEVICE_API restored_error_t restored_goodbye(restored_client_t client); -restored_error_t restored_start_restore(restored_client_t client); -restored_error_t restored_reboot(restored_client_t client); + +/** + * Requests to start a restore and retrieve it's port on success. + * + * @param client The restored client + * @param options PLIST_DICT with options for the restore process or NULL + * @param version the restore protocol version, see restored_query_type() + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG if a parameter + * is NULL, RESTORE_E_START_RESTORE_FAILED if the request fails + */ +LIBIMOBILEDEVICE_API restored_error_t restored_start_restore(restored_client_t client, plist_t options, uint64_t version); + +/** + * Requests device to reboot. + * + * @param client The restored client + * + * @return RESTORE_E_SUCCESS on success, RESTORE_E_INVALID_ARG if a parameter + * is NULL + */ +LIBIMOBILEDEVICE_API restored_error_t restored_reboot(restored_client_t client); /* Helper */ -void restored_client_set_label(restored_client_t client, const char *label); + +/** + * Sets the label to send for requests to restored. + * + * @param client The restore client + * @param label The label to set or NULL to disable sending a label + * + */ +LIBIMOBILEDEVICE_API void restored_client_set_label(restored_client_t client, const char *label); #ifdef __cplusplus } diff --git a/include/libimobiledevice/reverse_proxy.h b/include/libimobiledevice/reverse_proxy.h new file mode 100644 index 0000000..5e2f54b --- /dev/null +++ b/include/libimobiledevice/reverse_proxy.h @@ -0,0 +1,213 @@ +/** + * @file libimobiledevice/reverse_proxy.h + * @brief Provide a reverse proxy to allow the device to communicate through, + * which is used during firmware restore. + * \internal + * + * Copyright (c) 2021 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IREVERSE_PROXY_H +#define IREVERSE_PROXY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> + +#define REVERSE_PROXY_DEFAULT_PORT 1082 /**< default port the reverse proxy is listening on */ + +/** Error Codes */ +typedef enum { + REVERSE_PROXY_E_SUCCESS = 0, + REVERSE_PROXY_E_INVALID_ARG = -1, + REVERSE_PROXY_E_PLIST_ERROR = -2, + REVERSE_PROXY_E_MUX_ERROR = -3, + REVERSE_PROXY_E_SSL_ERROR = -4, + REVERSE_PROXY_E_NOT_ENOUGH_DATA = -5, + REVERSE_PROXY_E_TIMEOUT = -6, + REVERSE_PROXY_E_UNKNOWN_ERROR = -256 +} reverse_proxy_error_t; + +typedef struct reverse_proxy_client_private reverse_proxy_client_private; /**< \private */ +typedef reverse_proxy_client_private *reverse_proxy_client_t; /**< The client handle. */ + +/** reverse proxy client type */ +typedef enum { + RP_TYPE_CTRL = 1, /**< control connection */ + RP_TYPE_CONN /**< proxy connection */ +} reverse_proxy_client_type_t; + +/** reverse proxy status for reverse_proxy_status_cb_t callback */ +typedef enum { + RP_STATUS_READY = 1, /**< proxy is ready */ + RP_STATUS_TERMINATE, /**< proxy terminated */ + RP_STATUS_CONNECT_REQ, /**< connection request received (only RP_TYPE_CTRL) */ + RP_STATUS_SHUTDOWN_REQ, /**< shutdown request received (only RP_TYPE_CTRL) */ + RP_STATUS_CONNECTED, /**< connection established (only RP_TYPE_CONN) */ + RP_STATUS_DISCONNECTED, /**< connection closed (only RP_TYPE_CONN) */ +} reverse_proxy_status_t; + +/** reverse proxy data direction passed to reverse_proxy_data_cb_t callback */ +typedef enum { + RP_DATA_DIRECTION_OUT = 1, /**< data going out to remote host */ + RP_DATA_DIRECTION_IN /**< data coming in from remote host */ +} reverse_proxy_data_direction_t; + +/** + * Log callback function prototype. + * + * @param client The client that called the callback function + * @param log_msg The log message + * @param user_data The user_data pointer that was set when registering the callback + */ +typedef void (*reverse_proxy_log_cb_t) (reverse_proxy_client_t client, const char* log_msg, void* user_data); + +/** + * Data callback function prototype. + * + * @param client The client that called the callback function + * @param direction The direction of the data, either RP_DATA_DIRECTION_OUT or RP_DATA_DIRECTION_IN + * @param buffer The data buffer + * @param length The length of the data buffer + * @param user_data The user_data pointer that was set when registering the callback + */ +typedef void (*reverse_proxy_data_cb_t) (reverse_proxy_client_t client, reverse_proxy_data_direction_t direction, const char* buffer, uint32_t length, void* user_data); + +/** + * Status callback function prototype. + * + * @param client The client that called the callback function + * @param status The status the client is reporting + * @param status_msg A status message the client reports along with the status + * @param user_data The user_data pointer that was set when registering the callback + */ +typedef void (*reverse_proxy_status_cb_t) (reverse_proxy_client_t client, reverse_proxy_status_t status, const char* status_msg, void* user_data); + +/** + * Create a reverse proxy client using com.apple.PurpleReverseProxy.Ctrl and + * com.apple.PurpleReverseProxy.Conn lockdown services. This will open a port + * 1083 on the device that iOS apps could connect to; \b however that is + * only allowed if an app has the com.apple.private.PurpleReverseProxy.allowed + * entitlement, which currently only \c /usr/libexec/fdrhelper holds. + * + * @note This function only creates and initializes the reverse proxy; + * to make it operational, call reverse_proxy_client_start_proxy(). + * + * @param device The device to connect to. + * @param client Pointer that will be set to a newly allocated #reverse_proxy_client_t + * upon successful return. + * @param label A label to pass to lockdownd when creating the service + * connections, usually the program name. + * + * @return REVERSE_PROXY_E_SUCCESS on success, + * or a REVERSE_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_create_with_service(idevice_t device, reverse_proxy_client_t* client, const char* label); + +/** + * Create a reverse proxy client using an open port on the device. This is + * used during firmware restores with the default port REVERSE_PROXY_DEFAULT_PORT (1082). + * + * @note This function only creates and initializes the reverse proxy; + * to make it operational, call reverse_proxy_client_start_proxy(). + * + * @param device The device to connect to. + * @param client Pointer that will be set to a newly allocated reverse_proxy_client_t + * upon successful return. + * @param device_port An open port on the device. Unless it's being used for + * a custom implementation, pass REVERSE_PROXY_DEFAULT_PORT here. + * + * @return REVERSE_PROXY_E_SUCCESS on success, + * or a REVERSE_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_create_with_port(idevice_t device, reverse_proxy_client_t* client, uint16_t device_port); + +/** + * Disconnects a reverse proxy client and frees up the client data. + * + * @param client The reverse proxy client to disconnect and free. + */ +LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_free(reverse_proxy_client_t client); + +/** + * Make an initialized reverse proxy client operational, i.e. start the actual proxy. + * + * @param client The reverse proxy client to start. + * @param control_protocol_version The control protocol version to use. + * This is either 1 or 2. Recent devices use 2. + * + * @return REVERSE_PROXY_E_SUCCESS on success, + * or a REVERSE_PROXY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_start_proxy(reverse_proxy_client_t client, int control_protocol_version); + +/** + * Set a status callback function. This allows to report the status of the + * reverse proxy, like Ready, Connect Request, Connected, etc. + * + * @note Set the callback before calling reverse_proxy_client_start_proxy(). + * + * @param client The reverse proxy client + * @param callback The status callback function that will be called + * when the status of the reverse proxy changes. + * @param user_data A pointer that will be passed to the callback function. + */ +LIBIMOBILEDEVICE_API void reverse_proxy_client_set_status_callback(reverse_proxy_client_t client, reverse_proxy_status_cb_t callback, void* user_data); + +/** + * Set a log callback function. Useful for debugging or verbosity. + * + * @note Set the callback before calling reverse_proxy_client_start_proxy(). + * + * @param client The reverse proxy client + * @param callback The log callback function that will be called + * when the reverse proxy logs something. + * @param user_data A pointer that will be passed to the callback function. + */ +LIBIMOBILEDEVICE_API void reverse_proxy_client_set_log_callback(reverse_proxy_client_t client, reverse_proxy_log_cb_t callback, void* user_data); + +/** + * Set a data callback function. Useful for debugging or extra verbosity. + * + * @note Set the callback before calling reverse_proxy_client_start_proxy(). + * + * @param client The reverse proxy client + * @param callback The status callback function that will be called + * when the status of the reverse proxy changes. + * @param user_data A pointer that will be passed to the callback function. + */ + +LIBIMOBILEDEVICE_API void reverse_proxy_client_set_data_callback(reverse_proxy_client_t client, reverse_proxy_data_cb_t callback, void* user_data); + +/** + * Helper function to return the type of a given reverse proxy client, which + * is either RP_TYPE_CTRL or RP_TYPE_CONN. Useful for callback functions. + * @see reverse_proxy_client_type_t + * + * @param client The reverse proxy client + * + * @return The type of the rerverse proxy client + */ +LIBIMOBILEDEVICE_API reverse_proxy_client_type_t reverse_proxy_get_type(reverse_proxy_client_t client); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/sbservices.h b/include/libimobiledevice/sbservices.h index 616168e..7435947 100644 --- a/include/libimobiledevice/sbservices.h +++ b/include/libimobiledevice/sbservices.h @@ -3,7 +3,8 @@ * @brief Manage SpringBoard icons and retrieve icon images. * \internal * - * Copyright (c) 2009 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2009-2010 Nikias Bassen All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,38 +21,152 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef SB_SERVICES_H -#define SB_SERVICES_H +#ifndef ISB_SERVICES_H +#define ISB_SERVICES_H #ifdef __cplusplus extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define SBSERVICES_E_SUCCESS 0 -#define SBSERVICES_E_INVALID_ARG -1 -#define SBSERVICES_E_PLIST_ERROR -2 -#define SBSERVICES_E_CONN_FAILED -3 +/** Service identifier passed to lockdownd_start_service() to start the springboardservices service */ +#define SBSERVICES_SERVICE_NAME "com.apple.springboardservices" -#define SBSERVICES_E_UNKNOWN_ERROR -256 -/*@}*/ +/** Error Codes */ +typedef enum { + SBSERVICES_E_SUCCESS = 0, + SBSERVICES_E_INVALID_ARG = -1, + SBSERVICES_E_PLIST_ERROR = -2, + SBSERVICES_E_CONN_FAILED = -3, + SBSERVICES_E_UNKNOWN_ERROR = -256 +} sbservices_error_t; -/** Represents an error code. */ -typedef int16_t sbservices_error_t; +/** Orientation of the user interface on the device */ +typedef enum { + SBSERVICES_INTERFACE_ORIENTATION_UNKNOWN = 0, + SBSERVICES_INTERFACE_ORIENTATION_PORTRAIT = 1, + SBSERVICES_INTERFACE_ORIENTATION_PORTRAIT_UPSIDE_DOWN = 2, + SBSERVICES_INTERFACE_ORIENTATION_LANDSCAPE_RIGHT = 3, + SBSERVICES_INTERFACE_ORIENTATION_LANDSCAPE_LEFT = 4 +} sbservices_interface_orientation_t; -typedef struct sbservices_client_private sbservices_client_private; +typedef struct sbservices_client_private sbservices_client_private; /**< \private */ typedef sbservices_client_private *sbservices_client_t; /**< The client handle. */ /* Interface */ -sbservices_error_t sbservices_client_new(idevice_t device, uint16_t port, sbservices_client_t *client); -sbservices_error_t sbservices_client_free(sbservices_client_t client); -sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t *state, const char *format_version); -sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t newstate); -sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const char *bundleId, char **pngdata, uint64_t *pngsize); -sbservices_error_t sbservices_get_home_screen_wallpaper_pngdata(sbservices_client_t client, char **pngdata, uint64_t *pngsize); + +/** + * Connects to the springboardservices service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * sbservices_client_t upon successful return. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client is NULL, or an SBSERVICES_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_client_new(idevice_t device, lockdownd_service_descriptor_t service, sbservices_client_t *client); + +/** + * Starts a new sbservices service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * sbservices_client_t upon successful return. Must be freed using + * sbservices_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return SBSERVICES_E_SUCCESS on success, or an SBSERVICES_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_client_start_service(idevice_t device, sbservices_client_t* client, const char* label); + +/** + * Disconnects an sbservices client from the device and frees up the + * sbservices client data. + * + * @param client The sbservices client to disconnect and free. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client is NULL, or an SBSERVICES_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_client_free(sbservices_client_t client); + + +/** + * Gets the icon state of the connected device. + * + * @param client The connected sbservices client to use. + * @param state Pointer that will point to a newly allocated plist containing + * the current icon state. It is up to the caller to free the memory. + * @param format_version A string to be passed as formatVersion along with + * the request, or NULL if no formatVersion should be passed. This is only + * supported since iOS 4.0 so for older firmware versions this must be set + * to NULL. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client or state is invalid, or an SBSERVICES_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t *state, const char *format_version); + +/** + * Sets the icon state of the connected device. + * + * @param client The connected sbservices client to use. + * @param newstate A plist containing the new iconstate. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client or newstate is NULL, or an SBSERVICES_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t newstate); + +/** + * Get the icon of the specified app as PNG data. + * + * @param client The connected sbservices client to use. + * @param bundleId The bundle identifier of the app to retrieve the icon for. + * @param pngdata Pointer that will point to a newly allocated buffer + * containing the PNG data upon successful return. It is up to the caller + * to free the memory. + * @param pngsize Pointer to a uint64_t that will be set to the size of the + * buffer pngdata points to upon successful return. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client, bundleId, or pngdata are invalid, or an SBSERVICES_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const char *bundleId, char **pngdata, uint64_t *pngsize); + +/** + * Gets the interface orientation of the device. + * + * @param client The connected sbservices client to use. + * @param interface_orientation The interface orientation upon successful return. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client or state is invalid, or an SBSERVICES_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_get_interface_orientation(sbservices_client_t client, sbservices_interface_orientation_t* interface_orientation); + +/** + * Get the home screen wallpaper as PNG data. + * + * @param client The connected sbservices client to use. + * @param pngdata Pointer that will point to a newly allocated buffer + * containing the PNG data upon successful return. It is up to the caller + * to free the memory. + * @param pngsize Pointer to a uint64_t that will be set to the size of the + * buffer pngdata points to upon successful return. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client or pngdata are invalid, or an SBSERVICES_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API sbservices_error_t sbservices_get_home_screen_wallpaper_pngdata(sbservices_client_t client, char **pngdata, uint64_t *pngsize); #ifdef __cplusplus } diff --git a/include/libimobiledevice/screenshotr.h b/include/libimobiledevice/screenshotr.h index b3669ee..db3c969 100644 --- a/include/libimobiledevice/screenshotr.h +++ b/include/libimobiledevice/screenshotr.h @@ -4,7 +4,8 @@ * @note Requires a mounted developer image. * \internal * - * Copyright (c) 2010 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2014 Martin Szulecki, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,27 +30,86 @@ extern "C" { #endif #include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> -/** @name Error Codes */ -/*@{*/ -#define SCREENSHOTR_E_SUCCESS 0 -#define SCREENSHOTR_E_INVALID_ARG -1 -#define SCREENSHOTR_E_PLIST_ERROR -2 -#define SCREENSHOTR_E_MUX_ERROR -3 -#define SCREENSHOTR_E_BAD_VERSION -4 +/** Service identifier passed to lockdownd_start_service() to start the screenshotr service */ +#define SCREENSHOTR_SERVICE_NAME "com.apple.mobile.screenshotr" -#define SCREENSHOTR_E_UNKNOWN_ERROR -256 -/*@}*/ +/** Error Codes */ +typedef enum { + SCREENSHOTR_E_SUCCESS = 0, + SCREENSHOTR_E_INVALID_ARG = -1, + SCREENSHOTR_E_PLIST_ERROR = -2, + SCREENSHOTR_E_MUX_ERROR = -3, + SCREENSHOTR_E_SSL_ERROR = -4, + SCREENSHOTR_E_RECEIVE_TIMEOUT = -5, + SCREENSHOTR_E_BAD_VERSION = -6, + SCREENSHOTR_E_UNKNOWN_ERROR = -256 +} screenshotr_error_t; -/** Represents an error code. */ -typedef int16_t screenshotr_error_t; - -typedef struct screenshotr_client_private screenshotr_client_private; +typedef struct screenshotr_client_private screenshotr_client_private; /**< \private */ typedef screenshotr_client_private *screenshotr_client_t; /**< The client handle. */ -screenshotr_error_t screenshotr_client_new(idevice_t device, uint16_t port, screenshotr_client_t * client); -screenshotr_error_t screenshotr_client_free(screenshotr_client_t client); -screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, char **imgdata, uint64_t *imgsize); + +/** + * Connects to the screenshotr service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * screenshotr_client_t upon successful return. + * + * @note This service is only available if a developer disk image has been + * mounted. + * + * @return SCREENSHOTR_E_SUCCESS on success, SCREENSHOTR_E_INVALID ARG if one + * or more parameters are invalid, or SCREENSHOTR_E_CONN_FAILED if the + * connection to the device could not be established. + */ +LIBIMOBILEDEVICE_API screenshotr_error_t screenshotr_client_new(idevice_t device, lockdownd_service_descriptor_t service, screenshotr_client_t * client); + +/** + * Starts a new screenshotr service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * screenshotr_client_t upon successful return. Must be freed using + * screenshotr_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return SCREENSHOTR_E_SUCCESS on success, or an SCREENSHOTR_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API screenshotr_error_t screenshotr_client_start_service(idevice_t device, screenshotr_client_t* client, const char* label); + +/** + * Disconnects a screenshotr client from the device and frees up the + * screenshotr client data. + * + * @param client The screenshotr client to disconnect and free. + * + * @return SCREENSHOTR_E_SUCCESS on success, or SCREENSHOTR_E_INVALID_ARG + * if client is NULL. + */ +LIBIMOBILEDEVICE_API screenshotr_error_t screenshotr_client_free(screenshotr_client_t client); + + +/** + * Get a screen shot from the connected device. + * + * @param client The connection screenshotr service client. + * @param imgdata Pointer that will point to a newly allocated buffer + * containing TIFF image data upon successful return. It is up to the + * caller to free the memory. + * @param imgsize Pointer to a uint64_t that will be set to the size of the + * buffer imgdata points to upon successful return. + * + * @return SCREENSHOTR_E_SUCCESS on success, SCREENSHOTR_E_INVALID_ARG if + * one or more parameters are invalid, or another error code if an + * error occurred. + */ +LIBIMOBILEDEVICE_API screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, char **imgdata, uint64_t *imgsize); #ifdef __cplusplus } diff --git a/include/libimobiledevice/service.h b/include/libimobiledevice/service.h new file mode 100644 index 0000000..f31ada4 --- /dev/null +++ b/include/libimobiledevice/service.h @@ -0,0 +1,202 @@ +/** + * @file libimobiledevice/service.h + * @brief Generic basic service implementation to inherit. + * \internal + * + * Copyright (c) 2013-2014 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ISERVICE_H +#define ISERVICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Error Codes */ +typedef enum { + SERVICE_E_SUCCESS = 0, + SERVICE_E_INVALID_ARG = -1, + SERVICE_E_MUX_ERROR = -3, + SERVICE_E_SSL_ERROR = -4, + SERVICE_E_START_SERVICE_ERROR = -5, + SERVICE_E_NOT_ENOUGH_DATA = -6, + SERVICE_E_TIMEOUT = -7, + SERVICE_E_UNKNOWN_ERROR = -256 +} service_error_t; + +typedef struct service_client_private service_client_private; /**< \private */ +typedef service_client_private* service_client_t; /**< The client handle. */ + +/** service constructor cast */ +#define SERVICE_CONSTRUCTOR(x) (int32_t (*)(idevice_t, lockdownd_service_descriptor_t, void**))(x) + +/* Interface */ + +/** + * Creates a new service for the specified service descriptor. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will be set to a newly allocated + * service_client_t upon successful return. + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG when one of the arguments is invalid, + * or SERVICE_E_MUX_ERROR when connecting to the device failed. + */ +LIBIMOBILEDEVICE_API service_error_t service_client_new(idevice_t device, lockdownd_service_descriptor_t service, service_client_t *client); + +/** + * Starts a new service on the specified device with given name and + * connects to it. + * + * @param device The device to connect to. + * @param service_name The name of the service to start. + * @param client Pointer that will point to a newly allocated service_client_t + * upon successful return. Must be freed using service_client_free() after + * use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * @param constructor_func Constructor function for the service client to create (e.g. afc_client_new()) + * @param error_code Pointer to an int32_t that will receive the service start error code. + * + * @return SERVICE_E_SUCCESS on success, or a SERVICE_E_* error code + * otherwise. + */ +LIBIMOBILEDEVICE_API service_error_t service_client_factory_start_service(idevice_t device, const char* service_name, void **client, const char* label, int32_t (*constructor_func)(idevice_t, lockdownd_service_descriptor_t, void**), int32_t *error_code); + +/** + * Frees a service instance. + * + * @param client The service instance to free. + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG when client is invalid, or a + * SERVICE_E_UNKNOWN_ERROR when another error occurred. + */ +LIBIMOBILEDEVICE_API service_error_t service_client_free(service_client_t client); + + +/** + * Sends data using the given service client. + * + * @param client The service client to use for sending. + * @param data Data to send + * @param size Size of the data to send + * @param sent Number of bytes sent (can be NULL to ignore) + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG when one or more parameters are + * invalid, or SERVICE_E_UNKNOWN_ERROR when an unspecified + * error occurs. + */ +LIBIMOBILEDEVICE_API service_error_t service_send(service_client_t client, const char *data, uint32_t size, uint32_t *sent); + +/** + * Receives data using the given service client with specified timeout. + * + * @param client The service client to use for receiving + * @param data Buffer that will be filled with the data received + * @param size Number of bytes to receive + * @param received Number of bytes received (can be NULL to ignore) + * @param timeout Maximum time in milliseconds to wait for data. + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG when one or more parameters are + * invalid, SERVICE_E_MUX_ERROR when a communication error + * occurs, or SERVICE_E_UNKNOWN_ERROR when an unspecified + * error occurs. + */ +LIBIMOBILEDEVICE_API service_error_t service_receive_with_timeout(service_client_t client, char *data, uint32_t size, uint32_t *received, unsigned int timeout); + +/** + * Receives data using the given service client. + * + * @param client The service client to use for receiving + * @param data Buffer that will be filled with the data received + * @param size Number of bytes to receive + * @param received Number of bytes received (can be NULL to ignore) + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG when one or more parameters are + * invalid, SERVICE_E_NOT_ENOUGH_DATA when not enough data + * received, SERVICE_E_TIMEOUT when the connection times out, + * SERVICE_E_MUX_ERROR when a communication error + * occurs, or SERVICE_E_UNKNOWN_ERROR when an unspecified + * error occurs. + */ +LIBIMOBILEDEVICE_API service_error_t service_receive(service_client_t client, char *data, uint32_t size, uint32_t *received); + + +/** + * Enable SSL for the given service client. + * + * @param client The connected service client for that SSL should be enabled. + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG if client or client->connection is + * NULL, SERVICE_E_NOT_ENOUGH_DATA when not enough data + * received, SERVICE_E_TIMEOUT when the connection times out, + * SERVICE_E_SSL_ERROR when SSL could not be enabled, + * or SERVICE_E_UNKNOWN_ERROR otherwise. + */ +LIBIMOBILEDEVICE_API service_error_t service_enable_ssl(service_client_t client); + +/** + * Disable SSL for the given service client. + * + * @param client The connected service client for which SSL should be disabled. + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG if client or client->connection is + * NULL, or SERVICE_E_UNKNOWN_ERROR otherwise. + */ +LIBIMOBILEDEVICE_API service_error_t service_disable_ssl(service_client_t client); + +/** + * Disable SSL for the given service client, optionally without sending SSL terminate messages. + * + * @param client The connected service client for which SSL should be disabled. + * @param sslBypass A boolean value indicating wether to disable SSL with a proper + * SSL shutdown (0), or bypass the shutdown (1). + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG if client or client->connection is + * NULL, or SERVICE_E_UNKNOWN_ERROR otherwise. + */ +LIBIMOBILEDEVICE_API service_error_t service_disable_bypass_ssl(service_client_t client, uint8_t sslBypass); + +/** + * Return a handle to the parent #idevice_connection_t of the given service client. + * + * @param client The service client + * @param connection Pointer to be assigned to the #idevice_connection_t. + * + * @return SERVICE_E_SUCCESS on success, + * SERVICE_E_INVALID_ARG if one or more of the arguments are invalid. + */ +LIBIMOBILEDEVICE_API service_error_t service_get_connection(service_client_t client, idevice_connection_t *connection); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/syslog_relay.h b/include/libimobiledevice/syslog_relay.h new file mode 100644 index 0000000..0f6487a --- /dev/null +++ b/include/libimobiledevice/syslog_relay.h @@ -0,0 +1,184 @@ +/** + * @file libimobiledevice/syslog_relay.h + * @brief Capture the syslog output from a device. + * \internal + * + * Copyright (c) 2019-2020 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2013-2014 Martin Szulecki, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ISYSLOG_RELAY_H +#define ISYSLOG_RELAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the syslog relay service */ +#define SYSLOG_RELAY_SERVICE_NAME "com.apple.syslog_relay" + +/** Error Codes */ +typedef enum { + SYSLOG_RELAY_E_SUCCESS = 0, + SYSLOG_RELAY_E_INVALID_ARG = -1, + SYSLOG_RELAY_E_MUX_ERROR = -2, + SYSLOG_RELAY_E_SSL_ERROR = -3, + SYSLOG_RELAY_E_NOT_ENOUGH_DATA = -4, + SYSLOG_RELAY_E_TIMEOUT = -5, + SYSLOG_RELAY_E_UNKNOWN_ERROR = -256 +} syslog_relay_error_t; + +typedef struct syslog_relay_client_private syslog_relay_client_private; /**< \private */ +typedef syslog_relay_client_private *syslog_relay_client_t; /**< The client handle. */ + +/** Receives each character received from the device. */ +typedef void (*syslog_relay_receive_cb_t)(char c, void *user_data); + +/* Interface */ + +/** + * Connects to the syslog_relay service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * syslog_relay_client_t upon successful return. Must be freed using + * syslog_relay_client_free() after use. + * + * @return SYSLOG_RELAY_E_SUCCESS on success, SYSLOG_RELAY_E_INVALID_ARG when + * client is NULL, or an SYSLOG_RELAY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, syslog_relay_client_t * client); + +/** + * Starts a new syslog_relay service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * syslog_relay_client_t upon successful return. Must be freed using + * syslog_relay_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return SYSLOG_RELAY_E_SUCCESS on success, or an SYSLOG_RELAY_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_client_start_service(idevice_t device, syslog_relay_client_t * client, const char* label); + +/** + * Disconnects a syslog_relay client from the device and frees up the + * syslog_relay client data. + * + * @param client The syslog_relay client to disconnect and free. + * + * @return SYSLOG_RELAY_E_SUCCESS on success, SYSLOG_RELAY_E_INVALID_ARG when + * client is NULL, or an SYSLOG_RELAY_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_client_free(syslog_relay_client_t client); + + +/** + * Starts capturing the syslog of the device using a callback. + * + * Use syslog_relay_stop_capture() to stop receiving the syslog. + * + * @param client The syslog_relay client to use + * @param callback Callback to receive each character from the syslog. + * @param user_data Custom pointer passed to the callback function. + * + * @return SYSLOG_RELAY_E_SUCCESS on success, + * SYSLOG_RELAY_E_INVALID_ARG when one or more parameters are + * invalid or SYSLOG_RELAY_E_UNKNOWN_ERROR when an unspecified + * error occurs or a syslog capture has already been started. + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_start_capture(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data); + +/** + * Starts capturing the *raw* syslog of the device using a callback. + * This function is like syslog_relay_start_capture with the difference that + * it will neither check nor process the received data before passing it to + * the callback function. + * + * Use syslog_relay_stop_capture() to stop receiving the syslog. + * + * @note Use syslog_relay_start_capture for a safer implementation. + * + * @param client The syslog_relay client to use + * @param callback Callback to receive each character from the syslog. + * @param user_data Custom pointer passed to the callback function. + * + * @return SYSLOG_RELAY_E_SUCCESS on success, + * SYSLOG_RELAY_E_INVALID_ARG when one or more parameters are + * invalid or SYSLOG_RELAY_E_UNKNOWN_ERROR when an unspecified + * error occurs or a syslog capture has already been started. + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_start_capture_raw(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data); + +/** + * Stops capturing the syslog of the device. + * + * Use syslog_relay_start_capture() to start receiving the syslog. + * + * @param client The syslog_relay client to use + * + * @return SYSLOG_RELAY_E_SUCCESS on success, + * SYSLOG_RELAY_E_INVALID_ARG when one or more parameters are + * invalid or SYSLOG_RELAY_E_UNKNOWN_ERROR when an unspecified + * error occurs or a syslog capture has already been started. + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_stop_capture(syslog_relay_client_t client); + +/* Receiving */ + +/** + * Receives data using the given syslog_relay client with specified timeout. + * + * @param client The syslog_relay client to use for receiving + * @param data Buffer that will be filled with the data received + * @param size Number of bytes to receive + * @param received Number of bytes received (can be NULL to ignore) + * @param timeout Maximum time in milliseconds to wait for data. + * + * @return SYSLOG_RELAY_E_SUCCESS on success, + * SYSLOG_RELAY_E_INVALID_ARG when one or more parameters are + * invalid, SYSLOG_RELAY_E_MUX_ERROR when a communication error + * occurs, or SYSLOG_RELAY_E_UNKNOWN_ERROR when an unspecified + * error occurs. + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_receive_with_timeout(syslog_relay_client_t client, char *data, uint32_t size, uint32_t *received, unsigned int timeout); + +/** + * Receives data from the service. + * + * @param client The syslog_relay client + * @param data Buffer that will be filled with the data received + * @param size Number of bytes to receive + * @param received Number of bytes received (can be NULL to ignore) + * + * @return SYSLOG_RELAY_E_SUCCESS on success, + * SYSLOG_RELAY_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API syslog_relay_error_t syslog_relay_receive(syslog_relay_client_t client, char *data, uint32_t size, uint32_t *received); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libimobiledevice/webinspector.h b/include/libimobiledevice/webinspector.h new file mode 100644 index 0000000..16d2ca2 --- /dev/null +++ b/include/libimobiledevice/webinspector.h @@ -0,0 +1,137 @@ +/** + * @file libimobiledevice/webinspector.h + * @brief WebKit Remote Debugging. + * \internal + * + * Copyright (c) 2013-2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2013 Yury Melnichek All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IWEBINSPECTOR_H +#define IWEBINSPECTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +/** Service identifier passed to lockdownd_start_service() to start the webinspector service */ +#define WEBINSPECTOR_SERVICE_NAME "com.apple.webinspector" + +/** Error Codes */ +typedef enum { + WEBINSPECTOR_E_SUCCESS = 0, + WEBINSPECTOR_E_INVALID_ARG = -1, + WEBINSPECTOR_E_PLIST_ERROR = -2, + WEBINSPECTOR_E_MUX_ERROR = -3, + WEBINSPECTOR_E_SSL_ERROR = -4, + WEBINSPECTOR_E_RECEIVE_TIMEOUT = -5, + WEBINSPECTOR_E_NOT_ENOUGH_DATA = -6, + WEBINSPECTOR_E_UNKNOWN_ERROR = -256 +} webinspector_error_t; + +typedef struct webinspector_client_private webinspector_client_private; /**< \private */ +typedef webinspector_client_private *webinspector_client_t; /**< The client handle. */ + + +/** + * Connects to the webinspector service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * webinspector_client_t upon successful return. Must be freed using + * webinspector_client_free() after use. + * + * @return WEBINSPECTOR_E_SUCCESS on success, WEBINSPECTOR_E_INVALID_ARG when + * client is NULL, or an WEBINSPECTOR_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API webinspector_error_t webinspector_client_new(idevice_t device, lockdownd_service_descriptor_t service, webinspector_client_t * client); + +/** + * Starts a new webinspector service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * webinspector_client_t upon successful return. Must be freed using + * webinspector_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return WEBINSPECTOR_E_SUCCESS on success, or an WEBINSPECTOR_E_* error + * code otherwise. + */ +LIBIMOBILEDEVICE_API webinspector_error_t webinspector_client_start_service(idevice_t device, webinspector_client_t * client, const char* label); + +/** + * Disconnects a webinspector client from the device and frees up the + * webinspector client data. + * + * @param client The webinspector client to disconnect and free. + * + * @return WEBINSPECTOR_E_SUCCESS on success, WEBINSPECTOR_E_INVALID_ARG when + * client is NULL, or an WEBINSPECTOR_E_* error code otherwise. + */ +LIBIMOBILEDEVICE_API webinspector_error_t webinspector_client_free(webinspector_client_t client); + + +/** + * Sends a plist to the service. + * + * @param client The webinspector client + * @param plist The plist to send + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API webinspector_error_t webinspector_send(webinspector_client_t client, plist_t plist); + +/** + * Receives a plist from the service. + * + * @param client The webinspector client + * @param plist The plist to store the received data + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client or plist is NULL + */ +LIBIMOBILEDEVICE_API webinspector_error_t webinspector_receive(webinspector_client_t client, plist_t * plist); + +/** + * Receives a plist using the given webinspector client. + * + * @param client The webinspector client to use for receiving + * @param plist pointer to a plist_t that will point to the received plist + * upon successful return + * @param timeout_ms Maximum time in milliseconds to wait for data. + * + * @return WEBINSPECTOR_E_SUCCESS on success, + * WEBINSPECTOR_E_INVALID_ARG when client or *plist is NULL, + * WEBINSPECTOR_E_PLIST_ERROR when the received data cannot be + * converted to a plist, WEBINSPECTOR_E_MUX_ERROR when a + * communication error occurs, or WEBINSPECTOR_E_UNKNOWN_ERROR + * when an unspecified error occurs. + */ +LIBIMOBILEDEVICE_API webinspector_error_t webinspector_receive_with_timeout(webinspector_client_t client, plist_t * plist, uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libimobiledevice-1.0.pc.in b/libimobiledevice-1.0.pc.in deleted file mode 100644 index 526edcf..0000000 --- a/libimobiledevice-1.0.pc.in +++ /dev/null @@ -1,12 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libimobiledevice -Description: A library to communicate with services running on Apple iPhone/iPod Touch devices. -Version: @VERSION@ -Requires: libplist >= 0.12 libusbmuxd >= 0.1.0 glib-2.0 >= 2.14.1 gthread-2.0 >= 2.14.1 gnutls >= 1.6.3 libtasn1 >= 1.1 -Libs: -L${libdir} -limobiledevice -Cflags: -I${includedir} - diff --git a/m4/ac_pkg_cython.m4 b/m4/ac_pkg_cython.m4 new file mode 100644 index 0000000..e0af96a --- /dev/null +++ b/m4/ac_pkg_cython.m4 @@ -0,0 +1,67 @@ + +AC_DEFUN([AC_PROG_CYTHON],[ + AC_PATH_PROGS([CYTHON],[cython cython3]) + if test -z "$CYTHON" ; then + AC_MSG_WARN([Unable to find 'cython' or 'cython3' program. You should look at https://cython.org or install your distribution specific cython package.]) + CYTHON=false + elif test -n "$1" ; then + AC_MSG_CHECKING([for Cython version]) + [cython_version=`$CYTHON --version 2>&1 | sed 's/Cython version \(.*\)$/\1/g'`] + AC_MSG_RESULT([$cython_version]) + + # Setup extra version string for parsing + [cython_version_stripped=`echo $cython_version | sed 's/\([0-9]\+\)\.\([0-9]\+\)[^\.]*\(\.\([0-9]\+\)\)\?.*/0\1.0\2.0\4/g'`] + if test -n "$cython_version" ; then + # Calculate the required version number components + [required=$1] + [required_major=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_major" ; then + [required_major=0] + fi + [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] + [required_minor=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_minor" ; then + [required_minor=0] + fi + [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] + [required_patch=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_patch" ; then + [required_patch=0] + fi + + # Calculate the available version number components + [available=$cython_version_stripped] + [available_major=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_major" ; then + [available_major=0] + fi + [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] + [available_minor=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_minor" ; then + [available_minor=0] + fi + [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] + [available_patch=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_patch" ; then + [available_patch=0] + fi + + if test $available_major -gt $required_major || \ + ( test $available_major -eq $required_major && \ + test $available_minor -gt $required_minor ) || \ + ( test $available_major -eq $required_major && \ + test $available_minor -eq $required_minor && \ + test $available_patch -ge $required_patch ) ; then + + AC_MSG_NOTICE([Cython executable is '$CYTHON']) + else + AC_MSG_WARN([Cython version >= $1 is required. You have $cython_version. You should look at http://www.cython.org]) + CYTHON='echo "Error: Cython version >= $1 is required. You have '"$cython_version"'. You should look at http://www.cython.org" ; false' + fi + else + AC_MSG_WARN([Unable to determine Cython version]) + CYTHON=false + fi + fi + AC_SUBST([CYTHON_LIB]) +]) diff --git a/m4/ac_pkg_swig.m4 b/m4/ac_pkg_swig.m4 deleted file mode 100644 index 97244bc..0000000 --- a/m4/ac_pkg_swig.m4 +++ /dev/null @@ -1,122 +0,0 @@ -# =========================================================================== -# http://autoconf-archive.cryp.to/ac_pkg_swig.html -# =========================================================================== -# -# SYNOPSIS -# -# AC_PROG_SWIG([major.minor.micro]) -# -# DESCRIPTION -# -# This macro searches for a SWIG installation on your system. If found you -# should call SWIG via $(SWIG). You can use the optional first argument to -# check if the version of the available SWIG is greater than or equal to -# the value of the argument. It should have the format: N[.N[.N]] (N is a -# number between 0 and 999. Only the first N is mandatory.) -# -# If the version argument is given (e.g. 1.3.17), AC_PROG_SWIG checks that -# the swig package is this version number or higher. -# -# In configure.in, use as: -# -# AC_PROG_SWIG(1.3.17) -# SWIG_ENABLE_CXX -# SWIG_MULTI_MODULE_SUPPORT -# SWIG_PYTHON -# -# LAST MODIFICATION -# -# 2008-04-12 -# -# COPYLEFT -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Macro Archive. When you make and -# distribute a modified version of the Autoconf Macro, you may extend this -# special exception to the GPL to apply to your modified version as well. - -AC_DEFUN([AC_PROG_SWIG],[ - AC_PATH_PROG([SWIG],[swig]) - if test -z "$SWIG" ; then - AC_MSG_WARN([cannot find 'swig' program. You should look at http://www.swig.org] or install your distribution specific swig package.) - SWIG=false - elif test -n "$1" ; then - AC_MSG_CHECKING([for SWIG version]) - [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`] - AC_MSG_RESULT([$swig_version]) - if test -n "$swig_version" ; then - # Calculate the required version number components - [required=$1] - [required_major=`echo $required | sed 's/[^0-9].*//'`] - if test -z "$required_major" ; then - [required_major=0] - fi - [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] - [required_minor=`echo $required | sed 's/[^0-9].*//'`] - if test -z "$required_minor" ; then - [required_minor=0] - fi - [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] - [required_patch=`echo $required | sed 's/[^0-9].*//'`] - if test -z "$required_patch" ; then - [required_patch=0] - fi - # Calculate the available version number components - [available=$swig_version] - [available_major=`echo $available | sed 's/[^0-9].*//'`] - if test -z "$available_major" ; then - [available_major=0] - fi - [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] - [available_minor=`echo $available | sed 's/[^0-9].*//'`] - if test -z "$available_minor" ; then - [available_minor=0] - fi - [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] - [available_patch=`echo $available | sed 's/[^0-9].*//'`] - if test -z "$available_patch" ; then - [available_patch=0] - fi - if test $available_major -ne $required_major \ - -o $available_minor -ne $required_minor \ - -o $available_patch -lt $required_patch ; then - AC_MSG_WARN([SWIG version >= $1 is required. You have $swig_version. You should look at http://www.swig.org]) - SWIG=false - else - AC_MSG_NOTICE([SWIG executable is '$SWIG']) - SWIG_LIB=`$SWIG -swiglib` - AC_MSG_NOTICE([SWIG library directory is '$SWIG_LIB']) - fi - else - AC_MSG_WARN([cannot determine SWIG version]) - SWIG=false - fi - fi - AC_SUBST([SWIG_LIB]) -]) diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4 index 0f660cf..baab5d9 100644 --- a/m4/as-compiler-flag.m4 +++ b/m4/as-compiler-flag.m4 @@ -18,7 +18,7 @@ AC_DEFUN([AS_COMPILER_FLAG], save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $1" - AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) CFLAGS="$save_CFLAGS" if test "X$flag_ok" = Xyes ; then @@ -44,7 +44,7 @@ AC_DEFUN([AS_COMPILER_FLAGS], do save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $each" - AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) CFLAGS="$save_CFLAGS" if test "X$flag_ok" = Xyes ; then diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..9f35d13 --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,522 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is +# needed for multi-threaded programs (defaults to the value of CC +# respectively CXX otherwise). (This is necessary on e.g. AIX to use the +# special cc_r/CC_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# CXX="$PTHREAD_CXX" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> +# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG> +# Copyright (c) 2019 Marc Stevens <marc.stevens@cwi.nl> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <https://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 31 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" + ;; +esac + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +AS_IF([test "x$ax_pthread_clang" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread"]) + + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h> +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + + + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]], + [[int i = PTHREAD_PRIO_INHERIT; + return i;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [ + AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) + AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) + ], + [ + AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) + AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) + ] + ) + ]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" +test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) +AC_SUBST([PTHREAD_CXX]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/m4/ac_python_devel.m4 b/m4/ax_python_devel.m4 index 99ff7d0..44dbd83 100644 --- a/m4/ac_python_devel.m4 +++ b/m4/ax_python_devel.m4 @@ -1,10 +1,10 @@ # =========================================================================== -# http://autoconf-archive.cryp.to/ac_python_devel.html +# https://www.gnu.org/software/autoconf-archive/ax_python_devel.html # =========================================================================== # # SYNOPSIS # -# AC_PYTHON_DEVEL([version]) +# AX_PYTHON_DEVEL([version]) # # DESCRIPTION # @@ -12,8 +12,8 @@ # in your configure.ac. # # This macro checks for Python and tries to get the include path to -# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS) -# output variables. It also exports $(PYTHON_EXTRA_LIBS) and +# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output +# variables. It also exports $(PYTHON_EXTRA_LIBS) and # $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. # # You can search for some particular version of Python by passing a @@ -31,18 +31,15 @@ # If you need to use this macro for an older Python version, please # contact the authors. We're always open for feedback. # -# LAST MODIFICATION +# LICENSE # -# 2008-04-12 -# -# COPYLEFT -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# Copyright (c) 2008 Matteo Settenvini <matteo@member.fsf.org> -# Copyright (c) 2008 Horst Knorr <hk_classes@knoda.org> +# Copyright (c) 2009 Sebastian Huber <sebastian-huber@web.de> +# Copyright (c) 2009 Alan W. Irwin +# Copyright (c) 2009 Rafael Laboissiere <rafael@laboissiere.net> +# Copyright (c) 2009 Andrew Collier +# Copyright (c) 2009 Matteo Settenvini <matteo@member.fsf.org> +# Copyright (c) 2009 Horst Knorr <hk_classes@knoda.org> +# Copyright (c) 2013 Daniel Mullner <muellner@math.stanford.edu> # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the @@ -55,7 +52,7 @@ # Public License for more details. # # You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. +# with this program. If not, see <https://www.gnu.org/licenses/>. # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure @@ -66,11 +63,14 @@ # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Macro Archive. When you make and -# distribute a modified version of the Autoconf Macro, you may extend this -# special exception to the GPL to apply to your modified version as well. +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 21 -AC_DEFUN([AC_PYTHON_DEVEL],[ +AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) +AC_DEFUN([AX_PYTHON_DEVEL],[ # # Allow the use of a (user set) custom python version # @@ -89,9 +89,9 @@ AC_DEFUN([AC_PYTHON_DEVEL],[ # Check for a version of Python >= 2.1.0 # AC_MSG_CHECKING([for a version of Python >= '2.1.0']) - ac_supports_python_ver=`$PYTHON -c "import sys, string; \ - ver = string.split(sys.version)[[0]]; \ - print ver >= '2.1.0'"` + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver >= '2.1.0')"` if test "$ac_supports_python_ver" != "True"; then if test -z "$PYTHON_NOVERSIONCHECK"; then AC_MSG_RESULT([no]) @@ -99,7 +99,7 @@ AC_DEFUN([AC_PYTHON_DEVEL],[ This version of the AC@&t@_PYTHON_DEVEL macro doesn't work properly with versions of Python before 2.1.0. You may need to re-run configure, setting the -variables PYTHON_CPPFLAGS, PYTHON_LDFLAGS, PYTHON_SITE_PKG, +variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. Moreover, to disable this check, set PYTHON_NOVERSIONCHECK to something else than an empty string. @@ -116,11 +116,11 @@ to something else than an empty string. # if test -n "$1"; then AC_MSG_CHECKING([for a version of Python $1]) - ac_supports_python_ver=`$PYTHON -c "import sys, string; \ - ver = string.split(sys.version)[[0]]; \ - print ver $1"` + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver $1)"` if test "$ac_supports_python_ver" = "True"; then - AC_MSG_RESULT([yes]) + AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) AC_MSG_ERROR([this package requires Python $1. @@ -137,7 +137,7 @@ variable to configure. See ``configure --help'' for reference. # AC_MSG_CHECKING([for the distutils Python package]) ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` - if test -z "$ac_distutils_result"; then + if test $? -eq 0; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) @@ -153,9 +153,15 @@ $ac_distutils_result]) AC_MSG_CHECKING([for Python include path]) if test -z "$PYTHON_CPPFLAGS"; then python_path=`$PYTHON -c "import distutils.sysconfig; \ - print distutils.sysconfig.get_python_inc();"` + print (distutils.sysconfig.get_python_inc ());"` + plat_python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc (plat_specific=1));"` if test -n "${python_path}"; then - python_path="-I$python_path" + if test "${plat_python_path}" != "${python_path}"; then + python_path="-I$python_path -I$plat_python_path" + else + python_path="-I$python_path" + fi fi PYTHON_CPPFLAGS=$python_path fi @@ -166,28 +172,80 @@ $ac_distutils_result]) # Check for Python library path # AC_MSG_CHECKING([for Python library path]) - if test -z "$PYTHON_LDFLAGS"; then + if test -z "$PYTHON_LIBS"; then # (makes two attempts to ensure we've got a version number # from the interpreter) - py_version=`$PYTHON -c "from distutils.sysconfig import *; \ - from string import join; \ - print join(get_config_vars('VERSION'))"` - if test "$py_version" = "[None]"; then + ac_python_version=`cat<<EOD | $PYTHON - + +# join all versioning strings, on some systems +# major/minor numbers could be in different list elements +from distutils.sysconfig import * +e = get_config_var('VERSION') +if e is not None: + print(e) +EOD` + + if test -z "$ac_python_version"; then if test -n "$PYTHON_VERSION"; then - py_version=$PYTHON_VERSION + ac_python_version=$PYTHON_VERSION else - py_version=`$PYTHON -c "import sys; \ - print sys.version[[:3]]"` + ac_python_version=`$PYTHON -c "import sys; \ + print (sys.version[[:3]])"` fi fi - PYTHON_LDFLAGS=`$PYTHON -c "from distutils.sysconfig import *; \ - from string import join; \ - print '-L' + get_python_lib(0,1), \ - '-lpython';"`$py_version + # Make the versioning information available to the compiler + AC_DEFINE_UNQUOTED([HAVE_PYTHON], ["$ac_python_version"], + [If available, contains the Python version number currently in use.]) + + # First, the library directory: + ac_python_libdir=`cat<<EOD | $PYTHON - + +# There should be only one +import distutils.sysconfig +e = distutils.sysconfig.get_config_var('LIBDIR') +if e is not None: + print (e) +EOD` + + # Now, for the library: + ac_python_library=`cat<<EOD | $PYTHON - + +import distutils.sysconfig +c = distutils.sysconfig.get_config_vars() +if 'LDVERSION' in c: + print ('python'+c[['LDVERSION']]) +else: + print ('python'+c[['VERSION']]) +EOD` + + # This small piece shamelessly adapted from PostgreSQL python macro; + # credits goes to momjian, I think. I'd like to put the right name + # in the credits, if someone can point me in the right direction... ? + # + if test -n "$ac_python_libdir" -a -n "$ac_python_library" + then + # use the official shared library + ac_python_library=`echo "$ac_python_library" | sed "s/^lib//"` + PYTHON_LIBS="-L$ac_python_libdir -l$ac_python_library" + else + # old way: use libpython from python_configdir + ac_python_libdir=`$PYTHON -c \ + "from distutils.sysconfig import get_python_lib as f; \ + import os; \ + print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"` + PYTHON_LIBS="-L$ac_python_libdir -lpython$ac_python_version" + fi + + if test -z "PYTHON_LIBS"; then + AC_MSG_ERROR([ + Cannot determine location of your Python DSO. Please check it was installed with + dynamic libraries enabled, or try setting PYTHON_LIBS by hand. + ]) + fi fi - AC_MSG_RESULT([$PYTHON_LDFLAGS]) - AC_SUBST([PYTHON_LDFLAGS]) + AC_MSG_RESULT([$PYTHON_LIBS]) + AC_SUBST([PYTHON_LIBS]) # # Check for site packages @@ -195,7 +253,7 @@ $ac_distutils_result]) AC_MSG_CHECKING([for Python site-packages path]) if test -z "$PYTHON_SITE_PKG"; then PYTHON_SITE_PKG=`$PYTHON -c "import distutils.sysconfig; \ - print distutils.sysconfig.get_python_lib(0,0);"` + print (distutils.sysconfig.get_python_lib(0,0));"` fi AC_MSG_RESULT([$PYTHON_SITE_PKG]) AC_SUBST([PYTHON_SITE_PKG]) @@ -207,7 +265,7 @@ $ac_distutils_result]) if test -z "$PYTHON_EXTRA_LIBS"; then PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \ conf = distutils.sysconfig.get_config_var; \ - print conf('LOCALMODLIBS'), conf('LIBS')"` + print (conf('LIBS') + ' ' + conf('SYSLIBS'))"` fi AC_MSG_RESULT([$PYTHON_EXTRA_LIBS]) AC_SUBST(PYTHON_EXTRA_LIBS) @@ -219,7 +277,7 @@ $ac_distutils_result]) if test -z "$PYTHON_EXTRA_LDFLAGS"; then PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "import distutils.sysconfig; \ conf = distutils.sysconfig.get_config_var; \ - print conf('LINKFORSHARED')"` + print (conf('LINKFORSHARED'))"` fi AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS]) AC_SUBST(PYTHON_EXTRA_LDFLAGS) @@ -228,24 +286,32 @@ $ac_distutils_result]) # final check to see if everything compiles alright # AC_MSG_CHECKING([consistency of all components of python development environment]) - AC_LANG_PUSH([C]) # save current global flags - LIBS="$ac_save_LIBS $PYTHON_LDFLAGS" + ac_save_LIBS="$LIBS" + ac_save_LDFLAGS="$LDFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + LIBS="$ac_save_LIBS $PYTHON_LIBS $PYTHON_EXTRA_LIBS $PYTHON_EXTRA_LIBS" + LDFLAGS="$ac_save_LDFLAGS $PYTHON_EXTRA_LDFLAGS" CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS" - AC_TRY_LINK([ - #include <Python.h> - ],[ - Py_Initialize(); - ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_PUSH([C]) + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([[#include <Python.h>]], + [[Py_Initialize();]]) + ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_POP([C]) + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + LDFLAGS="$ac_save_LDFLAGS" AC_MSG_RESULT([$pythonexists]) - if test ! "$pythonexists" = "yes"; then - AC_MSG_ERROR([ + if test ! "x$pythonexists" = "xyes"; then + AC_MSG_FAILURE([ Could not link test program to Python. Maybe the main Python library has been installed in some non-standard library path. If so, pass it to configure, - via the LDFLAGS environment variable. - Example: ./configure LDFLAGS="-L/usr/non-standard-path/python/lib" + via the LIBS environment variable. + Example: ./configure LIBS="-L/usr/non-standard-path/python/lib" ============================================================================ ERROR! You probably have to install the development version of the Python package @@ -254,10 +320,6 @@ $ac_distutils_result]) ]) PYTHON_VERSION="" fi - AC_LANG_POP - # turn back to default flags - CPPFLAGS="$ac_save_CPPFLAGS" - LIBS="$ac_save_LIBS" # # all done! diff --git a/m4/ax_swig_enable_cxx.m4 b/m4/ax_swig_enable_cxx.m4 deleted file mode 100644 index c1eca8c..0000000 --- a/m4/ax_swig_enable_cxx.m4 +++ /dev/null @@ -1,53 +0,0 @@ -# =========================================================================== -# http://www.nongnu.org/autoconf-archive/ax_swig_enable_cxx.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_SWIG_ENABLE_CXX -# -# DESCRIPTION -# -# Enable SWIG C++ support. This affects all invocations of $(SWIG). -# -# LICENSE -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -AU_ALIAS([SWIG_ENABLE_CXX], [AX_SWIG_ENABLE_CXX]) -AC_DEFUN([AX_SWIG_ENABLE_CXX],[ - AC_REQUIRE([AC_PROG_SWIG]) - AC_REQUIRE([AC_PROG_CXX]) - if test "$SWIG" != "false"; then - SWIG="$SWIG -c++" - fi -]) diff --git a/m4/cython_python.m4 b/m4/cython_python.m4 new file mode 100644 index 0000000..5b4a041 --- /dev/null +++ b/m4/cython_python.m4 @@ -0,0 +1,7 @@ +AC_DEFUN([CYTHON_PYTHON],[ + AC_REQUIRE([AC_PROG_CYTHON]) + AC_REQUIRE([AX_PYTHON_DEVEL]) + test "x$1" != "xno" || cython_shadow=" -noproxy" + AC_SUBST([CYTHON_PYTHON_OPT],[-python$cython_shadow]) + AC_SUBST([CYTHON_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS]) +]) diff --git a/m4/swig_python.m4 b/m4/swig_python.m4 deleted file mode 100644 index 2496976..0000000 --- a/m4/swig_python.m4 +++ /dev/null @@ -1,65 +0,0 @@ -# =========================================================================== -# http://autoconf-archive.cryp.to/swig_python.html -# =========================================================================== -# -# SYNOPSIS -# -# SWIG_PYTHON([use-shadow-classes = {no, yes}]) -# -# DESCRIPTION -# -# Checks for Python and provides the $(SWIG_PYTHON_CPPFLAGS), and -# $(SWIG_PYTHON_OPT) output variables. -# -# $(SWIG_PYTHON_OPT) contains all necessary SWIG options to generate code -# for Python. Shadow classes are enabled unless the value of the optional -# first argument is exactly 'no'. If you need multi module support -# (provided by the SWIG_MULTI_MODULE_SUPPORT macro) use -# $(SWIG_PYTHON_LIBS) to link against the appropriate library. It contains -# the SWIG Python runtime library that is needed by the type check system -# for example. -# -# LAST MODIFICATION -# -# 2008-04-12 -# -# COPYLEFT -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Macro Archive. When you make and -# distribute a modified version of the Autoconf Macro, you may extend this -# special exception to the GPL to apply to your modified version as well. - -AC_DEFUN([SWIG_PYTHON],[ - AC_REQUIRE([AC_PROG_SWIG]) - AC_REQUIRE([AC_PYTHON_DEVEL]) - test "x$1" != "xno" || swig_shadow=" -noproxy" - AC_SUBST([SWIG_PYTHON_OPT],[-python$swig_shadow]) - AC_SUBST([SWIG_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS]) -]) diff --git a/src/Makefile.am b/src/Makefile.am index 70dc895..58cf07c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,24 +1,69 @@ -AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/3rd_party/libsrp6a-sha512 \ + -I$(top_srcdir)/3rd_party/ed25519 \ + -I$(top_srcdir) -AM_CFLAGS = $(GLOBAL_CFLAGS) $(libusbmuxd_CFLAGS) $(libglib2_CFLAGS) $(libgnutls_CFLAGS) $(libtasn1_CFLAGS) $(libgthread2_CFLAGS) $(libplist_CFLAGS) $(LFS_CFLAGS) -AM_LDFLAGS = $(libglib2_LIBS) $(libgnutls_LIBS) $(libtasn1_LIBS) $(libgthread2_LIBS) $(libplist_LIBS) $(libusbmuxd_LIBS) $(libgcrypt_LIBS) +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + $(ssl_lib_CFLAGS) \ + $(LFS_CFLAGS) \ + $(PTHREAD_CFLAGS) \ + $(libusbmuxd_CFLAGS) \ + $(libplist_CFLAGS) \ + $(limd_glue_CFLAGS) -lib_LTLIBRARIES = libimobiledevice.la -libimobiledevice_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBIMOBILEDEVICE_SO_VERSION) -no-undefined -libimobiledevice_la_SOURCES = idevice.c idevice.h \ - debug.c debug.h\ - userpref.c userpref.h\ - property_list_service.c property_list_service.h\ - device_link_service.c device_link_service.h\ - lockdown.c lockdown.h\ - afc.c afc.h\ - file_relay.c file_relay.h\ - notification_proxy.c notification_proxy.h\ - installation_proxy.c installation_proxy.h\ - sbservices.c sbservices.h\ - mobile_image_mounter.c mobile_image_mounter.h\ - screenshotr.c screenshotr.h\ - mobilesync.c mobilesync.h\ - mobilebackup.c mobilebackup.h\ - house_arrest.c house_arrest.h\ - restore.c restore.h +AM_LDFLAGS = \ + $(ssl_lib_LIBS) \ + $(PTHREAD_LIBS) \ + $(libusbmuxd_LIBS) \ + $(libplist_LIBS) \ + $(limd_glue_LIBS) + +lib_LTLIBRARIES = libimobiledevice-1.0.la +libimobiledevice_1_0_la_LIBADD = $(top_builddir)/common/libinternalcommon.la +if HAVE_WIRELESS_PAIRING +libimobiledevice_1_0_la_LIBADD += $(top_builddir)/3rd_party/ed25519/libed25519.la $(top_builddir)/3rd_party/libsrp6a-sha512/libsrp6a-sha512.la +endif +libimobiledevice_1_0_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBIMOBILEDEVICE_SO_VERSION) -no-undefined +if DARWIN +libimobiledevice_1_0_la_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration +endif +libimobiledevice_1_0_la_SOURCES = \ + idevice.c idevice.h \ + service.c service.h \ + property_list_service.c property_list_service.h \ + device_link_service.c device_link_service.h \ + lockdown.c lockdown.h \ + lockdown-cu.c \ + afc.c afc.h \ + file_relay.c file_relay.h \ + notification_proxy.c notification_proxy.h \ + installation_proxy.c installation_proxy.h \ + sbservices.c sbservices.h \ + mobile_image_mounter.c mobile_image_mounter.h \ + screenshotr.c screenshotr.h \ + mobilesync.c mobilesync.h \ + mobilebackup.c mobilebackup.h \ + house_arrest.c house_arrest.h \ + mobilebackup2.c mobilebackup2.h \ + misagent.c misagent.h \ + restore.c restore.h \ + diagnostics_relay.c diagnostics_relay.h \ + heartbeat.c heartbeat.h \ + debugserver.c debugserver.h \ + webinspector.c webinspector.h \ + mobileactivation.c mobileactivation.h \ + preboard.c preboard.h \ + companion_proxy.c companion_proxy.h \ + reverse_proxy.c reverse_proxy.h \ + syslog_relay.c syslog_relay.h \ + bt_packet_logger.c bt_packet_logger.h + +if WIN32 +libimobiledevice_1_0_la_LDFLAGS += -avoid-version -static-libgcc +libimobiledevice_1_0_la_LIBADD += -lole32 -lws2_32 -lgdi32 +endif + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libimobiledevice-1.0.pc @@ -1,308 +1,243 @@ /* - * afc.c + * afc.c * Contains functions for the built-in AFC client. - * + * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2009-2014 Nikias Bassen. All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> -#include "afc.h" #include "idevice.h" -#include "debug.h" - -/** The maximum size an AFC data packet can be */ -static const int MAXIMUM_PACKET_SIZE = (2 << 15); +#include "afc.h" +#include "common/debug.h" +#include "endianness.h" /** * Locks an AFC client, done for thread safety stuff - * + * * @param client The AFC client connection to lock */ static void afc_lock(afc_client_t client) { debug_info("Locked"); - g_mutex_lock(client->mutex); + mutex_lock(&client->mutex); } /** * Unlocks an AFC client, done for thread safety stuff. - * - * @param client The AFC + * + * @param client The AFC */ static void afc_unlock(afc_client_t client) { debug_info("Unlocked"); - g_mutex_unlock(client->mutex); + mutex_unlock(&client->mutex); } /** * Makes a connection to the AFC service on the device using the given * connection. * - * @param connection An idevice_connection_t that must have been previously - * connected using idevice_connect(). Note that this connection will - * not be closed by calling afc_client_free(). + * @param service_client A connected service client * @param client Pointer that will be set to a newly allocated afc_client_t * upon successful return. - * + * * @return AFC_E_SUCCESS on success, AFC_E_INVALID_ARG if connection is * invalid, or AFC_E_NO_MEM if there is a memory allocation problem. */ -afc_error_t afc_client_new_from_connection(idevice_connection_t connection, afc_client_t *client) +afc_error_t afc_client_new_with_service_client(service_client_t service_client, afc_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!connection) + if (!service_client) return AFC_E_INVALID_ARG; afc_client_t client_loc = (afc_client_t) malloc(sizeof(struct afc_client_private)); - client_loc->connection = connection; - client_loc->own_connection = 0; + client_loc->parent = service_client; + client_loc->free_parent = 0; /* allocate a packet */ - client_loc->afc_packet = (AFCPacket *) malloc(sizeof(AFCPacket)); + client_loc->packet_extra = 1024; + client_loc->afc_packet = (AFCPacket *) malloc(sizeof(AFCPacket) + client_loc->packet_extra); if (!client_loc->afc_packet) { free(client_loc); return AFC_E_NO_MEM; } - client_loc->afc_packet->packet_num = 0; client_loc->afc_packet->entire_length = 0; client_loc->afc_packet->this_length = 0; memcpy(client_loc->afc_packet->magic, AFC_MAGIC, AFC_MAGIC_LEN); - client_loc->file_handle = 0; - client_loc->lock = 0; - client_loc->mutex = g_mutex_new(); + mutex_init(&client_loc->mutex); *client = client_loc; return AFC_E_SUCCESS; } -/** - * Makes a connection to the AFC service on the device. - * This function calls afc_client_new_from_connection() after creating - * a connection to the specified device and port. - * - * @see afc_client_new_from_connection - * - * @param device The device to connect to. - * @param port The destination port. - * @param client Pointer that will be set to a newly allocated afc_client_t - * upon successful return. - * - * @return AFC_E_SUCCESS on success, AFC_E_INVALID_ARG if device or port is - * invalid, AFC_E_MUX_ERROR if the connection cannot be established, - * or AFC_E_NO_MEM if there is a memory allocation problem. - */ -afc_error_t afc_client_new(idevice_t device, uint16_t port, afc_client_t * client) +afc_error_t afc_client_new(idevice_t device, lockdownd_service_descriptor_t service, afc_client_t * client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device || port==0) + if (!device || !service || service->port == 0) return AFC_E_INVALID_ARG; - /* attempt connection */ - idevice_connection_t connection = NULL; - if (idevice_connect(device, port, &connection) != IDEVICE_E_SUCCESS) { + service_client_t parent = NULL; + if (service_client_new(device, service, &parent) != SERVICE_E_SUCCESS) { return AFC_E_MUX_ERROR; } - afc_error_t err = afc_client_new_from_connection(connection, client); + afc_error_t err = afc_client_new_with_service_client(parent, client); if (err != AFC_E_SUCCESS) { - idevice_disconnect(connection); + service_client_free(parent); } else { - (*client)->own_connection = 1; + (*client)->free_parent = 1; } return err; } -/** - * Frees up an AFC client. If the connection was created by the - * client itself, the connection will be closed. - * - * @param client The client to free. - */ +afc_error_t afc_client_start_service(idevice_t device, afc_client_t * client, const char* label) +{ + afc_error_t err = AFC_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, AFC_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(afc_client_new), &err); + return err; +} + afc_error_t afc_client_free(afc_client_t client) { if (!client || !client->afc_packet) return AFC_E_INVALID_ARG; - if (client->own_connection && client->connection) { - idevice_disconnect(client->connection); - client->connection = NULL; + if (client->free_parent && client->parent) { + service_client_free(client->parent); + client->parent = NULL; } free(client->afc_packet); - if (client->mutex) { - g_mutex_free(client->mutex); - } + mutex_destroy(&client->mutex); free(client); return AFC_E_SUCCESS; } /** * Dispatches an AFC packet over a client. - * + * * @param client The client to send data through. - * @param data The data to send. - * @param length The length to send. - * @param bytes_sent The number of bytes actually sent. + * @param operation The operation to perform. + * @param data The data to send together with the header. + * @param data_length The length of the data to send with the header. + * @param payload The data to send after the header has been sent. + * @param payload_length The length of data to send after the header. + * @param bytes_sent The total number of bytes actually sent. * * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - * - * @warning set client->afc_packet->this_length and - * client->afc_packet->entire_length to 0 before calling this. The - * reason is that if you set them to different values, it indicates - * you want to send the data as two packets. */ -static afc_error_t afc_dispatch_packet(afc_client_t client, const char *data, uint32_t length, uint32_t *bytes_sent) +static afc_error_t afc_dispatch_packet(afc_client_t client, uint64_t operation, uint32_t data_length, const char* payload, uint32_t payload_length, uint32_t *bytes_sent) { - uint32_t offset = 0; uint32_t sent = 0; - if (!client || !client->connection || !client->afc_packet) + if (!client || !client->parent || !client->afc_packet) return AFC_E_INVALID_ARG; *bytes_sent = 0; - if (!data || !length) - length = 0; + if (!payload || !payload_length) + payload_length = 0; client->afc_packet->packet_num++; - if (!client->afc_packet->entire_length) { - client->afc_packet->entire_length = (length) ? sizeof(AFCPacket) + length : sizeof(AFCPacket); - client->afc_packet->this_length = client->afc_packet->entire_length; - } - if (!client->afc_packet->this_length) { - client->afc_packet->this_length = sizeof(AFCPacket); - } - /* We want to send two segments; buffer+sizeof(AFCPacket) to this_length - is the parameters and everything beyond that is the next packet. - (for writing) */ - if (client->afc_packet->this_length != client->afc_packet->entire_length) { - offset = client->afc_packet->this_length - sizeof(AFCPacket); - - debug_info("Offset: %i", offset); - if ((length) < (client->afc_packet->entire_length - client->afc_packet->this_length)) { - debug_info("Length did not resemble what it was supposed to based on packet"); - debug_info("length minus offset: %i", length - offset); - debug_info("rest of packet: %i\n", client->afc_packet->entire_length - client->afc_packet->this_length); - return AFC_E_INTERNAL_ERROR; - } - - /* send AFC packet header */ - AFCPacket_to_LE(client->afc_packet); - sent = 0; - idevice_connection_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); - AFCPacket_from_LE(client->afc_packet); - if (sent == 0) { - /* FIXME: should this be handled as success?! */ - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - - /* send AFC packet data */ - sent = 0; - idevice_connection_send(client->connection, data, offset, &sent); - if (sent == 0) { - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - - debug_info("sent the first now go with the second"); - debug_info("Length: %i", length - offset); - debug_info("Buffer: "); - debug_buffer(data + offset, length - offset); - - sent = 0; - idevice_connection_send(client->connection, data + offset, length - offset, &sent); - - *bytes_sent = sent; + client->afc_packet->operation = operation; + client->afc_packet->entire_length = sizeof(AFCPacket) + data_length + payload_length; + client->afc_packet->this_length = sizeof(AFCPacket) + data_length; + + debug_info("packet length = %i", client->afc_packet->this_length); + + /* send AFC packet header and data */ + AFCPacket_to_LE(client->afc_packet); + debug_buffer((char*)client->afc_packet, sizeof(AFCPacket) + data_length); + sent = 0; + service_send(client->parent, (void*)client->afc_packet, sizeof(AFCPacket) + data_length, &sent); + AFCPacket_from_LE(client->afc_packet); + *bytes_sent += sent; + if (sent < sizeof(AFCPacket) + data_length) { return AFC_E_SUCCESS; - } else { - debug_info("doin things the old way"); - debug_info("packet length = %i", client->afc_packet->this_length); - - debug_buffer((char*)client->afc_packet, sizeof(AFCPacket)); + } - /* send AFC packet header */ - AFCPacket_to_LE(client->afc_packet); - sent = 0; - idevice_connection_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); - AFCPacket_from_LE(client->afc_packet); - if (sent == 0) { - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - /* send AFC packet data (if there's data to send) */ - if (length > 0) { - debug_info("packet data follows"); - - debug_buffer(data, length); - idevice_connection_send(client->connection, data, length, &sent); - *bytes_sent += sent; + sent = 0; + if (payload_length > 0) { + if (payload_length > 256) { + debug_info("packet payload follows (256/%u)", payload_length); + debug_buffer(payload, 256); + } else { + debug_info("packet payload follows"); + debug_buffer(payload, payload_length); } + service_send(client->parent, payload, payload_length, &sent); + } + *bytes_sent += sent; + if (sent < payload_length) { return AFC_E_SUCCESS; } - return AFC_E_INTERNAL_ERROR; + + return AFC_E_SUCCESS; } /** * Receives data through an AFC client and sets a variable to the received data. - * + * * @param client The client to receive data on. - * @param dump_here The char* to point to the newly-received data. + * @param bytes The char* to point to the newly-received data. * @param bytes_recv How much data was received. - * + * * @return AFC_E_SUCCESS on success or an AFC_E_* error value. */ -static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint32_t *bytes_recv) +static afc_error_t afc_receive_data(afc_client_t client, char **bytes, uint32_t *bytes_recv) { AFCPacket header; uint32_t entire_len = 0; uint32_t this_len = 0; uint32_t current_count = 0; uint64_t param1 = -1; + char *buf = NULL; + uint32_t recv_len = 0; - *bytes_recv = 0; + if (bytes_recv) { + *bytes_recv = 0; + } + if (bytes) { + *bytes = NULL; + } /* first, read the AFC header */ - idevice_connection_receive(client->connection, (char*)&header, sizeof(AFCPacket), bytes_recv); + service_receive(client->parent, (char*)&header, sizeof(AFCPacket), &recv_len); AFCPacket_from_LE(&header); - if (*bytes_recv == 0) { + if (recv_len == 0) { debug_info("Just didn't get enough."); - *dump_here = NULL; return AFC_E_MUX_ERROR; - } else if (*bytes_recv < sizeof(AFCPacket)) { + } + + if (recv_len < sizeof(AFCPacket)) { debug_info("Did not even get the AFCPacket header"); - *dump_here = NULL; return AFC_E_MUX_ERROR; } /* check if it's a valid AFC header */ - if (strncmp(header.magic, AFC_MAGIC, AFC_MAGIC_LEN)) { + if (strncmp(header.magic, AFC_MAGIC, AFC_MAGIC_LEN) != 0) { debug_info("Invalid AFC packet received (magic != " AFC_MAGIC ")!"); } @@ -310,25 +245,21 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 if (header.packet_num != client->afc_packet->packet_num) { /* otherwise print a warning but do not abort */ debug_info("ERROR: Unexpected packet number (%lld != %lld) aborting.", header.packet_num, client->afc_packet->packet_num); - *dump_here = NULL; return AFC_E_OP_HEADER_INVALID; } /* then, read the attached packet */ if (header.this_length < sizeof(AFCPacket)) { debug_info("Invalid AFCPacket header received!"); - *dump_here = NULL; return AFC_E_OP_HEADER_INVALID; - } else if ((header.this_length == header.entire_length) - && header.entire_length == sizeof(AFCPacket)) { + } + if ((header.this_length == header.entire_length) + && header.entire_length == sizeof(AFCPacket)) { debug_info("Empty AFCPacket received!"); - *dump_here = NULL; - *bytes_recv = 0; if (header.operation == AFC_OP_DATA) { return AFC_E_SUCCESS; - } else { - return AFC_E_IO_ERROR; } + return AFC_E_IO_ERROR; } debug_info("received AFC packet, full len=%lld, this len=%lld, operation=0x%llx", header.entire_length, header.this_length, header.operation); @@ -336,22 +267,17 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 entire_len = (uint32_t)header.entire_length - sizeof(AFCPacket); this_len = (uint32_t)header.this_length - sizeof(AFCPacket); - /* this is here as a check (perhaps a different upper limit is good?) */ - if (entire_len > (uint32_t)MAXIMUM_PACKET_SIZE) { - fprintf(stderr, "%s: entire_len is larger than MAXIMUM_PACKET_SIZE, (%d > %d)!", __func__, entire_len, MAXIMUM_PACKET_SIZE); - } - - *dump_here = (char*)malloc(entire_len); + buf = (char*)malloc(entire_len); if (this_len > 0) { - idevice_connection_receive(client->connection, *dump_here, this_len, bytes_recv); - if (*bytes_recv <= 0) { - free(*dump_here); - *dump_here = NULL; + recv_len = 0; + service_receive(client->parent, buf, this_len, &recv_len); + if (recv_len <= 0) { + free(buf); debug_info("Did not get packet contents!"); return AFC_E_NOT_ENOUGH_DATA; - } else if (*bytes_recv < this_len) { - free(*dump_here); - *dump_here = NULL; + } + if (recv_len < this_len) { + free(buf); debug_info("Could not receive this_len=%d bytes", this_len); return AFC_E_NOT_ENOUGH_DATA; } @@ -361,12 +287,13 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 if (entire_len > this_len) { while (current_count < entire_len) { - idevice_connection_receive(client->connection, (*dump_here)+current_count, entire_len - current_count, bytes_recv); - if (*bytes_recv <= 0) { - debug_info("Error receiving data (recv returned %d)", *bytes_recv); + recv_len = 0; + service_receive(client->parent, buf+current_count, entire_len - current_count, &recv_len); + if (recv_len <= 0) { + debug_info("Error receiving data (recv returned %d)", recv_len); break; } - current_count += *bytes_recv; + current_count += recv_len; } if (current_count < entire_len) { debug_info("WARNING: could not receive full packet (read %s, size %d)", current_count, entire_len); @@ -374,12 +301,17 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 } if (current_count >= sizeof(uint64_t)) { - param1 = GUINT64_FROM_LE(*(uint64_t*)(*dump_here)); + param1 = le64toh(*(uint64_t*)(buf)); } debug_info("packet data size = %i", current_count); - debug_info("packet data follows"); - debug_buffer(*dump_here, current_count); + if (current_count > 256) { + debug_info("packet data follows (256/%u)", current_count); + debug_buffer(buf, 256); + } else { + debug_info("packet data follows"); + debug_buffer(buf, current_count); + } /* check operation types */ if (header.operation == AFC_OP_STATUS) { @@ -389,8 +321,7 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 if (param1 != AFC_E_SUCCESS) { /* error status */ /* free buffer */ - free(*dump_here); - *dump_here = NULL; + free(buf); return (afc_error_t)param1; } } else if (header.operation == AFC_OP_DATA) { @@ -404,16 +335,22 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 debug_info("got a tell response, position=%lld", param1); } else { /* unknown operation code received */ - free(*dump_here); - *dump_here = NULL; - *bytes_recv = 0; + free(buf); debug_info("WARNING: Unknown operation code received 0x%llx param1=%lld", header.operation, param1); +#ifndef WIN32 fprintf(stderr, "%s: WARNING: Unknown operation code received 0x%llx param1=%lld", __func__, (long long)header.operation, (long long)param1); +#endif return AFC_E_OP_NOT_SUPPORTED; } + if (bytes) { + *bytes = buf; + } else { + free(buf); + } + *bytes_recv = current_count; return AFC_E_SUCCESS; } @@ -421,7 +358,7 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 /** * Returns counts of null characters within a string. */ -static uint32_t count_nullspaces(char *string, uint32_t number) +static uint32_t count_nullspaces(const char *string, uint32_t number) { uint32_t i = 0, nulls = 0; @@ -462,32 +399,42 @@ static char **make_strings_list(char *tokens, uint32_t length) return list; } -/** - * Gets a directory listing of the directory requested. - * - * @param client The client to get a directory listing from. - * @param dir The directory to list. (must be a fully-qualified path) - * @param list A char list of files in that directory, terminated by an empty - * string or NULL if there was an error. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***list) +static int _afc_check_packet_buffer(afc_client_t client, uint32_t data_len) +{ + if (data_len > client->packet_extra) { + client->packet_extra = (data_len & ~8) + 8; + AFCPacket* newpkt = (AFCPacket*)realloc(client->afc_packet, sizeof(AFCPacket) + client->packet_extra); + if (!newpkt) { + return -1; + } + client->afc_packet = newpkt; + } + return 0; +} + +#define AFC_PACKET_DATA_PTR ((char*)client->afc_packet + sizeof(AFCPacket)) + +afc_error_t afc_read_directory(afc_client_t client, const char *path, char ***directory_information) { uint32_t bytes = 0; char *data = NULL, **list_loc = NULL; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !dir || !list || (list && *list)) + if (!client || !path || !directory_information || (directory_information && *directory_information)) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send the command */ - client->afc_packet->operation = AFC_OP_READ_DIR; - client->afc_packet->entire_length = 0; - client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_READ_DIR, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -495,6 +442,8 @@ afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***lis /* Receive the data */ ret = afc_receive_data(client, &data, &bytes); if (ret != AFC_E_SUCCESS) { + if (data) + free(data); afc_unlock(client); return ret; } @@ -504,37 +453,24 @@ afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***lis free(data); afc_unlock(client); - *list = list_loc; + *directory_information = list_loc; return ret; } -/** - * Get device info for a client connection to phone. The device information - * returned is the device model as well as the free space, the total capacity - * and blocksize on the accessed disk partition. - * - * @param client The client to get device info for. - * @param infos A char ** list of parameters as given by AFC or NULL if there - * was an error. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_get_device_info(afc_client_t client, char ***infos) +afc_error_t afc_get_device_info(afc_client_t client, char ***device_information) { uint32_t bytes = 0; char *data = NULL, **list = NULL; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !infos) + if (!client || !device_information) return AFC_E_INVALID_ARG; afc_lock(client); /* Send the command */ - client->afc_packet->operation = AFC_OP_GET_DEVINFO; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, NULL, 0, &bytes); + ret = afc_dispatch_packet(client, AFC_OP_GET_DEVINFO, 0, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -542,6 +478,8 @@ afc_error_t afc_get_device_info(afc_client_t client, char ***infos) /* Receive the data */ ret = afc_receive_data(client, &data, &bytes); if (ret != AFC_E_SUCCESS) { + if (data) + free(data); afc_unlock(client); return ret; } @@ -552,22 +490,11 @@ afc_error_t afc_get_device_info(afc_client_t client, char ***infos) afc_unlock(client); - *infos = list; + *device_information = list; return ret; } -/** - * Get a specific key of the device info list for a client connection. - * Known key values are: Model, FSTotalBytes, FSFreeBytes and FSBlockSize. - * This is a helper function for afc_get_device_info(). - * - * @param client The client to get device info for. - * @param key The key to get the value of. - * @param value The value for the key if successful or NULL otherwise. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char **value) { afc_error_t ret = AFC_E_INTERNAL_ERROR; @@ -587,43 +514,40 @@ afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char * break; } } - - g_strfreev(kvps); + for (ptr = kvps; *ptr; ptr++) { + free(*ptr); + } + free(kvps); return ret; } -/** - * Deletes a file or directory. - * - * @param client The client to use. - * @param path The path to delete. (must be a fully-qualified path) - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_remove_path(afc_client_t client, const char *path) { - char *response = NULL; uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !client->afc_packet || !client->connection) + if (!client || !path || !client->afc_packet || !client->parent) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - client->afc_packet->operation = AFC_OP_REMOVE_PATH; - ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_REMOVE_PATH, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); /* special case; unknown error actually means directory not empty */ if (ret == AFC_E_UNKNOWN_ERROR) @@ -634,61 +558,45 @@ afc_error_t afc_remove_path(afc_client_t client, const char *path) return ret; } -/** - * Renames a file or directory on the phone. - * - * @param client The client to have rename. - * @param from The name to rename from. (must be a fully-qualified path) - * @param to The new name. (must also be a fully-qualified path) - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_rename_path(afc_client_t client, const char *from, const char *to) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(from) + strlen(to) + 1 + sizeof(uint32_t))); + if (!client || !from || !to || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !from || !to || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARG; + size_t from_len = strlen(from); + size_t to_len = strlen(to); afc_lock(client); + uint32_t data_len = (uint32_t)(from_len+1 + to_len+1); + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - memcpy(send, from, strlen(from) + 1); - memcpy(send + strlen(from) + 1, to, strlen(to) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_RENAME_PATH; - ret = afc_dispatch_packet(client, send, strlen(to)+1 + strlen(from)+1, &bytes); - free(send); + memcpy(AFC_PACKET_DATA_PTR, from, from_len+1); + memcpy(AFC_PACKET_DATA_PTR + from_len+1, to, to_len+1); + ret = afc_dispatch_packet(client, AFC_OP_RENAME_PATH, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Creates a directory on the phone. - * - * @param client The client to use to make a directory. - * @param dir The directory's path. (must be a fully-qualified path, I assume - * all other mkdir restrictions apply as well) - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_make_directory(afc_client_t client, const char *dir) +afc_error_t afc_make_directory(afc_client_t client, const char *path) { uint32_t bytes = 0; - char *response = NULL; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client) @@ -696,50 +604,51 @@ afc_error_t afc_make_directory(afc_client_t client, const char *dir) afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - client->afc_packet->operation = AFC_OP_MAKE_DIR; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_MAKE_DIR, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Gets information about a specific file. - * - * @param client The client to use to get the information of the file. - * @param path The fully-qualified path to the file. - * @param infolist Pointer to a buffer that will be filled with a NULL-terminated - * list of strings with the file information. - * Set to NULL before calling this function. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***infolist) +afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***file_information) { char *received = NULL; uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !infolist) + if (!client || !path || !file_information) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + + debug_info("We got %p and %p", client->afc_packet, AFC_PACKET_DATA_PTR); + /* Send command */ - client->afc_packet->operation = AFC_OP_GET_FILE_INFO; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_GET_FILE_INFO, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -748,7 +657,7 @@ afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***inf /* Receive data */ ret = afc_receive_data(client, &received, &bytes); if (received) { - *infolist = make_strings_list(received, bytes); + *file_information = make_strings_list(received, bytes); free(received); } @@ -757,51 +666,39 @@ afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***inf return ret; } -/** - * Opens a file on the phone. - * - * @param client The client to use to open the file. - * @param filename The file to open. (must be a fully-qualified path) - * @param file_mode The mode to use to open the file. Can be AFC_FILE_READ or - * AFC_FILE_WRITE; the former lets you read and write, - * however, and the second one will *create* the file, - * destroying anything previously there. - * @param handle Pointer to a uint64_t that will hold the handle of the file - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -idevice_error_t -afc_file_open(afc_client_t client, const char *filename, - afc_file_mode_t file_mode, uint64_t *handle) +afc_error_t afc_file_open(afc_client_t client, const char *filename, afc_file_mode_t file_mode, uint64_t *handle) { - uint64_t file_mode_loc = GUINT64_TO_LE(file_mode); + if (!client || !client->parent || !client->afc_packet) + return AFC_E_INVALID_ARG; + + //uint64_t file_mode_loc = htole64(file_mode); uint32_t bytes = 0; - char *data = (char *) malloc(sizeof(char) * (8 + strlen(filename) + 1)); afc_error_t ret = AFC_E_UNKNOWN_ERROR; /* set handle to 0 so in case an error occurs, the handle is invalid */ *handle = 0; - if (!client || !client->connection || !client->afc_packet) - return AFC_E_INVALID_ARG; - afc_lock(client); - /* Send command */ - memcpy(data, &file_mode_loc, 8); - memcpy(data + 8, filename, strlen(filename)); - data[8 + strlen(filename)] = '\0'; - client->afc_packet->operation = AFC_OP_FILE_OPEN; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, data, 8 + strlen(filename) + 1, &bytes); - free(data); + uint32_t data_len = (uint32_t)(strlen(filename)+1 + 8); + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ + //memcpy(AFC_PACKET_DATA_PTR, &file_mode_loc, 8); + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(file_mode); + memcpy(AFC_PACKET_DATA_PTR + 8, filename, data_len-8); + ret = afc_dispatch_packet(client, AFC_OP_FILE_OPEN, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { debug_info("Didn't receive a response to the command"); afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive the data */ + char* data = NULL; ret = afc_receive_data(client, &data, &bytes); if ((ret == AFC_E_SUCCESS) && (bytes > 0) && data) { afc_unlock(client); @@ -811,6 +708,8 @@ afc_file_open(afc_client_t client, const char *filename, free(data); return ret; } + /* in case memory was allocated but no data received or an error occurred */ + free(data); debug_info("Didn't get any further data"); @@ -819,156 +718,81 @@ afc_file_open(afc_client_t client, const char *filename, return ret; } -/** - * Attempts to the read the given number of bytes from the given file. - * - * @param client The relevant AFC client - * @param handle File handle of a previously opened file - * @param data The pointer to the memory region to store the read data - * @param length The number of bytes to read - * @param bytes_read The number of bytes actually read. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -idevice_error_t -afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read) +afc_error_t afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read) { char *input = NULL; uint32_t current_count = 0, bytes_loc = 0; - const uint32_t MAXIMUM_READ_SIZE = 1 << 16; + struct readinfo { + uint64_t handle; + uint64_t size; + }; afc_error_t ret = AFC_E_SUCCESS; - if (!client || !client->afc_packet || !client->connection || handle == 0) + if (!client || !client->afc_packet || !client->parent || handle == 0) return AFC_E_INVALID_ARG; debug_info("called for length %i", length); + //uint32_t data_len = 8 + 8; + afc_lock(client); - /* Looping here to get around the maximum amount of data that - afc_receive_data can handle */ - while (current_count < length) { - debug_info("current count is %i but length is %i", current_count, length); - - /* Send the read command */ - AFCFilePacket *packet = (AFCFilePacket *) malloc(sizeof(AFCFilePacket)); - packet->filehandle = handle; - packet->size = GUINT64_TO_LE(((length - current_count) < MAXIMUM_READ_SIZE) ? (length - current_count) : MAXIMUM_READ_SIZE); - client->afc_packet->operation = AFC_OP_READ; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, (char *) packet, sizeof(AFCFilePacket), &bytes_loc); - free(packet); - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - /* Receive the data */ - ret = afc_receive_data(client, &input, &bytes_loc); - debug_info("afc_receive_data returned error: %d", ret); - debug_info("bytes returned: %i", bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } else if (bytes_loc == 0) { - if (input) - free(input); - afc_unlock(client); - *bytes_read = current_count; - /* FIXME: check that's actually a success */ - return ret; - } else { - if (input) { - debug_info("%d", bytes_loc); - memcpy(data + current_count, input, (bytes_loc > length) ? length : bytes_loc); - free(input); - input = NULL; - current_count += (bytes_loc > length) ? length : bytes_loc; - } - } + /* Send the read command */ + struct readinfo* readinfo = (struct readinfo*)(AFC_PACKET_DATA_PTR); + readinfo->handle = handle; + readinfo->size = htole64(length); + ret = afc_dispatch_packet(client, AFC_OP_FILE_READ, sizeof(struct readinfo), NULL, 0, &bytes_loc); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + /* Receive the data */ + ret = afc_receive_data(client, &input, &bytes_loc); + debug_info("afc_receive_data returned error: %d", ret); + debug_info("bytes returned: %i", bytes_loc); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return ret; + } + if (bytes_loc == 0) { + if (input) + free(input); + afc_unlock(client); + *bytes_read = current_count; + /* FIXME: check that's actually a success */ + return ret; + } + if (input) { + debug_info("%d", bytes_loc); + memcpy(data + current_count, input, (bytes_loc > length) ? length : bytes_loc); + free(input); + input = NULL; + current_count += (bytes_loc > length) ? length : bytes_loc; } - debug_info("returning current_count as %i", current_count); afc_unlock(client); *bytes_read = current_count; return ret; } -/** - * Writes a given number of bytes to a file. - * - * @param client The client to use to write to the file. - * @param handle File handle of previously opened file. - * @param data The data to write to the file. - * @param length How much data to write. - * @param bytes_written The number of bytes actually written to the file. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -idevice_error_t -afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written) +afc_error_t afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written) { - char *acknowledgement = NULL; - const uint32_t MAXIMUM_WRITE_SIZE = 1 << 15; - uint32_t current_count = 0, i = 0; - uint32_t segments = (length / MAXIMUM_WRITE_SIZE); + uint32_t current_count = 0; uint32_t bytes_loc = 0; - char *out_buffer = NULL; afc_error_t ret = AFC_E_SUCCESS; - if (!client || !client->afc_packet || !client->connection || !bytes_written || (handle == 0)) + if (!client || !client->afc_packet || !client->parent || !bytes_written || (handle == 0)) return AFC_E_INVALID_ARG; + uint32_t data_len = 8; + afc_lock(client); debug_info("Write length: %i", length); - /* Divide the file into segments. */ - for (i = 0; i < segments; i++) { - /* Send the segment */ - client->afc_packet->this_length = sizeof(AFCPacket) + 8; - client->afc_packet->entire_length = client->afc_packet->this_length + MAXIMUM_WRITE_SIZE; - client->afc_packet->operation = AFC_OP_WRITE; - out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); - memcpy(out_buffer, (char *)&handle, sizeof(uint64_t)); - memcpy(out_buffer + 8, data + current_count, MAXIMUM_WRITE_SIZE); - ret = afc_dispatch_packet(client, out_buffer, MAXIMUM_WRITE_SIZE + 8, &bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - free(out_buffer); - out_buffer = NULL; - - current_count += bytes_loc; - ret = afc_receive_data(client, &acknowledgement, &bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } else { - free(acknowledgement); - } - } - - /* By this point, we should be at the end. i.e. the last segment that didn't - get sent in the for loop. This length is fine because it's always - sizeof(AFCPacket) + 8, but to be sure we do it again */ - if (current_count == length) { - afc_unlock(client); - *bytes_written = current_count; - return ret; - } - - client->afc_packet->this_length = sizeof(AFCPacket) + 8; - client->afc_packet->entire_length = client->afc_packet->this_length + (length - current_count); - client->afc_packet->operation = AFC_OP_WRITE; - out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); - memcpy(out_buffer, (char *) &handle, sizeof(uint64_t)); - memcpy(out_buffer + 8, data + current_count, (length - current_count)); - ret = afc_dispatch_packet(client, out_buffer, (length - current_count) + 8, &bytes_loc); - free(out_buffer); - out_buffer = NULL; + *(uint64_t*)(AFC_PACKET_DATA_PTR) = handle; + ret = afc_dispatch_packet(client, AFC_OP_FILE_WRITE, data_len, data, length, &bytes_loc); - current_count += bytes_loc; + current_count += bytes_loc - (sizeof(AFCPacket) + 8); if (ret != AFC_E_SUCCESS) { afc_unlock(client); @@ -976,43 +800,32 @@ afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t return AFC_E_SUCCESS; } - ret = afc_receive_data(client, &acknowledgement, &bytes_loc); + ret = afc_receive_data(client, NULL, &bytes_loc); afc_unlock(client); if (ret != AFC_E_SUCCESS) { - debug_info("uh oh?"); - } else { - free(acknowledgement); + debug_info("Failed to receive reply (%d)", ret); } *bytes_written = current_count; return ret; } -/** - * Closes a file on the phone. - * - * @param client The client to close the file with. - * @param handle File handle of a previously opened file. - */ afc_error_t afc_file_close(afc_client_t client, uint64_t handle) { - char *buffer = malloc(sizeof(char) * 8); uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) return AFC_E_INVALID_ARG; + uint32_t data_len = 8; + afc_lock(client); debug_info("File handle %i", handle); /* Send command */ - memcpy(buffer, &handle, sizeof(uint64_t)); - client->afc_packet->operation = AFC_OP_FILE_CLOSE; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, buffer, 8, &bytes); - free(buffer); - buffer = NULL; + *(uint64_t*)(AFC_PACKET_DATA_PTR) = handle; + ret = afc_dispatch_packet(client, AFC_OP_FILE_CLOSE, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); @@ -1020,32 +833,20 @@ afc_error_t afc_file_close(afc_client_t client, uint64_t handle) } /* Receive the response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Locks or unlocks a file on the phone. - * - * makes use of flock on the device, see - * http://developer.apple.com/documentation/Darwin/Reference/ManPages/man2/flock.2.html - * - * @param client The client to lock the file with. - * @param handle File handle of a previously opened file. - * @param operation the lock or unlock operation to perform, this is one of - * AFC_LOCK_SH (shared lock), AFC_LOCK_EX (exclusive lock), - * or AFC_LOCK_UN (unlock). - */ afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation) { - char *buffer = malloc(16); uint32_t bytes = 0; - uint64_t op = GUINT64_TO_LE(operation); + struct lockinfo { + uint64_t handle; + uint64_t op; + }; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) @@ -1056,47 +857,31 @@ afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t op debug_info("file handle %i", handle); /* Send command */ - memcpy(buffer, &handle, sizeof(uint64_t)); - memcpy(buffer + 8, &op, 8); - - client->afc_packet->operation = AFC_OP_FILE_LOCK; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, buffer, 16, &bytes); - free(buffer); - buffer = NULL; - + struct lockinfo* lockinfo = (struct lockinfo*)(AFC_PACKET_DATA_PTR); + lockinfo->handle = handle; + lockinfo->op = htole64(operation); + ret = afc_dispatch_packet(client, AFC_OP_FILE_LOCK, sizeof(struct lockinfo), NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); debug_info("could not send lock command"); return AFC_E_UNKNOWN_ERROR; } /* Receive the response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) { - debug_buffer(buffer, bytes); - free(buffer); - } + ret = afc_receive_data(client, NULL, &bytes); + afc_unlock(client); return ret; } -/** - * Seeks to a given position of a pre-opened file on the phone. - * - * @param client The client to use to seek to the position. - * @param handle File handle of a previously opened. - * @param offset Seek offset. - * @param whence Seeking direction, one of SEEK_SET, SEEK_CUR, or SEEK_END. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence) { - char *buffer = (char *) malloc(sizeof(char) * 24); - int64_t offset_loc = (int64_t)GUINT64_TO_LE(offset); - uint64_t whence_loc = GUINT64_TO_LE(whence); uint32_t bytes = 0; + struct seekinfo { + uint64_t handle; + uint64_t whence; + int64_t offset; + }; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) @@ -1105,57 +890,40 @@ afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, afc_lock(client); /* Send the command */ - memcpy(buffer, &handle, sizeof(uint64_t)); /* handle */ - memcpy(buffer + 8, &whence_loc, sizeof(uint64_t)); /* fromwhere */ - memcpy(buffer + 16, &offset_loc, sizeof(uint64_t)); /* offset */ - client->afc_packet->operation = AFC_OP_FILE_SEEK; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 24, &bytes); - free(buffer); - buffer = NULL; + struct seekinfo* seekinfo = (struct seekinfo*)(AFC_PACKET_DATA_PTR); + seekinfo->handle = handle; + seekinfo->whence = htole64(whence); + seekinfo->offset = (int64_t)htole64(offset); + ret = afc_dispatch_packet(client, AFC_OP_FILE_SEEK, sizeof(struct seekinfo), NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Returns current position in a pre-opened file on the phone. - * - * @param client The client to use. - * @param handle File handle of a previously opened file. - * @param position Position in bytes of indicator - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position) { - char *buffer = (char *) malloc(sizeof(char) * 8); + char *buffer = NULL; uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) return AFC_E_INVALID_ARG; + uint32_t data_len = 8; + afc_lock(client); /* Send the command */ - memcpy(buffer, &handle, sizeof(uint64_t)); /* handle */ - client->afc_packet->operation = AFC_OP_FILE_TELL; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 8, &bytes); - free(buffer); - buffer = NULL; - + *(uint64_t*)(AFC_PACKET_DATA_PTR) = handle; + ret = afc_dispatch_packet(client, AFC_OP_FILE_TELL, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -1166,33 +934,22 @@ afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *positi if (bytes > 0 && buffer) { /* Get the position */ memcpy(position, buffer, sizeof(uint64_t)); - *position = GUINT64_FROM_LE(*position); + *position = le64toh(*position); } - if (buffer) - free(buffer); + free(buffer); afc_unlock(client); return ret; } -/** - * Sets the size of a file on the phone. - * - * @param client The client to use to set the file size. - * @param handle File handle of a previously opened file. - * @param newsize The size to set the file to. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - * - * @note This function is more akin to ftruncate than truncate, and truncate - * calls would have to open the file before calling this, sadly. - */ afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize) { - char *buffer = (char *) malloc(sizeof(char) * 16); uint32_t bytes = 0; - uint64_t newsize_loc = GUINT64_TO_LE(newsize); + struct truncinfo { + uint64_t handle; + uint64_t newsize; + }; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) @@ -1201,160 +958,240 @@ afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t new afc_lock(client); /* Send command */ - memcpy(buffer, &handle, sizeof(uint64_t)); /* handle */ - memcpy(buffer + 8, &newsize_loc, sizeof(uint64_t)); /* newsize */ - client->afc_packet->operation = AFC_OP_FILE_SET_SIZE; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 16, &bytes); - free(buffer); - buffer = NULL; + struct truncinfo* truncinfo = (struct truncinfo*)(AFC_PACKET_DATA_PTR); + truncinfo->handle = handle; + truncinfo->newsize = htole64(newsize); + ret = afc_dispatch_packet(client, AFC_OP_FILE_SET_SIZE, sizeof(struct truncinfo), NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Sets the size of a file on the phone without prior opening it. - * - * @param client The client to use to set the file size. - * @param path The path of the file to be truncated. - * @param newsize The size to set the file to. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_truncate(afc_client_t client, const char *path, uint64_t newsize) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); + if (!client || !path || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; - uint64_t size_requested = GUINT64_TO_LE(newsize); afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARG; - afc_lock(client); + uint32_t data_len = 8 + (uint32_t)(strlen(path)+1); + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - memcpy(send, &size_requested, 8); - memcpy(send + 8, path, strlen(path) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_TRUNCATE; - ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); - free(send); + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(newsize); + memcpy(AFC_PACKET_DATA_PTR + 8, path, data_len-8); + ret = afc_dispatch_packet(client, AFC_OP_TRUNCATE, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Creates a hard link or symbolic link on the device. - * - * @param client The client to use for making a link - * @param linktype 1 = hard link, 2 = symlink - * @param target The file to be linked. - * @param linkname The name of link. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, const char *target, const char *linkname) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(target)+1 + strlen(linkname)+1 + 8)); + if (!client || !target || !linkname || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; - uint64_t type = GUINT64_TO_LE(linktype); afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !target || !linkname || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARG; + size_t target_len = strlen(target); + size_t link_len = strlen(linkname); afc_lock(client); - debug_info("link type: %lld", type); - debug_info("target: %s, length:%d", target, strlen(target)); - debug_info("linkname: %s, length:%d", linkname, strlen(linkname)); + uint32_t data_len = 8 + target_len + 1 + link_len + 1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + + debug_info("link type: %lld", htole64(linktype)); + debug_info("target: %s, length:%d", target, target_len); + debug_info("linkname: %s, length:%d", linkname, link_len); /* Send command */ - memcpy(send, &type, 8); - memcpy(send + 8, target, strlen(target) + 1); - memcpy(send + 8 + strlen(target) + 1, linkname, strlen(linkname) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_MAKE_LINK; - ret = afc_dispatch_packet(client, send, 8 + strlen(linkname) + 1 + strlen(target) + 1, &bytes); - free(send); + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(linktype); + memcpy(AFC_PACKET_DATA_PTR + 8, target, target_len + 1); + memcpy(AFC_PACKET_DATA_PTR + 8 + target_len + 1, linkname, link_len + 1); + ret = afc_dispatch_packet(client, AFC_OP_MAKE_LINK, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Sets the modification time of a file on the phone. - * - * @param client The client to use to set the file size. - * @param path Path of the file for which the modification time should be set. - * @param mtime The modification time to set in nanoseconds since epoch. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_set_file_time(afc_client_t client, const char *path, uint64_t mtime) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); + if (!client || !path || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; - uint64_t mtime_loc = GUINT64_TO_LE(mtime); afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !client->afc_packet || !client->connection) + afc_lock(client); + + uint32_t data_len = 8 + strlen(path) + 1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + + /* Send command */ + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(mtime); + memcpy(AFC_PACKET_DATA_PTR + 8, path, data_len-8); + ret = afc_dispatch_packet(client, AFC_OP_SET_FILE_MOD_TIME, data_len, NULL, 0, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + /* Receive response */ + ret = afc_receive_data(client, NULL, &bytes); + + afc_unlock(client); + + return ret; +} + +afc_error_t afc_remove_path_and_contents(afc_client_t client, const char *path) +{ + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !path || !client->afc_packet || !client->parent) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = strlen(path) + 1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - memcpy(send, &mtime_loc, 8); - memcpy(send + 8, path, strlen(path) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_SET_FILE_TIME; - ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); - free(send); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_REMOVE_PATH_AND_CONTENTS, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } +afc_error_t afc_dictionary_free(char **dictionary) +{ + int i = 0; + + if (!dictionary) + return AFC_E_INVALID_ARG; + + for (i = 0; dictionary[i]; i++) { + free(dictionary[i]); + } + free(dictionary); + + return AFC_E_SUCCESS; +} + +const char* afc_strerror(afc_error_t err) +{ + switch (err) { + case AFC_E_SUCCESS: + return "Success"; + case AFC_E_UNKNOWN_ERROR: + return "Unknown Error"; + case AFC_E_OP_HEADER_INVALID: + return "Operation header invalid"; + case AFC_E_NO_RESOURCES: + return "No resources"; + case AFC_E_READ_ERROR: + return "Read error"; + case AFC_E_WRITE_ERROR: + return "Write error"; + case AFC_E_UNKNOWN_PACKET_TYPE: + return "Unknown packet type"; + case AFC_E_INVALID_ARG: + return "Invalid argument"; + case AFC_E_OBJECT_NOT_FOUND: + return "Not found"; + case AFC_E_OBJECT_IS_DIR: + return "Object is a directory"; + case AFC_E_PERM_DENIED: + return "Permission denied"; + case AFC_E_SERVICE_NOT_CONNECTED: + return "Service not connected"; + case AFC_E_OP_TIMEOUT: + return "Timeout"; + case AFC_E_TOO_MUCH_DATA: + return "Too much data"; + case AFC_E_END_OF_DATA: + return "End of data"; + case AFC_E_OP_NOT_SUPPORTED: + return "Operation not supported"; + case AFC_E_OBJECT_EXISTS: + return "Object exists"; + case AFC_E_OBJECT_BUSY: + return "Object busy"; + case AFC_E_NO_SPACE_LEFT: + return "No space left on device"; + case AFC_E_OP_WOULD_BLOCK: + return "Operation would block"; + case AFC_E_IO_ERROR: + return "I/O error"; + case AFC_E_OP_INTERRUPTED: + return "Operation interrupted"; + case AFC_E_OP_IN_PROGRESS: + return "Operation on progress"; + case AFC_E_INTERNAL_ERROR: + return "Internal error"; + case AFC_E_MUX_ERROR: + return "MUX error"; + case AFC_E_NO_MEM: + return "Out of memory"; + case AFC_E_NOT_ENOUGH_DATA: + return "Not enough data"; + case AFC_E_DIR_NOT_EMPTY: + return "Directory not empty"; + case AFC_E_FORCE_SIGNED_TYPE: + return "Force signed type"; + default: + break; + } + return "Unknown Error"; +} @@ -1,28 +1,34 @@ -/* +/* * afc.h * Defines and structs and the like for the built-in AFC client - * + * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <glib.h> +#ifndef __AFC_H +#define __AFC_H + #include <stdint.h> #include "libimobiledevice/afc.h" +#include "service.h" +#include "endianness.h" +#include <libimobiledevice-glue/thread.h> #define AFC_MAGIC "CFA6LPAA" #define AFC_MAGIC_LEN (8) @@ -33,60 +39,72 @@ typedef struct { } AFCPacket; #define AFCPacket_to_LE(x) \ - (x)->entire_length = GUINT64_TO_LE((x)->entire_length); \ - (x)->this_length = GUINT64_TO_LE((x)->this_length); \ - (x)->packet_num = GUINT64_TO_LE((x)->packet_num); \ - (x)->operation = GUINT64_TO_LE((x)->operation); + (x)->entire_length = htole64((x)->entire_length); \ + (x)->this_length = htole64((x)->this_length); \ + (x)->packet_num = htole64((x)->packet_num); \ + (x)->operation = htole64((x)->operation); #define AFCPacket_from_LE(x) \ - (x)->entire_length = GUINT64_FROM_LE((x)->entire_length); \ - (x)->this_length = GUINT64_FROM_LE((x)->this_length); \ - (x)->packet_num = GUINT64_FROM_LE((x)->packet_num); \ - (x)->operation = GUINT64_FROM_LE((x)->operation); - -typedef struct { - uint64_t filehandle, size; -} AFCFilePacket; + (x)->entire_length = le64toh((x)->entire_length); \ + (x)->this_length = le64toh((x)->this_length); \ + (x)->packet_num = le64toh((x)->packet_num); \ + (x)->operation = le64toh((x)->operation); struct afc_client_private { - idevice_connection_t connection; + service_client_t parent; AFCPacket *afc_packet; - int file_handle; - int lock; - GMutex *mutex; - int own_connection; + uint32_t packet_extra; + mutex_t mutex; + int free_parent; }; /* AFC Operations */ enum { - AFC_OP_STATUS = 0x00000001, /* Status */ - AFC_OP_DATA = 0x00000002, /* Data */ - AFC_OP_READ_DIR = 0x00000003, /* ReadDir */ - AFC_OP_READ_FILE = 0x00000004, /* ReadFile */ - AFC_OP_WRITE_FILE = 0x00000005, /* WriteFile */ - AFC_OP_WRITE_PART = 0x00000006, /* WritePart */ - AFC_OP_TRUNCATE = 0x00000007, /* TruncateFile */ - AFC_OP_REMOVE_PATH = 0x00000008, /* RemovePath */ - AFC_OP_MAKE_DIR = 0x00000009, /* MakeDir */ - AFC_OP_GET_FILE_INFO = 0x0000000a, /* GetFileInfo */ - AFC_OP_GET_DEVINFO = 0x0000000b, /* GetDeviceInfo */ - AFC_OP_WRITE_FILE_ATOM = 0x0000000c, /* WriteFileAtomic (tmp file+rename) */ - AFC_OP_FILE_OPEN = 0x0000000d, /* FileRefOpen */ - AFC_OP_FILE_OPEN_RES = 0x0000000e, /* FileRefOpenResult */ - AFC_OP_READ = 0x0000000f, /* FileRefRead */ - AFC_OP_WRITE = 0x00000010, /* FileRefWrite */ - AFC_OP_FILE_SEEK = 0x00000011, /* FileRefSeek */ - AFC_OP_FILE_TELL = 0x00000012, /* FileRefTell */ - AFC_OP_FILE_TELL_RES = 0x00000013, /* FileRefTellResult */ - AFC_OP_FILE_CLOSE = 0x00000014, /* FileRefClose */ - AFC_OP_FILE_SET_SIZE = 0x00000015, /* FileRefSetFileSize (ftruncate) */ - AFC_OP_GET_CON_INFO = 0x00000016, /* GetConnectionInfo */ - AFC_OP_SET_CON_OPTIONS = 0x00000017, /* SetConnectionOptions */ - AFC_OP_RENAME_PATH = 0x00000018, /* RenamePath */ - AFC_OP_SET_FS_BS = 0x00000019, /* SetFSBlockSize (0x800000) */ - AFC_OP_SET_SOCKET_BS = 0x0000001A, /* SetSocketBlockSize (0x800000) */ - AFC_OP_FILE_LOCK = 0x0000001B, /* FileRefLock */ - AFC_OP_MAKE_LINK = 0x0000001C, /* MakeLink */ - AFC_OP_SET_FILE_TIME = 0x0000001E /* set st_mtime */ + AFC_OP_INVALID = 0x00000000, /* Invalid */ + AFC_OP_STATUS = 0x00000001, /* Status */ + AFC_OP_DATA = 0x00000002, /* Data */ + AFC_OP_READ_DIR = 0x00000003, /* ReadDir */ + AFC_OP_READ_FILE = 0x00000004, /* ReadFile */ + AFC_OP_WRITE_FILE = 0x00000005, /* WriteFile */ + AFC_OP_WRITE_PART = 0x00000006, /* WritePart */ + AFC_OP_TRUNCATE = 0x00000007, /* TruncateFile */ + AFC_OP_REMOVE_PATH = 0x00000008, /* RemovePath */ + AFC_OP_MAKE_DIR = 0x00000009, /* MakeDir */ + AFC_OP_GET_FILE_INFO = 0x0000000A, /* GetFileInfo */ + AFC_OP_GET_DEVINFO = 0x0000000B, /* GetDeviceInfo */ + AFC_OP_WRITE_FILE_ATOM = 0x0000000C, /* WriteFileAtomic (tmp file+rename) */ + AFC_OP_FILE_OPEN = 0x0000000D, /* FileRefOpen */ + AFC_OP_FILE_OPEN_RES = 0x0000000E, /* FileRefOpenResult */ + AFC_OP_FILE_READ = 0x0000000F, /* FileRefRead */ + AFC_OP_FILE_WRITE = 0x00000010, /* FileRefWrite */ + AFC_OP_FILE_SEEK = 0x00000011, /* FileRefSeek */ + AFC_OP_FILE_TELL = 0x00000012, /* FileRefTell */ + AFC_OP_FILE_TELL_RES = 0x00000013, /* FileRefTellResult */ + AFC_OP_FILE_CLOSE = 0x00000014, /* FileRefClose */ + AFC_OP_FILE_SET_SIZE = 0x00000015, /* FileRefSetFileSize (ftruncate) */ + AFC_OP_GET_CON_INFO = 0x00000016, /* GetConnectionInfo */ + AFC_OP_SET_CON_OPTIONS = 0x00000017, /* SetConnectionOptions */ + AFC_OP_RENAME_PATH = 0x00000018, /* RenamePath */ + AFC_OP_SET_FS_BS = 0x00000019, /* SetFSBlockSize (0x800000) */ + AFC_OP_SET_SOCKET_BS = 0x0000001A, /* SetSocketBlockSize (0x800000) */ + AFC_OP_FILE_LOCK = 0x0000001B, /* FileRefLock */ + AFC_OP_MAKE_LINK = 0x0000001C, /* MakeLink */ + AFC_OP_GET_FILE_HASH = 0x0000001D, /* GetFileHash */ + AFC_OP_SET_FILE_MOD_TIME = 0x0000001E, /* SetModTime */ + AFC_OP_GET_FILE_HASH_RANGE = 0x0000001F, /* GetFileHashWithRange */ + /* iOS 6+ */ + AFC_OP_FILE_SET_IMMUTABLE_HINT = 0x00000020, /* FileRefSetImmutableHint */ + AFC_OP_GET_SIZE_OF_PATH_CONTENTS = 0x00000021, /* GetSizeOfPathContents */ + AFC_OP_REMOVE_PATH_AND_CONTENTS = 0x00000022, /* RemovePathAndContents */ + AFC_OP_DIR_OPEN = 0x00000023, /* DirectoryEnumeratorRefOpen */ + AFC_OP_DIR_OPEN_RESULT = 0x00000024, /* DirectoryEnumeratorRefOpenResult */ + AFC_OP_DIR_READ = 0x00000025, /* DirectoryEnumeratorRefRead */ + AFC_OP_DIR_CLOSE = 0x00000026, /* DirectoryEnumeratorRefClose */ + /* iOS 7+ */ + AFC_OP_FILE_READ_OFFSET = 0x00000027, /* FileRefReadWithOffset */ + AFC_OP_FILE_WRITE_OFFSET = 0x00000028 /* FileRefWriteWithOffset */ }; +afc_error_t afc_client_new_with_service_client(service_client_t service_client, afc_client_t *client); + +#endif diff --git a/src/bt_packet_logger.c b/src/bt_packet_logger.c new file mode 100644 index 0000000..937747c --- /dev/null +++ b/src/bt_packet_logger.c @@ -0,0 +1,231 @@ +/* + * bt_packet_logger.c + * com.apple.bluetooth.BTPacketLogger service implementation. + * + * Copyright (c) 2021 Geoffrey Kruse, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> + +#include "bt_packet_logger.h" +#include "lockdown.h" +#include "common/debug.h" + +struct bt_packet_logger_worker_thread { + bt_packet_logger_client_t client; + bt_packet_logger_receive_cb_t cbfunc; + void *user_data; + uint8_t rxbuff[BT_MAX_PACKET_SIZE]; +}; + +#define SZ_READ_TIMEOUT 100 +#define PAYLOAD_READ_TIMEOUT 500 + +/** + * Convert a service_error_t value to a bt_packet_logger_error_t value. + * Used internally to get correct error codes. + * + * @param err An service_error_t error code + * + * @return A matching bt_packet_logger_error_t error code, + * BT_PACKET_LOGGER_E_UNKNOWN_ERROR otherwise. + */ +static bt_packet_logger_error_t bt_packet_logger_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return BT_PACKET_LOGGER_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return BT_PACKET_LOGGER_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return BT_PACKET_LOGGER_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return BT_PACKET_LOGGER_E_SSL_ERROR; + case SERVICE_E_NOT_ENOUGH_DATA: + return BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA; + case SERVICE_E_TIMEOUT: + return BT_PACKET_LOGGER_E_TIMEOUT; + default: + break; + } + return BT_PACKET_LOGGER_E_UNKNOWN_ERROR; +} + +bt_packet_logger_error_t bt_packet_logger_client_new(idevice_t device, lockdownd_service_descriptor_t service, bt_packet_logger_client_t * client) +{ + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to bt_packet_logger_client_new."); + return BT_PACKET_LOGGER_E_INVALID_ARG; + } + + debug_info("Creating bt_packet_logger_client, port = %d.", service->port); + + service_client_t parent = NULL; + bt_packet_logger_error_t ret = bt_packet_logger_error(service_client_new(device, service, &parent)); + if (ret != BT_PACKET_LOGGER_E_SUCCESS) { + debug_info("Creating base service client failed. Error: %i", ret); + return ret; + } + + bt_packet_logger_client_t client_loc = (bt_packet_logger_client_t) malloc(sizeof(struct bt_packet_logger_client_private)); + client_loc->parent = parent; + client_loc->worker = THREAD_T_NULL; + + *client = client_loc; + + debug_info("bt_packet_logger_client successfully created."); + return 0; +} + +bt_packet_logger_error_t bt_packet_logger_client_start_service(idevice_t device, bt_packet_logger_client_t * client, const char* label) +{ + bt_packet_logger_error_t err = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, BT_PACKETLOGGER_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(bt_packet_logger_client_new), &err); + return err; +} + +bt_packet_logger_error_t bt_packet_logger_client_free(bt_packet_logger_client_t client) +{ + if (!client) + return BT_PACKET_LOGGER_E_INVALID_ARG; + bt_packet_logger_stop_capture(client); + bt_packet_logger_error_t err = bt_packet_logger_error(service_client_free(client->parent)); + free(client); + + return err; +} + +bt_packet_logger_error_t bt_packet_logger_receive_with_timeout(bt_packet_logger_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + bt_packet_logger_error_t res = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return BT_PACKET_LOGGER_E_INVALID_ARG; + } + + res = bt_packet_logger_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); + if (res != BT_PACKET_LOGGER_E_SUCCESS && res != BT_PACKET_LOGGER_E_TIMEOUT && res != BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA) { + debug_info("Could not read data, error %d", res); + } + if (received) { + *received = (uint32_t)bytes; + } + + return res; +} + +void *bt_packet_logger_worker(void *arg) +{ + bt_packet_logger_error_t ret = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + struct bt_packet_logger_worker_thread *btwt = (struct bt_packet_logger_worker_thread*)arg; + + if (!btwt) { + return NULL; + } + + debug_info("Running"); + + while (btwt->client->parent) { + uint32_t bytes = 0; + uint16_t len; + + ret = bt_packet_logger_receive_with_timeout(btwt->client, (char*)&len, 2, &bytes, SZ_READ_TIMEOUT); + + if (ret == BT_PACKET_LOGGER_E_TIMEOUT || ret == BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA || ((bytes == 0) && (ret == BT_PACKET_LOGGER_E_SUCCESS))) { + continue; + } else if (ret < 0) { + debug_info("Connection to bt packet logger interrupted"); + break; + } + + // sanity check received length + if(bytes > 0 && len > sizeof(bt_packet_logger_header_t)) { + debug_info("Reading %u bytes\n", len); + ret = bt_packet_logger_receive_with_timeout(btwt->client, (char *)btwt->rxbuff, len, &bytes, PAYLOAD_READ_TIMEOUT); + + if(len != bytes) { + debug_info("Failed Read Expected %u, Received %u\n", len, bytes); + continue; + } + + if (ret == BT_PACKET_LOGGER_E_TIMEOUT || ret == BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA || ((bytes == 0) && (ret == BT_PACKET_LOGGER_E_SUCCESS))) { + continue; + } else if (ret < 0) { + debug_info("Connection to bt packet logger interrupted"); + break; + } + + btwt->cbfunc(btwt->rxbuff, len, btwt->user_data); + } + } + + // null check performed above + free(btwt); + + debug_info("Exiting"); + + return NULL; +} + +bt_packet_logger_error_t bt_packet_logger_start_capture(bt_packet_logger_client_t client, bt_packet_logger_receive_cb_t callback, void* user_data) +{ + if (!client || !callback) + return BT_PACKET_LOGGER_E_INVALID_ARG; + + bt_packet_logger_error_t res = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + + if (client->worker) { + debug_info("Another syslog capture thread appears to be running already."); + return res; + } + + /* start worker thread */ + struct bt_packet_logger_worker_thread *btwt = (struct bt_packet_logger_worker_thread*)malloc(sizeof(struct bt_packet_logger_worker_thread)); + if (btwt) { + btwt->client = client; + btwt->cbfunc = callback; + btwt->user_data = user_data; + + if (thread_new(&client->worker, bt_packet_logger_worker, btwt) == 0) { + res = BT_PACKET_LOGGER_E_SUCCESS; + } + } + + return res; +} + + +bt_packet_logger_error_t bt_packet_logger_stop_capture(bt_packet_logger_client_t client) +{ + if (client->worker) { + /* notify thread to finish */ + service_client_t parent = client->parent; + client->parent = NULL; + /* join thread to make it exit */ + thread_join(client->worker); + thread_free(client->worker); + client->worker = THREAD_T_NULL; + client->parent = parent; + } + + return BT_PACKET_LOGGER_E_SUCCESS; +} diff --git a/src/bt_packet_logger.h b/src/bt_packet_logger.h new file mode 100644 index 0000000..620555e --- /dev/null +++ b/src/bt_packet_logger.h @@ -0,0 +1,37 @@ +/* + * bt_packet_logger.h + * com.apple.bluetooth.BTPacketLogger service header file. + * + * Copyright (c) 2021 Geoffrey Kruse, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _BR_PACKET_LOGGER_H +#define _BR_PACKET_LOGGER_H + +#include "idevice.h" +#include "libimobiledevice/bt_packet_logger.h" +#include "service.h" +#include <libimobiledevice-glue/thread.h> + +struct bt_packet_logger_client_private { + service_client_t parent; + THREAD_T worker; +}; + +void *bt_packet_logger_worker(void *arg); + +#endif diff --git a/src/companion_proxy.c b/src/companion_proxy.c new file mode 100644 index 0000000..421fa9a --- /dev/null +++ b/src/companion_proxy.c @@ -0,0 +1,380 @@ +/* + * companion_proxy.c + * com.apple.companion_proxy service implementation. + * + * Copyright (c) 2019-2020 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <plist/plist.h> + +#include "companion_proxy.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a companion_proxy_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching companion_proxy_error_t error code, + * COMPANION_PROXY_E_UNKNOWN_ERROR otherwise. + */ +static companion_proxy_error_t companion_proxy_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return COMPANION_PROXY_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return COMPANION_PROXY_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return COMPANION_PROXY_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return COMPANION_PROXY_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return COMPANION_PROXY_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return COMPANION_PROXY_E_NOT_ENOUGH_DATA; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return COMPANION_PROXY_E_TIMEOUT; + default: + break; + } + return COMPANION_PROXY_E_UNKNOWN_ERROR; +} + +companion_proxy_error_t companion_proxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, companion_proxy_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to companion_proxy_client_new."); + return COMPANION_PROXY_E_INVALID_ARG; + } + + debug_info("Creating companion_proxy_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + companion_proxy_error_t ret = companion_proxy_error(property_list_service_client_new(device, service, &plclient)); + if (ret != COMPANION_PROXY_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + companion_proxy_client_t client_loc = (companion_proxy_client_t) malloc(sizeof(struct companion_proxy_client_private)); + client_loc->parent = plclient; + client_loc->event_thread = THREAD_T_NULL; + + *client = client_loc; + + debug_info("Created companion_proxy_client successfully."); + return COMPANION_PROXY_E_SUCCESS; +} + +companion_proxy_error_t companion_proxy_client_start_service(idevice_t device, companion_proxy_client_t * client, const char* label) +{ + companion_proxy_error_t err = COMPANION_PROXY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, COMPANION_PROXY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(companion_proxy_client_new), &err); + return err; +} + +companion_proxy_error_t companion_proxy_client_free(companion_proxy_client_t client) +{ + if (!client) + return COMPANION_PROXY_E_INVALID_ARG; + + property_list_service_client_t parent = client->parent; + client->parent = NULL; + if (client->event_thread) { + debug_info("joining event thread"); + thread_join(client->event_thread); + thread_free(client->event_thread); + client->event_thread = THREAD_T_NULL; + } + companion_proxy_error_t err = companion_proxy_error(property_list_service_client_free(parent)); + free(client); + + return err; +} + +companion_proxy_error_t companion_proxy_send(companion_proxy_client_t client, plist_t plist) +{ + companion_proxy_error_t res = COMPANION_PROXY_E_UNKNOWN_ERROR; + + res = companion_proxy_error(property_list_service_send_binary_plist(client->parent, plist)); + if (res != COMPANION_PROXY_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + + return res; +} + +companion_proxy_error_t companion_proxy_receive(companion_proxy_client_t client, plist_t * plist) +{ + companion_proxy_error_t res = COMPANION_PROXY_E_UNKNOWN_ERROR; + plist_t outplist = NULL; + res = companion_proxy_error(property_list_service_receive_plist_with_timeout(client->parent, &outplist, 10000)); + if (res != COMPANION_PROXY_E_SUCCESS && res != COMPANION_PROXY_E_TIMEOUT) { + debug_info("Could not receive plist, error %d", res); + plist_free(outplist); + } else if (res == COMPANION_PROXY_E_SUCCESS) { + *plist = outplist; + } + return res; +} + +companion_proxy_error_t companion_proxy_get_device_registry(companion_proxy_client_t client, plist_t* paired_devices) +{ + if (!client || !paired_devices) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("GetDeviceRegistry")); + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + if (!dict || !PLIST_IS_DICT(dict)) { + return COMPANION_PROXY_E_PLIST_ERROR; + } + plist_t val = plist_dict_get_item(dict, "PairedDevicesArray"); + if (val) { + *paired_devices = plist_copy(val); + res = COMPANION_PROXY_E_SUCCESS; + } else { + res = COMPANION_PROXY_E_UNKNOWN_ERROR; + val = plist_dict_get_item(dict, "Error"); + if (val) { + if (plist_string_val_compare(val, "NoPairedWatches")) { + res = COMPANION_PROXY_E_NO_DEVICES; + } + } + } + plist_free(dict); + return res; +} + +struct companion_proxy_cb_data { + companion_proxy_client_t client; + companion_proxy_device_event_cb_t cbfunc; + void* user_data; +}; + +static void* companion_proxy_event_thread(void* arg) +{ + struct companion_proxy_cb_data* data = (struct companion_proxy_cb_data*)arg; + companion_proxy_client_t client = data->client; + companion_proxy_error_t res; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("StartListeningForDevices")); + res = companion_proxy_send(client, command); + plist_free(command); + + if (res != COMPANION_PROXY_E_SUCCESS) { + free(data); + client->event_thread = THREAD_T_NULL; + return NULL; + } + + while (client && client->parent) { + plist_t node = NULL; + res = companion_proxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000)); + if (res != COMPANION_PROXY_E_SUCCESS && res != COMPANION_PROXY_E_TIMEOUT) { + debug_info("could not receive plist, error %d", res); + break; + } + + if (node) { + data->cbfunc(node, data->user_data); + } + plist_free(node); + } + + client->event_thread = THREAD_T_NULL; + free(data); + + return NULL; +} + +companion_proxy_error_t companion_proxy_start_listening_for_devices(companion_proxy_client_t client, companion_proxy_device_event_cb_t callback, void* userdata) +{ + if (!client || !client->parent || !callback) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + if (client->event_thread) { + return COMPANION_PROXY_E_OP_IN_PROGRESS; + } + + companion_proxy_error_t res = COMPANION_PROXY_E_UNKNOWN_ERROR; + struct companion_proxy_cb_data *data = (struct companion_proxy_cb_data*)malloc(sizeof(struct companion_proxy_cb_data)); + if (data) { + data->client = client; + data->cbfunc = callback; + data->user_data = userdata; + + if (thread_new(&client->event_thread, companion_proxy_event_thread, data) == 0) { + res = COMPANION_PROXY_E_SUCCESS; + } else { + free(data); + } + } + return res; +} + +companion_proxy_error_t companion_proxy_stop_listening_for_devices(companion_proxy_client_t client) +{ + property_list_service_client_t parent = client->parent; + client->parent = NULL; + if (client->event_thread) { + debug_info("joining event thread"); + thread_join(client->event_thread); + thread_free(client->event_thread); + client->event_thread = THREAD_T_NULL; + } + client->parent = parent; + return COMPANION_PROXY_E_SUCCESS; +} + +companion_proxy_error_t companion_proxy_get_value_from_registry(companion_proxy_client_t client, const char* companion_udid, const char* key, plist_t* value) +{ + if (!client || !companion_udid || !key || !value) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("GetValueFromRegistry")); + plist_dict_set_item(dict, "GetValueGizmoUDIDKey", plist_new_string(companion_udid)); + plist_dict_set_item(dict, "GetValueKeyKey", plist_new_string(key)); + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + if (!dict || !PLIST_IS_DICT(dict)) { + return COMPANION_PROXY_E_PLIST_ERROR; + } + plist_t val = plist_dict_get_item(dict, "RetrievedValueDictionary"); + if (val) { + *value = plist_copy(val); + res = COMPANION_PROXY_E_SUCCESS; + } else { + res = COMPANION_PROXY_E_UNKNOWN_ERROR; + val = plist_dict_get_item(dict, "Error"); + if (val) { + if (!plist_string_val_compare(val, "UnsupportedWatchKey")) { + res = COMPANION_PROXY_E_UNSUPPORTED_KEY; + } else if (plist_string_val_compare(val, "TimeoutReply")) { + res = COMPANION_PROXY_E_TIMEOUT_REPLY; + } + } + } + plist_free(dict); + return res; +} + +companion_proxy_error_t companion_proxy_start_forwarding_service_port(companion_proxy_client_t client, uint16_t remote_port, const char* service_name, uint16_t* forward_port, plist_t options) +{ + if (!client) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("StartForwardingServicePort")); + plist_dict_set_item(dict, "GizmoRemotePortNumber", plist_new_uint(remote_port)); + if (service_name) { + plist_dict_set_item(dict, "ForwardedServiceName", plist_new_string(service_name)); + } + plist_dict_set_item(dict, "IsServiceLowPriority", plist_new_bool(0)); + plist_dict_set_item(dict, "PreferWifi", plist_new_bool(0)); + if (options) { + plist_dict_merge(&dict, options); + } + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + plist_t val = plist_dict_get_item(dict, "CompanionProxyServicePort"); + if (val) { + uint64_t u64val = 0; + plist_get_uint_val(val, &u64val); + *forward_port = (uint16_t)u64val; + res = COMPANION_PROXY_E_SUCCESS; + } else { + res = COMPANION_PROXY_E_UNKNOWN_ERROR; + } + plist_free(dict); + + return res; +} + +companion_proxy_error_t companion_proxy_stop_forwarding_service_port(companion_proxy_client_t client, uint16_t remote_port) +{ + if (!client) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("StopForwardingServicePort")); + plist_dict_set_item(dict, "GizmoRemotePortNumber", plist_new_uint(remote_port)); + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + plist_free(dict); + + return res; +} diff --git a/src/companion_proxy.h b/src/companion_proxy.h new file mode 100644 index 0000000..e36932a --- /dev/null +++ b/src/companion_proxy.h @@ -0,0 +1,35 @@ +/* + * companion_proxy.h + * com.apple.companion_proxy service header file. + * + * Copyright (c) 2019-2020 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __COMPANION_PROXY_H +#define __COMPANION_PROXY_H + +#include "idevice.h" +#include "libimobiledevice/companion_proxy.h" +#include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> + +struct companion_proxy_client_private { + property_list_service_client_t parent; + THREAD_T event_thread; +}; + +#endif diff --git a/src/debugserver.c b/src/debugserver.c new file mode 100644 index 0000000..74ade8a --- /dev/null +++ b/src/debugserver.c @@ -0,0 +1,657 @@ +/* + * debugserver.c + * com.apple.debugserver service implementation. + * + * Copyright (c) 2019 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2014-2015 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include <stdio.h> + +#include <libimobiledevice-glue/utils.h> + +#include "debugserver.h" +#include "lockdown.h" +#include "common/debug.h" +#include "asprintf.h" + +/** + * Convert a service_error_t value to a debugserver_error_t value. + * Used internally to get correct error codes. + * + * @param err An service_error_t error code + * + * @return A matching debugserver_error_t error code, + * DEBUGSERVER_E_UNKNOWN_ERROR otherwise. + */ +static debugserver_error_t debugserver_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return DEBUGSERVER_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return DEBUGSERVER_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return DEBUGSERVER_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return DEBUGSERVER_E_SSL_ERROR; + case SERVICE_E_TIMEOUT: + return DEBUGSERVER_E_TIMEOUT; + default: + break; + } + return DEBUGSERVER_E_UNKNOWN_ERROR; +} + +debugserver_error_t debugserver_client_new(idevice_t device, lockdownd_service_descriptor_t service, debugserver_client_t* client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to debugserver_client_new."); + return DEBUGSERVER_E_INVALID_ARG; + } + + debug_info("Creating debugserver_client, port = %d.", service->port); + + service_client_t parent = NULL; + debugserver_error_t ret = debugserver_error(service_client_new(device, service, &parent)); + if (ret != DEBUGSERVER_E_SUCCESS) { + debug_info("Creating base service client failed. Error: %i", ret); + return ret; + } + + if (service->identifier && (strcmp(service->identifier, DEBUGSERVER_SECURE_SERVICE_NAME) != 0)) { + service_disable_bypass_ssl(parent, 1); + } + + debugserver_client_t client_loc = (debugserver_client_t) malloc(sizeof(struct debugserver_client_private)); + client_loc->parent = parent; + client_loc->noack_mode = 0; + client_loc->cancel_receive = NULL; + client_loc->receive_loop_timeout = 1000; + + *client = client_loc; + + debug_info("debugserver_client successfully created."); + return DEBUGSERVER_E_SUCCESS; +} + +debugserver_error_t debugserver_client_start_service(idevice_t device, debugserver_client_t * client, const char* label) +{ + debugserver_error_t err = DEBUGSERVER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, DEBUGSERVER_SECURE_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(debugserver_client_new), &err); + if (err != DEBUGSERVER_E_SUCCESS) { + err = DEBUGSERVER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, DEBUGSERVER_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(debugserver_client_new), &err); + } + return err; +} + +debugserver_error_t debugserver_client_free(debugserver_client_t client) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t err = debugserver_error(service_client_free(client->parent)); + client->parent = NULL; + free(client); + + return err; +} + +debugserver_error_t debugserver_client_send(debugserver_client_t client, const char* data, uint32_t size, uint32_t *sent) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return DEBUGSERVER_E_INVALID_ARG; + } + + debug_info("sending %d bytes", size); + res = debugserver_error(service_send(client->parent, data, size, (uint32_t*)&bytes)); + if (bytes <= 0) { + debug_info("ERROR: sending to device failed."); + } + if (sent) { + *sent = (uint32_t)bytes; + } + + return res; +} + +debugserver_error_t debugserver_client_receive_with_timeout(debugserver_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return DEBUGSERVER_E_INVALID_ARG; + } + + res = debugserver_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); + if (bytes <= 0 && res != DEBUGSERVER_E_TIMEOUT) { + debug_info("Could not read data, error %d", res); + } + if (received) { + *received = (uint32_t)bytes; + } + + return (bytes > 0) ? DEBUGSERVER_E_SUCCESS : res; +} + +debugserver_error_t debugserver_client_receive(debugserver_client_t client, char* data, uint32_t size, uint32_t *received) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + do { + /* Is this allowed to return DEBUGSERVER_E_TIMEOUT and also set data and received? */ + res = debugserver_client_receive_with_timeout(client, data, size, received, client->receive_loop_timeout); + } while (res == DEBUGSERVER_E_TIMEOUT && client->cancel_receive != NULL && !client->cancel_receive()); + return res; +} + +debugserver_error_t debugserver_command_new(const char* name, int argc, char* argv[], debugserver_command_t* command) +{ + int i; + debugserver_command_t tmp = (debugserver_command_t) malloc(sizeof(struct debugserver_command_private)); + + /* copy name */ + tmp->name = strdup(name); + + /* copy arguments */ + tmp->argc = argc; + tmp->argv = NULL; + if (argc > 0) { + tmp->argv = malloc(sizeof(char*) * (argc + 2)); + for (i = 0; i < argc; i++) { + tmp->argv[i] = strdup(argv[i]); + } + tmp->argv[i+1] = NULL; + } + + /* return */ + *command = tmp; + + return DEBUGSERVER_E_SUCCESS; +} + +debugserver_error_t debugserver_command_free(debugserver_command_t command) +{ + int i; + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + + if (!command) + return DEBUGSERVER_E_INVALID_ARG; + + if (command) { + if (command->name) + free(command->name); + if (command->argv && command->argc) { + for (i = 0; i < command->argc; i++) { + free(command->argv[i]); + } + free(command->argv); + } + free(command); + res = DEBUGSERVER_E_SUCCESS; + } + + return res; +} + +static int debugserver_hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return 10 + c - 'a'; + else if (c >= 'A' && c <= 'F') + return 10 + c - 'A'; + else + return c; +} + +static char debugserver_int2hex(int x) +{ + const char *hexchars = "0123456789ABCDEF"; + return hexchars[x]; +} + +#define DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(byte) debugserver_int2hex(((byte) >> 0x4) & 0xf) +#define DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(byte) debugserver_int2hex((byte) & 0xf) +#define DEBUGSERVER_HEX_DECODE_FIRST_BYTE(byte) (((byte) >> 0x4) & 0xf) +#define DEBUGSERVER_HEX_DECODE_SECOND_BYTE(byte) ((byte) & 0xf) + +static uint32_t debugserver_get_checksum_for_buffer(const char* buffer, uint32_t size) +{ + uint32_t checksum = 0; + uint32_t i; + + for (i = 0; i < size; i++) { + checksum += buffer[i]; + } + + return checksum; +} + +static int debugserver_response_is_checksum_valid(const char* response, uint32_t size) +{ + uint32_t checksum = 0; + if ((size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1) > 0) + checksum = debugserver_get_checksum_for_buffer(&response[1], size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1); + + debug_info("checksum: 0x%x", checksum); + + if ((unsigned)debugserver_hex2int(response[size - 2]) != DEBUGSERVER_HEX_DECODE_FIRST_BYTE(checksum)) + return 0; + + if ((unsigned)debugserver_hex2int(response[size - 1]) != DEBUGSERVER_HEX_DECODE_SECOND_BYTE(checksum)) + return 0; + + debug_info("valid checksum"); + + return 1; +} + +void debugserver_encode_string(const char* buffer, char** encoded_buffer, uint32_t* encoded_length) +{ + uint32_t position; + uint32_t index; + uint32_t length = strlen(buffer); + *encoded_length = (2 * length) + DEBUGSERVER_CHECKSUM_HASH_LENGTH + 1; + + *encoded_buffer = malloc(sizeof(char) * (*encoded_length)); + memset(*encoded_buffer, '\0', *encoded_length); + for (position = 0, index = 0; index < length; index++) { + position = (index * (2 * sizeof(char))); + (*encoded_buffer)[position] = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(buffer[index]); + (*encoded_buffer)[position + 1] = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(buffer[index]); + } +} + +void debugserver_decode_string(const char *encoded_buffer, size_t encoded_length, char** buffer) +{ + *buffer = malloc(sizeof(char) * ((encoded_length / 2)+1)); + char* t = *buffer; + const char *f = encoded_buffer; + const char *fend = f + encoded_length; + while (f < fend) { + *t++ = debugserver_hex2int(*f) << 4 | debugserver_hex2int(f[1]); + f += 2; + } + *t = '\0'; +} + +static void debugserver_format_command(const char* prefix, const char* command, const char* arguments, int calculate_checksum, char** buffer, uint32_t* size) +{ + char checksum_hash[DEBUGSERVER_CHECKSUM_HASH_LENGTH + 1] = {'#', '0', '0', '\0'}; + char* encoded = NULL; + uint32_t encoded_length = 0; + + if (arguments) { + /* arguments must be hex encoded */ + debugserver_encode_string(arguments, &encoded, &encoded_length); + } else { + encoded = NULL; + } + + char* encoded_command = string_concat(command, encoded, NULL); + encoded_length = strlen(encoded_command); + + if (calculate_checksum) { + uint32_t checksum = debugserver_get_checksum_for_buffer(encoded_command, encoded_length); + checksum_hash[1] = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(checksum); + checksum_hash[2] = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(checksum); + } + + *buffer = string_concat(prefix, encoded_command, checksum_hash, NULL); + *size = strlen(prefix) + strlen(encoded_command) + DEBUGSERVER_CHECKSUM_HASH_LENGTH; + + debug_info("formatted command: %s size: %d checksum: 0x%s", *buffer, *size, checksum_hash); + + if (encoded_command) + free(encoded_command); + + if (encoded) + free(encoded); +} + +static debugserver_error_t debugserver_client_send_ack(debugserver_client_t client) +{ + debug_info("sending ACK"); + return debugserver_client_send(client, "+", sizeof(char), NULL); +} + +static debugserver_error_t debugserver_client_send_noack(debugserver_client_t client) +{ + debug_info("sending !ACK"); + return debugserver_client_send(client, "-", sizeof(char), NULL); +} + +debugserver_error_t debugserver_client_set_ack_mode(debugserver_client_t client, int enabled) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + client->noack_mode = (enabled == 0)? 1: 0; + + debug_info("ack mode: %s", client->noack_mode == 0 ? "on": "off"); + + return DEBUGSERVER_E_SUCCESS; +} + +debugserver_error_t debugserver_client_set_receive_params(debugserver_client_t client, int (*cancel_receive)(), int receive_loop_timeout) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + client->cancel_receive = cancel_receive; + client->receive_loop_timeout = receive_loop_timeout; + + debug_info("receive params: cancel_receive %s, receive_loop_timeout %dms", (client->cancel_receive == NULL ? "unset": "set"), client->receive_loop_timeout); + + return DEBUGSERVER_E_SUCCESS; +} + +static debugserver_error_t debugserver_client_receive_internal_char(debugserver_client_t client, char* received_char) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + uint32_t bytes = 0; + + /* we loop here as we expect an answer */ + res = debugserver_client_receive(client, received_char, sizeof(char), &bytes); + if (res != DEBUGSERVER_E_SUCCESS) { + return res; + } + if (bytes != 1) { + debug_info("received %d bytes when asking for %d!", bytes, sizeof(char)); + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + return res; +} + +debugserver_error_t debugserver_client_receive_response(debugserver_client_t client, char** response, size_t* response_size) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + + char data = '\0'; + int skip_prefix = 0; + + char* buffer = malloc(1024); + uint32_t buffer_size = 0; + uint32_t buffer_capacity = 1024; + + if (response) + *response = NULL; + + if (!client->noack_mode) { + debug_info("attempting to receive ACK (+)"); + res = debugserver_client_receive_internal_char(client, &data); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + if (data == '+') { + debug_info("received ACK (+)"); + } else if (data == '$') { + debug_info("received prefix ($)"); + buffer[0] = '$'; + buffer_size = 1; + skip_prefix = 1; + } else { + debug_info("unrecognized response when looking for ACK: %c", data); + goto cleanup; + } + } + + debug_info("skip_prefix: %d", skip_prefix); + + if (!skip_prefix) { + debug_info("attempting to receive prefix ($)"); + res = debugserver_client_receive_internal_char(client, &data); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + if (data == '$') { + debug_info("received prefix ($)"); + buffer[0] = '$'; + buffer_size = 1; + } else { + debug_info("unrecognized response when looking for prefix: %c", data); + goto cleanup; + } + } + + uint32_t checksum_length = DEBUGSERVER_CHECKSUM_HASH_LENGTH; + int receiving_checksum_response = 0; + debug_info("attempting to read up response until checksum"); + + while ((checksum_length > 0)) { + res = debugserver_client_receive_internal_char(client, &data); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + if (data == '#') { + receiving_checksum_response = 1; + } + if (receiving_checksum_response) { + checksum_length--; + } + if (buffer_size + 1 >= buffer_capacity) { + char* newbuffer = realloc(buffer, buffer_capacity+1024); + if (!newbuffer) { + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + buffer = newbuffer; + buffer_capacity += 1024; + } + buffer[buffer_size] = data; + buffer_size += sizeof(char); + } + debug_info("validating response checksum..."); + if (client->noack_mode || debugserver_response_is_checksum_valid(buffer, buffer_size)) { + if (response) { + /* assemble response string */ + uint32_t resp_size = sizeof(char) * (buffer_size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1); + *response = (char*)malloc(resp_size + 1); + memcpy(*response, buffer + 1, resp_size); + (*response)[resp_size] = '\0'; + if (response_size) *response_size = resp_size; + } + if (!client->noack_mode) { + /* confirm valid command */ + debugserver_client_send_ack(client); + } + } else { + /* response was invalid */ + res = DEBUGSERVER_E_RESPONSE_ERROR; + if (!client->noack_mode) { + /* report invalid command */ + debugserver_client_send_noack(client); + } + } + +cleanup: + if (response) { + debug_info("response: %s", *response); + } + + if (buffer) + free(buffer); + + return res; +} + +debugserver_error_t debugserver_client_send_command(debugserver_client_t client, debugserver_command_t command, char** response, size_t* response_size) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + int i; + uint32_t bytes = 0; + + char* send_buffer = NULL; + uint32_t send_buffer_size = 0; + + char* command_arguments = NULL; + + /* concat all arguments */ + for (i = 0; i < command->argc; i++) { + debug_info("argv[%d]: %s", i, command->argv[i]); + command_arguments = string_append(command_arguments, command->argv[i], NULL); + } + + debug_info("command_arguments(%d): %s", command->argc, command_arguments); + + /* encode command arguments, add checksum if required and assemble entire command */ + debugserver_format_command("$", command->name, command_arguments, 1, &send_buffer, &send_buffer_size); + + debug_info("sending encoded command: %s", send_buffer); + + res = debugserver_client_send(client, send_buffer, send_buffer_size, &bytes); + debug_info("command result: %d", res); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + + /* receive response */ + res = debugserver_client_receive_response(client, response, response_size); + debug_info("response result: %d", res); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + + if (response) { + debug_info("received response: %s", *response); + } + + /* disable sending ack on the client */ + if (!strncmp(command->name, "QStartNoAckMode", 16)) { + debugserver_client_set_ack_mode(client, 0); + } + +cleanup: + if (command_arguments) + free(command_arguments); + + if (send_buffer) + free(send_buffer); + + return res; +} + +debugserver_error_t debugserver_client_set_environment_hex_encoded(debugserver_client_t client, const char* env, char** response) +{ + if (!client || !env) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t result = DEBUGSERVER_E_UNKNOWN_ERROR; + char* env_tmp = strdup(env); + char* env_arg[2] = { env_tmp, NULL }; + + debugserver_command_t command = NULL; + debugserver_command_new("QEnvironmentHexEncoded:", 1, env_arg, &command); + result = debugserver_client_send_command(client, command, response, NULL); + debugserver_command_free(command); + + free(env_tmp); + + return result; +} + +debugserver_error_t debugserver_client_set_argv(debugserver_client_t client, int argc, char* argv[], char** response) +{ + if (!client || !argc) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t result = DEBUGSERVER_E_UNKNOWN_ERROR; + char *pkt = NULL; + size_t pkt_len = 0; + int i = 0; + + /* calculate total length */ + while (i < argc && argv && argv[i]) { + char *prefix = NULL; + int ret = asprintf(&prefix, ",%zu,%d,", strlen(argv[i]) * 2, i); + if (ret < 0 || prefix == NULL) { + debug_info("asprintf failed, out of memory?"); + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + pkt_len += strlen(prefix) + strlen(argv[i]) * 2; + free(prefix); + i++; + } + + /* allocate packet and initialize it */ + pkt = (char *) malloc(pkt_len + 1); + memset(pkt, 0, pkt_len + 1); + + char *pktp = pkt; + + i = 0; + while (i < argc && argv && argv[i]) { + debug_info("argv[%d] = \"%s\"", i, argv[i]); + + char *prefix = NULL; + char *m = NULL; + size_t arg_len = strlen(argv[i]); + size_t arg_hexlen = arg_len * 2; + + int ret = asprintf(&prefix, ",%zu,%d,", arg_hexlen, i); + if (ret < 0 || prefix == NULL) { + debug_info("asprintf failed, out of memory?"); + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + + m = (char *) malloc(arg_hexlen); + char *p = m; + char *q = (char*)argv[i]; + while (*q) { + *p++ = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(*q); + *p++ = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(*q); + q++; + } + + memcpy(pktp, prefix, strlen(prefix)); + pktp += strlen(prefix); + + memcpy(pktp, m, arg_hexlen); + pktp += arg_hexlen; + + free(prefix); + free(m); + + i++; + } + + pkt[0] = 'A'; + + debugserver_command_t command = NULL; + debugserver_command_new(pkt, 0, NULL, &command); + result = debugserver_client_send_command(client, command, response, NULL); + debugserver_command_free(command); + + if (pkt) + free(pkt); + + return result; +} diff --git a/src/debugserver.h b/src/debugserver.h new file mode 100644 index 0000000..ce9c255 --- /dev/null +++ b/src/debugserver.h @@ -0,0 +1,44 @@ +/* + * debugserver.h + * com.apple.debugserver service header file. + * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _DEBUGSERVER_H +#define _DEBUGSERVER_H + +#include "idevice.h" +#include "libimobiledevice/debugserver.h" +#include "service.h" + +#define DEBUGSERVER_CHECKSUM_HASH_LENGTH 0x3 + +struct debugserver_client_private { + service_client_t parent; + int noack_mode; + int (*cancel_receive)(); + int receive_loop_timeout; +}; + +struct debugserver_command_private { + char* name; + int argc; + char** argv; +}; + +#endif diff --git a/src/device_link_service.c b/src/device_link_service.c index 6083d80..66c2461 100644 --- a/src/device_link_service.c +++ b/src/device_link_service.c @@ -1,28 +1,53 @@ - /* +/* * device_link_service.c * DeviceLink service implementation. - * - * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> #include "device_link_service.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" + +static device_link_service_error_t device_link_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return DEVICE_LINK_SERVICE_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return DEVICE_LINK_SERVICE_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return DEVICE_LINK_SERVICE_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return DEVICE_LINK_SERVICE_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return DEVICE_LINK_SERVICE_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT; + default: + break; + } + return DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; +} /** * Internally used function to extract the message string from a DL* message @@ -58,7 +83,7 @@ static int device_link_service_get_message(plist_t dl_msg, char **message) return 0; } - if ((strlen(cmd_str) < 9) || (strncmp(cmd_str, "DL", 2))) { + if ((strlen(cmd_str) < 9) || (strncmp(cmd_str, "DL", 2) != 0)) { free(cmd_str); return 0; } @@ -74,7 +99,7 @@ static int device_link_service_get_message(plist_t dl_msg, char **message) * Creates a new device link service client. * * @param device The device to connect to. - * @param port Port on device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. * @param client Reference that will point to a newly allocated * device_link_service_client_t upon successful return. * @@ -82,15 +107,16 @@ static int device_link_service_get_message(plist_t dl_msg, char **message) * DEVICE_LINK_SERVICE_E_INVALID_ARG when one of the parameters is invalid, * or DEVICE_LINK_SERVICE_E_MUX_ERROR when the connection failed. */ -device_link_service_error_t device_link_service_client_new(idevice_t device, uint16_t port, device_link_service_client_t *client) +device_link_service_error_t device_link_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, device_link_service_client_t *client) { - if (!device || port == 0 || !client || *client) { + if (!device || !service || service->port == 0 || !client || *client) { return DEVICE_LINK_SERVICE_E_INVALID_ARG; } property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; + device_link_service_error_t err = device_link_error(property_list_service_client_new(device, service, &plistclient)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { + return err; } /* create client object */ @@ -117,11 +143,10 @@ device_link_service_error_t device_link_service_client_free(device_link_service_ if (!client) return DEVICE_LINK_SERVICE_E_INVALID_ARG; - if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_client_free(client->parent)); free(client); - return DEVICE_LINK_SERVICE_E_SUCCESS; + + return err; } /** @@ -145,7 +170,7 @@ device_link_service_error_t device_link_service_version_exchange(device_link_ser { if (!client) return DEVICE_LINK_SERVICE_E_INVALID_ARG; - + device_link_service_error_t err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; /* perform version exchange */ @@ -153,13 +178,13 @@ device_link_service_error_t device_link_service_version_exchange(device_link_ser char *msg = NULL; /* receive DLMessageVersionExchange from device */ - if (property_list_service_receive_plist(client->parent, &array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + err = device_link_error(property_list_service_receive_plist(client->parent, &array)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { debug_info("Did not receive initial message from device!"); - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; goto leave; } device_link_service_get_message(array, &msg); - if (!msg || strcmp(msg, "DLMessageVersionExchange")) { + if (!msg || strcmp(msg, "DLMessageVersionExchange") != 0) { debug_info("Did not receive DLMessageVersionExchange from device!"); err = DEVICE_LINK_SERVICE_E_PLIST_ERROR; goto leave; @@ -199,22 +224,22 @@ device_link_service_error_t device_link_service_version_exchange(device_link_ser plist_array_append_item(array, plist_new_string("DLMessageVersionExchange")); plist_array_append_item(array, plist_new_string("DLVersionsOk")); plist_array_append_item(array, plist_new_uint(version_major)); - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { debug_info("Error when sending DLVersionsOk"); - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; goto leave; } plist_free(array); /* receive DeviceReady message */ array = NULL; - if (property_list_service_receive_plist(client->parent, &array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + err = device_link_error(property_list_service_receive_plist(client->parent, &array)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { debug_info("Error when receiving DLMessageDeviceReady!"); - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; goto leave; } device_link_service_get_message(array, &msg); - if (!msg || strcmp(msg, "DLMessageDeviceReady")) { + if (!msg || strcmp(msg, "DLMessageDeviceReady") != 0) { debug_info("Did not get DLMessageDeviceReady!"); err = DEVICE_LINK_SERVICE_E_PLIST_ERROR; goto leave; @@ -235,26 +260,28 @@ leave: * Performs a disconnect with the connected device link service client. * * @param client The device link service client to disconnect. - * + * @param message Optional message to send send to the device or NULL. + * * @return DEVICE_LINK_SERVICE_E_SUCCESS on success, * DEVICE_LINK_SERVICE_E_INVALID_ARG if client is NULL, * or DEVICE_LINK_SERVICE_E_MUX_ERROR when there's an error when sending * the the disconnect message. */ -device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client) +device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client, const char *message) { if (!client) return DEVICE_LINK_SERVICE_E_INVALID_ARG; plist_t array = plist_new_array(); plist_array_append_item(array, plist_new_string("DLMessageDisconnect")); - plist_array_append_item(array, plist_new_string("All done, thanks for the memories")); + if (message) + plist_array_append_item(array, plist_new_string(message)); + else + plist_array_append_item(array, plist_new_string("___EmptyParameterString___")); - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_SUCCESS; - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); plist_free(array); + return err; } @@ -278,11 +305,9 @@ device_link_service_error_t device_link_service_send_ping(device_link_service_cl plist_array_append_item(array, plist_new_string("DLMessagePing")); plist_array_append_item(array, plist_new_string(message)); - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_SUCCESS; - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); plist_free(array); + return err; } @@ -309,11 +334,9 @@ device_link_service_error_t device_link_service_send_process_message(device_link plist_array_append_item(array, plist_new_string("DLMessageProcessMessage")); plist_array_append_item(array, plist_copy(message)); - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_SUCCESS; - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); plist_free(array); + return err; } @@ -340,8 +363,9 @@ device_link_service_error_t device_link_service_receive_message(device_link_serv return DEVICE_LINK_SERVICE_E_INVALID_ARG; *msg_plist = NULL; - if (property_list_service_receive_plist(client->parent, msg_plist) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; + device_link_service_error_t err = device_link_error(property_list_service_receive_plist(client->parent, msg_plist)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { + return err; } if (!device_link_service_get_message(*msg_plist, dlmessage)) { @@ -370,15 +394,16 @@ device_link_service_error_t device_link_service_receive_process_message(device_l return DEVICE_LINK_SERVICE_E_INVALID_ARG; plist_t pmsg = NULL; - if (property_list_service_receive_plist(client->parent, &pmsg) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; + device_link_service_error_t err = device_link_error(property_list_service_receive_plist(client->parent, &pmsg)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { + return err; } - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; + err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; char *msg = NULL; device_link_service_get_message(pmsg, &msg); - if (!msg || strcmp(msg, "DLMessageProcessMessage")) { + if (!msg || strcmp(msg, "DLMessageProcessMessage") != 0) { debug_info("Did not receive DLMessageProcessMessage as expected!"); err = DEVICE_LINK_SERVICE_E_PLIST_ERROR; goto leave; @@ -424,10 +449,7 @@ device_link_service_error_t device_link_service_send(device_link_service_client_ if (!client || !plist) { return DEVICE_LINK_SERVICE_E_INVALID_ARG; } - if (property_list_service_send_binary_plist(client->parent, plist) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; - } - return DEVICE_LINK_SERVICE_E_SUCCESS; + return device_link_error(property_list_service_send_binary_plist(client->parent, plist)); } /* Generic device link service receive function. @@ -447,9 +469,6 @@ device_link_service_error_t device_link_service_receive(device_link_service_clie return DEVICE_LINK_SERVICE_E_INVALID_ARG; } - if (property_list_service_receive_plist(client->parent, plist) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; - } - return DEVICE_LINK_SERVICE_E_SUCCESS; + return device_link_error(property_list_service_receive_plist(client->parent, plist)); } diff --git a/src/device_link_service.h b/src/device_link_service.h index 9953f77..0255b21 100644 --- a/src/device_link_service.h +++ b/src/device_link_service.h @@ -1,39 +1,41 @@ - /* +/* * device_link_service.h * Definitions for the DeviceLink service - * - * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef DEVICE_LINK_SERVICE_H -#define DEVICE_LINK_SERVICE_H +#ifndef __DEVICE_LINK_SERVICE_H +#define __DEVICE_LINK_SERVICE_H + +#include "idevice.h" #include "property_list_service.h" /* Error Codes */ -#define DEVICE_LINK_SERVICE_E_SUCCESS 0 -#define DEVICE_LINK_SERVICE_E_INVALID_ARG -1 -#define DEVICE_LINK_SERVICE_E_PLIST_ERROR -2 -#define DEVICE_LINK_SERVICE_E_MUX_ERROR -3 -#define DEVICE_LINK_SERVICE_E_BAD_VERSION -4 - -#define DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR -256 - -/** Represents an error code. */ -typedef int16_t device_link_service_error_t; +typedef enum { + DEVICE_LINK_SERVICE_E_SUCCESS = 0, + DEVICE_LINK_SERVICE_E_INVALID_ARG = -1, + DEVICE_LINK_SERVICE_E_PLIST_ERROR = -2, + DEVICE_LINK_SERVICE_E_MUX_ERROR = -3, + DEVICE_LINK_SERVICE_E_SSL_ERROR = -4, + DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT = -5, + DEVICE_LINK_SERVICE_E_BAD_VERSION = -6, + DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR = -256 +} device_link_service_error_t; struct device_link_service_client_private { property_list_service_client_t parent; @@ -41,14 +43,14 @@ struct device_link_service_client_private { typedef struct device_link_service_client_private *device_link_service_client_t; -device_link_service_error_t device_link_service_client_new(idevice_t device, uint16_t port, device_link_service_client_t *client); +device_link_service_error_t device_link_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, device_link_service_client_t *client); device_link_service_error_t device_link_service_client_free(device_link_service_client_t client); device_link_service_error_t device_link_service_version_exchange(device_link_service_client_t client, uint64_t version_major, uint64_t version_minor); device_link_service_error_t device_link_service_send_ping(device_link_service_client_t client, const char *message); device_link_service_error_t device_link_service_receive_message(device_link_service_client_t client, plist_t *msg_plist, char **dlmessage); device_link_service_error_t device_link_service_send_process_message(device_link_service_client_t client, plist_t message); device_link_service_error_t device_link_service_receive_process_message(device_link_service_client_t client, plist_t *message); -device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client); +device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client, const char *message); device_link_service_error_t device_link_service_send(device_link_service_client_t client, plist_t plist); device_link_service_error_t device_link_service_receive(device_link_service_client_t client, plist_t *plist); diff --git a/src/diagnostics_relay.c b/src/diagnostics_relay.c new file mode 100644 index 0000000..6ee3150 --- /dev/null +++ b/src/diagnostics_relay.c @@ -0,0 +1,467 @@ +/* + * diagnostics_relay.c + * com.apple.mobile.diagnostics_relay service implementation. + * + * Copyright (c) 2012 Martin Szulecki, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include "diagnostics_relay.h" +#include "property_list_service.h" +#include "common/debug.h" + +#define RESULT_SUCCESS 0 +#define RESULT_FAILURE 1 +#define RESULT_UNKNOWN_REQUEST 2 + +/** + * Internally used function for checking the result from a service response + * plist to a previously sent request. + * + * @param dict The plist to evaluate. + * + * @return RESULT_SUCCESS when the result is 'Success', + * RESULT_FAILURE when the result is 'Failure', + * or a negative value if an error occurred during evaluation. + */ +static int diagnostics_relay_check_result(plist_t dict) +{ + int ret = -1; + + plist_t result_node = plist_dict_get_item(dict, "Status"); + if (!result_node) + return ret; + + plist_type result_type = plist_get_node_type(result_node); + if (result_type == PLIST_STRING) { + char *result_value = NULL; + + plist_get_string_val(result_node, &result_value); + + if (result_value) { + if (!strcmp(result_value, "Success")) { + ret = RESULT_SUCCESS; + } else if (!strcmp(result_value, "Failure")) { + ret = RESULT_FAILURE; + } else if (!strcmp(result_value, "UnknownRequest")) { + ret = RESULT_UNKNOWN_REQUEST; + } else { + debug_info("ERROR: unknown result value '%s'", result_value); + } + } + if (result_value) + free(result_value); + } + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, diagnostics_relay_client_t *client) +{ + if (!device || !service || service->port == 0 || !client || *client) { + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + } + + property_list_service_client_t plistclient = NULL; + if (property_list_service_client_new(device, service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return DIAGNOSTICS_RELAY_E_MUX_ERROR; + } + + /* create client object */ + diagnostics_relay_client_t client_loc = (diagnostics_relay_client_t) malloc(sizeof(struct diagnostics_relay_client_private)); + client_loc->parent = plistclient; + + /* all done, return success */ + *client = client_loc; + return DIAGNOSTICS_RELAY_E_SUCCESS; +} + +diagnostics_relay_error_t diagnostics_relay_client_start_service(idevice_t device, diagnostics_relay_client_t * client, const char* label) +{ + diagnostics_relay_error_t err = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, DIAGNOSTICS_RELAY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(diagnostics_relay_client_new), &err); + return err; +} + +diagnostics_relay_error_t diagnostics_relay_client_free(diagnostics_relay_client_t client) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + free(client); + return DIAGNOSTICS_RELAY_E_SUCCESS; +} + +/** + * Receives a plist from the service. + * + * @param client The diagnostics_relay client + * @param plist The plist to store the received data + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client or plist is NULL + */ +static diagnostics_relay_error_t diagnostics_relay_receive(diagnostics_relay_client_t client, plist_t *plist) +{ + if (!client || !plist || (plist && *plist)) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_SUCCESS; + property_list_service_error_t err; + + err = property_list_service_receive_plist(client->parent, plist); + if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (!*plist) + ret = DIAGNOSTICS_RELAY_E_PLIST_ERROR; + + return ret; +} + +/** + * Sends a plist to the service. + * + * @note This function is low-level and should only be used if you need to send + * a new type of message. + * + * @param client The diagnostics_relay client + * @param plist The plist to send + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client or plist is NULL + */ +static diagnostics_relay_error_t diagnostics_relay_send(diagnostics_relay_client_t client, plist_t plist) +{ + if (!client || !plist) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_SUCCESS; + property_list_service_error_t err; + + err = property_list_service_send_xml_plist(client->parent, plist); + if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_goodbye(diagnostics_relay_client_t client) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Request", plist_new_string("Goodbye")); + + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + debug_info("did not get goodbye response back"); + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + plist_free(dict); + dict = NULL; + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_sleep(diagnostics_relay_client_t client) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + + plist_dict_set_item(dict,"Request", plist_new_string("Sleep")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + plist_free(dict); + return ret; +} + +static diagnostics_relay_error_t internal_diagnostics_relay_action(diagnostics_relay_client_t client, const char* name, diagnostics_relay_action_t flags) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"Request", plist_new_string(name)); + + if (flags & DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) { + plist_dict_set_item(dict, "WaitForDisconnect", plist_new_bool(1)); + } + + if (flags & DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_PASS) { + plist_dict_set_item(dict, "DisplayPass", plist_new_bool(1)); + } + + if (flags & DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_FAIL) { + plist_dict_set_item(dict, "DisplayFail", plist_new_bool(1)); + } + + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_restart(diagnostics_relay_client_t client, diagnostics_relay_action_t flags) +{ + return internal_diagnostics_relay_action(client, "Restart", flags); +} + +diagnostics_relay_error_t diagnostics_relay_shutdown(diagnostics_relay_client_t client, diagnostics_relay_action_t flags) +{ + return internal_diagnostics_relay_action(client, "Shutdown", flags); +} + +diagnostics_relay_error_t diagnostics_relay_request_diagnostics(diagnostics_relay_client_t client, const char* type, plist_t* diagnostics) +{ + if (!client || diagnostics == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"Request", plist_new_string(type)); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + return ret; + } + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *diagnostics = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_query_mobilegestalt(diagnostics_relay_client_t client, plist_t keys, plist_t* result) +{ + if (!client || plist_get_node_type(keys) != PLIST_ARRAY || result == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"MobileGestaltKeys", plist_copy(keys)); + plist_dict_set_item(dict,"Request", plist_new_string("MobileGestalt")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + return ret; + } + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *result = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_query_ioregistry_entry(diagnostics_relay_client_t client, const char* entry_name, const char* entry_class, plist_t* result) +{ + if (!client || (entry_name == NULL && entry_class == NULL) || result == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + if (entry_name) + plist_dict_set_item(dict,"EntryName", plist_new_string(entry_name)); + if (entry_class) + plist_dict_set_item(dict,"EntryClass", plist_new_string(entry_class)); + plist_dict_set_item(dict,"Request", plist_new_string("IORegistry")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + return ret; + } + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *result = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_query_ioregistry_plane(diagnostics_relay_client_t client, const char* plane, plist_t* result) +{ + if (!client || plane == NULL || result == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"CurrentPlane", plist_new_string(plane)); + plist_dict_set_item(dict,"Request", plist_new_string("IORegistry")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *result = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} diff --git a/src/diagnostics_relay.h b/src/diagnostics_relay.h new file mode 100644 index 0000000..3bb543a --- /dev/null +++ b/src/diagnostics_relay.h @@ -0,0 +1,33 @@ +/* + * diagnostics_relay.h + * com.apple.mobile.diagnostics_relay service header file. + * + * Copyright (c) 2012 Martin Szulecki, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __DIAGNOSTICS_RELAY_H +#define __DIAGNOSTICS_RELAY_H + +#include "idevice.h" +#include "libimobiledevice/diagnostics_relay.h" +#include "property_list_service.h" + +struct diagnostics_relay_client_private { + property_list_service_client_t parent; +}; + +#endif diff --git a/src/file_relay.c b/src/file_relay.c index 680e28d..fbe7cbf 100644 --- a/src/file_relay.c +++ b/src/file_relay.c @@ -1,49 +1,41 @@ - /* +/* * file_relay.c * com.apple.mobile.file_relay service implementation. - * + * * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> #include "file_relay.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" -/** - * Connects to the file_relay service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Reference that will point to a newly allocated - * file_relay_client_t upon successful return. - * - * @return FILE_RELAY_E_SUCCESS on success, - * FILE_RELAY_E_INVALID_ARG when one of the parameters is invalid, - * or FILE_RELAY_E_MUX_ERROR when the connection failed. - */ -file_relay_error_t file_relay_client_new(idevice_t device, uint16_t port, file_relay_client_t *client) +file_relay_error_t file_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, file_relay_client_t *client) { - if (!device || port == 0 || !client || *client) { + if (!device || !service || service->port == 0 || !client || *client) { return FILE_RELAY_E_INVALID_ARG; } property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + if (property_list_service_client_new(device, service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { return FILE_RELAY_E_MUX_ERROR; } @@ -56,17 +48,13 @@ file_relay_error_t file_relay_client_new(idevice_t device, uint16_t port, file_r return FILE_RELAY_E_SUCCESS; } -/** - * Disconnects a file_relay client from the device and frees up the file_relay - * client data. - * - * @param client The file_relay client to disconnect and free. - * - * @return FILE_RELAY_E_SUCCESS on success, - * FILE_RELAY_E_INVALID_ARG when one of client or client->parent - * is invalid, or FILE_RELAY_E_UNKNOWN_ERROR when the was an error - * freeing the parent property_list_service client. - */ +file_relay_error_t file_relay_client_start_service(idevice_t device, file_relay_client_t * client, const char* label) +{ + file_relay_error_t err = FILE_RELAY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, FILE_RELAY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(file_relay_client_new), &err); + return err; +} + file_relay_error_t file_relay_client_free(file_relay_client_t client) { if (!client) @@ -75,40 +63,11 @@ file_relay_error_t file_relay_client_free(file_relay_client_t client) if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { return FILE_RELAY_E_UNKNOWN_ERROR; } + free(client); return FILE_RELAY_E_SUCCESS; } -/** - * Request data for the given sources. - * - * @param client The connected file_relay client. - * @param sources A NULL-terminated list of sources to retrieve. - * Valid sources are: - * - AppleSupport - * - Network - * - VPN - * - WiFi - * - UserDatabases - * - CrashReporter - * - tmp - * - SystemConfiguration - * @param connection The connection that has to be used for receiving the - * data using idevice_connection_receive(). The connection will be closed - * automatically by the device, but use file_relay_client_free() to clean - * up properly. - * - * @note WARNING: Don't call this function without reading the data afterwards. - * A directory mobile_file_relay.XXXX used for creating the archive will - * remain in the /tmp directory otherwise. - * - * @return FILE_RELAY_E_SUCCESS on succes, FILE_RELAY_E_INVALID_ARG when one or - * more parameters are invalid, FILE_RELAY_E_MUX_ERROR if a communication - * error occurs, FILE_RELAY_E_PLIST_ERROR when the received result is NULL - * or is not a valid plist, FILE_RELAY_E_INVALID_SOURCE if one or more - * sources are invalid, FILE_RELAY_E_STAGING_EMPTY if no data is available - * for the given sources, or FILE_RELAY_E_UNKNOWN_ERROR otherwise. - */ -file_relay_error_t file_relay_request_sources(file_relay_client_t client, const char **sources, idevice_connection_t *connection) +file_relay_error_t file_relay_request_sources_timeout(file_relay_client_t client, const char **sources, idevice_connection_t *connection, unsigned int timeout) { if (!client || !client->parent || !sources || !sources[0]) { return FILE_RELAY_E_INVALID_ARG; @@ -121,9 +80,9 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const while (sources[i]) { plist_array_append_item(array, plist_new_string(sources[i])); i++; - } + } plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Sources", array); + plist_dict_set_item(dict, "Sources", array); if (property_list_service_send_xml_plist(client->parent, dict) != PROPERTY_LIST_SERVICE_E_SUCCESS) { debug_info("ERROR: Could not send request to device!"); @@ -133,7 +92,7 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const plist_free(dict); dict = NULL; - if (property_list_service_receive_plist_with_timeout(client->parent, &dict, 60000) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + if (property_list_service_receive_plist_with_timeout(client->parent, &dict, timeout) != PROPERTY_LIST_SERVICE_E_SUCCESS) { debug_info("ERROR: Could not receive answer from device!"); err = FILE_RELAY_E_MUX_ERROR; goto leave; @@ -156,6 +115,9 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const } else if (!strcmp(errmsg, "StagingEmpty")) { debug_info("ERROR: StagingEmpty - No data available!"); err = FILE_RELAY_E_STAGING_EMPTY; + } else if (!strcmp(errmsg, "PermissionDenied")) { + debug_info("ERROR: Permission denied."); + err = FILE_RELAY_E_PERMISSION_DENIED; } else { debug_info("ERROR: Unknown error '%s'", errmsg); } @@ -181,14 +143,14 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const goto leave; } - if (strcmp(ack, "Acknowledged")) { + if (strcmp(ack, "Acknowledged") != 0) { debug_info("ERROR: Did not receive 'Acknowledged' but '%s'", ack); goto leave; } free(ack); err = FILE_RELAY_E_SUCCESS; - *connection = client->parent->connection; + *connection = client->parent->parent->connection; leave: if (dict) { @@ -196,3 +158,8 @@ leave: } return err; } + +file_relay_error_t file_relay_request_sources(file_relay_client_t client, const char **sources, idevice_connection_t *connection) +{ + return file_relay_request_sources_timeout(client, sources, connection, 60000); +} diff --git a/src/file_relay.h b/src/file_relay.h index 60cc32f..65bf460 100644 --- a/src/file_relay.h +++ b/src/file_relay.h @@ -1,38 +1,31 @@ - /* +/* * file_relay.h * com.apple.mobile.file_relay service header file. - * + * * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef FILE_RELAY_H -#define FILE_RELAY_H +#ifndef __FILE_RELAY_H +#define __FILE_RELAY_H + +#include "idevice.h" #include "libimobiledevice/file_relay.h" #include "property_list_service.h" -/* Error Codes */ -#define FILE_RELAY_E_SUCCESS 0 -#define FILE_RELAY_E_INVALID_ARG -1 -#define FILE_RELAY_E_PLIST_ERROR -2 -#define FILE_RELAY_E_MUX_ERROR -3 - -#define FILE_RELAY_E_UNKNOWN_ERROR -256 - - struct file_relay_client_private { property_list_service_client_t parent; }; diff --git a/src/heartbeat.c b/src/heartbeat.c new file mode 100644 index 0000000..3945d73 --- /dev/null +++ b/src/heartbeat.c @@ -0,0 +1,147 @@ +/* + * heartbeat.c + * com.apple.mobile.heartbeat service implementation. + * + * Copyright (c) 2013 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <plist/plist.h> + +#include "heartbeat.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a heartbeat_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching heartbeat_error_t error code, + * HEARTBEAT_E_UNKNOWN_ERROR otherwise. + */ +static heartbeat_error_t heartbeat_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return HEARTBEAT_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return HEARTBEAT_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return HEARTBEAT_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return HEARTBEAT_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return HEARTBEAT_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return HEARTBEAT_E_NOT_ENOUGH_DATA; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return HEARTBEAT_E_TIMEOUT; + default: + break; + } + return HEARTBEAT_E_UNKNOWN_ERROR; +} + +heartbeat_error_t heartbeat_client_new(idevice_t device, lockdownd_service_descriptor_t service, heartbeat_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to heartbeat_client_new."); + return HEARTBEAT_E_INVALID_ARG; + } + + debug_info("Creating heartbeat_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + heartbeat_error_t ret = heartbeat_error(property_list_service_client_new(device, service, &plclient)); + if (ret != HEARTBEAT_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + heartbeat_client_t client_loc = (heartbeat_client_t) malloc(sizeof(struct heartbeat_client_private)); + client_loc->parent = plclient; + + *client = client_loc; + + debug_info("heartbeat_client successfully created."); + return 0; +} + +heartbeat_error_t heartbeat_client_start_service(idevice_t device, heartbeat_client_t * client, const char* label) +{ + heartbeat_error_t err = HEARTBEAT_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, HEARTBEAT_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(heartbeat_client_new), &err); + return err; +} + +heartbeat_error_t heartbeat_client_free(heartbeat_client_t client) +{ + if (!client) + return HEARTBEAT_E_INVALID_ARG; + + heartbeat_error_t err = heartbeat_error(property_list_service_client_free(client->parent)); + free(client); + + return err; +} + +heartbeat_error_t heartbeat_send(heartbeat_client_t client, plist_t plist) +{ + heartbeat_error_t res = HEARTBEAT_E_UNKNOWN_ERROR; + + res = heartbeat_error(property_list_service_send_binary_plist(client->parent, plist)); + if (res != HEARTBEAT_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + + debug_plist(plist); + + return res; +} + +heartbeat_error_t heartbeat_receive(heartbeat_client_t client, plist_t * plist) +{ + return heartbeat_receive_with_timeout(client, plist, 1000); +} + +heartbeat_error_t heartbeat_receive_with_timeout(heartbeat_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + heartbeat_error_t res = HEARTBEAT_E_UNKNOWN_ERROR; + plist_t outplist = NULL; + + res = heartbeat_error(property_list_service_receive_plist_with_timeout(client->parent, &outplist, timeout_ms)); + if (res != HEARTBEAT_E_SUCCESS || !outplist) { + debug_info("Could not receive plist, error %d", res); + plist_free(outplist); + return HEARTBEAT_E_MUX_ERROR; + } + + *plist = outplist; + + debug_plist(*plist); + + return res; +} diff --git a/src/heartbeat.h b/src/heartbeat.h new file mode 100644 index 0000000..379ecc1 --- /dev/null +++ b/src/heartbeat.h @@ -0,0 +1,33 @@ +/* + * heartbeat.h + * com.apple.mobile.heartbeat service header file. + * + * Copyright (c) 2013 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __HEARTBEAT_H +#define __HEARTBEAT_H + +#include "idevice.h" +#include "libimobiledevice/heartbeat.h" +#include "property_list_service.h" + +struct heartbeat_client_private { + property_list_service_client_t parent; +}; + +#endif diff --git a/src/house_arrest.c b/src/house_arrest.c index 5baa76e..caad731 100644 --- a/src/house_arrest.c +++ b/src/house_arrest.c @@ -8,17 +8,20 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> #include <unistd.h> @@ -27,7 +30,7 @@ #include "house_arrest.h" #include "property_list_service.h" #include "afc.h" -#include "debug.h" +#include "common/debug.h" /** * Convert a property_list_service_error_t value to a house_arrest_error_t @@ -40,39 +43,25 @@ */ static house_arrest_error_t house_arrest_error(property_list_service_error_t err) { - switch (err) { - case PROPERTY_LIST_SERVICE_E_SUCCESS: - return HOUSE_ARREST_E_SUCCESS; - case PROPERTY_LIST_SERVICE_E_INVALID_ARG: - return HOUSE_ARREST_E_INVALID_ARG; - case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: - return HOUSE_ARREST_E_PLIST_ERROR; - case PROPERTY_LIST_SERVICE_E_MUX_ERROR: - return HOUSE_ARREST_E_CONN_FAILED; - default: - break; - } - return HOUSE_ARREST_E_UNKNOWN_ERROR; + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return HOUSE_ARREST_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return HOUSE_ARREST_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return HOUSE_ARREST_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return HOUSE_ARREST_E_CONN_FAILED; + default: + break; + } + return HOUSE_ARREST_E_UNKNOWN_ERROR; } -/** - * Connects to the house_arrest service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will point to a newly allocated - * housearrest_client_t upon successful return. - * - * @return HOUSE_ARREST_E_SUCCESS on success, HOUSE_ARREST_E_INVALID_ARG when - * client is NULL, or an HOUSE_ARREST_E_* error code otherwise. - */ -house_arrest_error_t house_arrest_client_new(idevice_t device, uint16_t port, house_arrest_client_t *client) +house_arrest_error_t house_arrest_client_new(idevice_t device, lockdownd_service_descriptor_t service, house_arrest_client_t *client) { - if (!device) - return HOUSE_ARREST_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - house_arrest_error_t err = house_arrest_error(property_list_service_client_new(device, port, &plistclient)); + house_arrest_error_t err = house_arrest_error(property_list_service_client_new(device, service, &plistclient)); if (err != HOUSE_ARREST_E_SUCCESS) { return err; } @@ -85,27 +74,20 @@ house_arrest_error_t house_arrest_client_new(idevice_t device, uint16_t port, ho return HOUSE_ARREST_E_SUCCESS; } -/** - * Disconnects an house_arrest client from the device and frees up the - * house_arrest client data. - * - * @note After using afc_client_new_from_house_arrest_client(), make sure - * you call afc_client_free() before calling this function to ensure - * a proper cleanup. Do not call this function if you still need to - * perform AFC operations since it will close the connection. - * - * @param client The house_arrest client to disconnect and free. - * - * @return HOUSE_ARREST_E_SUCCESS on success, HOUSE_ARREST_E_INVALID_ARG when - * client is NULL, or an HOUSE_ARREST_E_* error code otherwise. - */ +house_arrest_error_t house_arrest_client_start_service(idevice_t device, house_arrest_client_t * client, const char* label) +{ + house_arrest_error_t err = HOUSE_ARREST_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, HOUSE_ARREST_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(house_arrest_client_new), &err); + return err; +} + house_arrest_error_t house_arrest_client_free(house_arrest_client_t client) { if (!client) return HOUSE_ARREST_E_INVALID_ARG; house_arrest_error_t err = HOUSE_ARREST_E_SUCCESS; - if (client->parent && client->parent->connection) { + if (client->parent && client->parent->parent->connection) { house_arrest_error(property_list_service_client_free(client->parent)); } client->parent = NULL; @@ -114,70 +96,34 @@ house_arrest_error_t house_arrest_client_free(house_arrest_client_t client) return err; } -/** - * Sends a generic request to the connected house_arrest service. - * - * @param client The house_arrest client to use. - * @param dict The request to send as a plist of type PLIST_DICT. - * - * @note If this function returns HOUSE_ARREST_E_SUCCESS it does not mean - * that the request was successful. To check for success or failure you - * need to call house_arrest_get_result(). - * @see house_arrest_get_result - * - * @return HOUSE_ARREST_E_SUCCESS if the request was successfully sent, - * HOUSE_ARREST_E_INVALID_ARG if client or dict is invalid, - * HOUSE_ARREST_E_PLIST_ERROR if dict is not a plist of type PLIST_DICT, - * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, - * or HOUSE_ARREST_E_CONN_FAILED if a connection error occured. - */ house_arrest_error_t house_arrest_send_request(house_arrest_client_t client, plist_t dict) { if (!client || !client->parent || !dict) - return HOUSE_ARREST_E_INVALID_ARG; + return HOUSE_ARREST_E_INVALID_ARG; if (plist_get_node_type(dict) != PLIST_DICT) return HOUSE_ARREST_E_PLIST_ERROR; if (client->mode != HOUSE_ARREST_CLIENT_MODE_NORMAL) return HOUSE_ARREST_E_INVALID_MODE; house_arrest_error_t res = house_arrest_error(property_list_service_send_xml_plist(client->parent, dict)); - if (res != HOUSE_ARREST_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - } + if (res != HOUSE_ARREST_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + } return res; } -/** - * Send a command to the connected house_arrest service. - * Calls house_arrest_send_request() internally. - * - * @param client The house_arrest client to use. - * @param command The command to send. Currently, only VendContainer and - * VendDocuments are known. - * @param appid The application identifier to pass along with the . - * - * @note If this function returns HOUSE_ARREST_E_SUCCESS it does not mean - * that the command was successful. To check for success or failure you - * need to call house_arrest_get_result(). - * @see house_arrest_get_result - * - * @return HOUSE_ARREST_E_SUCCESS if the command was successfully sent, - * HOUSE_ARREST_E_INVALID_ARG if client, command, or appid is invalid, - * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, - * or HOUSE_ARREST_E_CONN_FAILED if a connection error occured. - */ house_arrest_error_t house_arrest_send_command(house_arrest_client_t client, const char *command, const char *appid) { if (!client || !client->parent || !command || !appid) - return HOUSE_ARREST_E_INVALID_ARG; + return HOUSE_ARREST_E_INVALID_ARG; if (client->mode != HOUSE_ARREST_CLIENT_MODE_NORMAL) return HOUSE_ARREST_E_INVALID_MODE; house_arrest_error_t res = HOUSE_ARREST_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Command", plist_new_string(command)); - plist_dict_insert_item(dict, "Identifier", plist_new_string(appid)); + plist_dict_set_item(dict, "Command", plist_new_string(command)); + plist_dict_set_item(dict, "Identifier", plist_new_string(appid)); res = house_arrest_send_request(client, dict); @@ -186,63 +132,30 @@ house_arrest_error_t house_arrest_send_command(house_arrest_client_t client, con return res; } -/** - * Retrieves the result of a previously sent house_arrest_request_* request. - * - * @param client The house_arrest client to use - * @param dict Pointer that will be set to a plist containing the result to - * the last performed operation. It holds a key 'Status' with the value - * 'Complete' on success or a key 'Error' with an error description as - * value. The caller is responsible for freeing the returned plist. - * - * @return HOUSE_ARREST_E_SUCCESS if a result plist was retrieved, - * HOUSE_ARREST_E_INVALID_ARG if client is invalid, - * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, - * or HOUSE_ARREST_E_CONN_FAILED if a connection error occured. - */ house_arrest_error_t house_arrest_get_result(house_arrest_client_t client, plist_t *dict) { if (!client || !client->parent) - return HOUSE_ARREST_E_INVALID_ARG; + return HOUSE_ARREST_E_INVALID_ARG; if (client->mode != HOUSE_ARREST_CLIENT_MODE_NORMAL) return HOUSE_ARREST_E_INVALID_MODE; house_arrest_error_t res = house_arrest_error(property_list_service_receive_plist(client->parent, dict)); - if (res != HOUSE_ARREST_E_SUCCESS) { - debug_info("could not get result, error %d", res); - if (*dict) { - plist_free(*dict); - *dict = NULL; - } - } + if (res != HOUSE_ARREST_E_SUCCESS) { + debug_info("could not get result, error %d", res); + if (*dict) { + plist_free(*dict); + *dict = NULL; + } + } return res; } -/** - * Creates an AFC client using the given house_arrest client's connection - * allowing file access to a specific application directory requested by - * functions like house_arrest_request_vendor_documents(). - * - * @param client The house_arrest client to use. - * @param afc_client Pointer that will be set to a newly allocated afc_client_t - * upon successful return. - * - * @note After calling this function the house_arrest client will go in an - * AFC mode that will only allow calling house_arrest_client_free(). - * Only call house_arrest_client_free() if all AFC operations have - * completed since it will close the connection. - * - * @return AFC_E_SUCCESS if the afc client was successfully created, - * AFC_E_INVALID_ARG if client is invalid or was already used to create - * an afc client, or an AFC_E_* error code returned by - * afc_client_new_from_connection(). - */ afc_error_t afc_client_new_from_house_arrest_client(house_arrest_client_t client, afc_client_t *afc_client) { if (!client || !client->parent || (client->mode == HOUSE_ARREST_CLIENT_MODE_AFC)) { return AFC_E_INVALID_ARG; } - afc_error_t err = afc_client_new_from_connection(client->parent->connection, afc_client); + afc_error_t err = afc_client_new_with_service_client(client->parent->parent, afc_client); if (err == AFC_E_SUCCESS) { client->mode = HOUSE_ARREST_CLIENT_MODE_AFC; } diff --git a/src/house_arrest.h b/src/house_arrest.h index 6d13a88..5612a29 100644 --- a/src/house_arrest.h +++ b/src/house_arrest.h @@ -8,21 +8,21 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IHOUSE_ARREST_H -#define IHOUSE_ARREST_H -#include <glib.h> +#ifndef __HOUSE_ARREST_H +#define __HOUSE_ARREST_H +#include "idevice.h" #include "libimobiledevice/house_arrest.h" #include "property_list_service.h" diff --git a/src/idevice.c b/src/idevice.c index 5a9d49b..b9bbb1f 100644 --- a/src/idevice.c +++ b/src/idevice.c @@ -1,98 +1,387 @@ -/* +/* * idevice.c * Device discovery and communication interface. * + * Copyright (c) 2009-2021 Nikias Bassen. All Rights Reserved. + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. - * Copyright (c) 2009 Nikias Bassen. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #include <stdlib.h> #include <string.h> #include <errno.h> +#include <time.h> + +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif #include <usbmuxd.h> + +#if defined(HAVE_OPENSSL) +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/ssl.h> +#elif defined(HAVE_GNUTLS) #include <gnutls/gnutls.h> +#elif defined(HAVE_MBEDTLS) +#include <mbedtls/rsa.h> +#include <mbedtls/ssl.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/debug.h> +#else +#error No supported TLS/SSL library enabled +#endif + +#include <libimobiledevice-glue/socket.h> +#include <libimobiledevice-glue/thread.h> + #include "idevice.h" -#include "userpref.h" -#include "debug.h" +#include "lockdown.h" +#include "common/userpref.h" +#include "common/debug.h" + +#ifndef ECONNREFUSED +#define ECONNREFUSED 107 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 138 +#endif + + +#ifdef HAVE_OPENSSL + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x20020000L)) +#define TLS_method TLSv1_method +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER) +static void SSL_COMP_free_compression_methods(void) +{ + sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); +} +#endif + +static void openssl_remove_thread_state(void) +{ +/* ERR_remove_thread_state() is available since OpenSSL 1.0.0-beta1, but + * deprecated in OpenSSL 1.1.0 */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10000001L + ERR_remove_thread_state(NULL); +#else + ERR_remove_state(0); +#endif +#endif +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +static mutex_t *mutex_buf = NULL; +static void locking_function(int mode, int n, const char* file, int line) +{ + if (mode & CRYPTO_LOCK) + mutex_lock(&mutex_buf[n]); + else + mutex_unlock(&mutex_buf[n]); +} + +#if OPENSSL_VERSION_NUMBER < 0x10000000L +static unsigned long id_function(void) +{ + return ((unsigned long)THREAD_ID); +} +#else +static void id_function(CRYPTO_THREADID *thread) +{ + CRYPTO_THREADID_set_numeric(thread, (unsigned long)THREAD_ID); +} +#endif +#endif +#endif /* HAVE_OPENSSL */ + +static void internal_idevice_init(void) +{ +#if defined(HAVE_OPENSSL) +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + int i; + SSL_library_init(); + + mutex_buf = malloc(CRYPTO_num_locks() * sizeof(mutex_t)); + if (!mutex_buf) + return; + for (i = 0; i < CRYPTO_num_locks(); i++) + mutex_init(&mutex_buf[i]); + +#if OPENSSL_VERSION_NUMBER < 0x10000000L + CRYPTO_set_id_callback(id_function); +#else + CRYPTO_THREADID_set_callback(id_function); +#endif + CRYPTO_set_locking_callback(locking_function); +#endif +#elif defined(HAVE_GNUTLS) + gnutls_global_init(); +#elif defined(HAVE_MBEDTLS) + // NO-OP +#endif +} + +static void internal_idevice_deinit(void) +{ +#if defined(HAVE_OPENSSL) +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + int i; + if (mutex_buf) { +#if OPENSSL_VERSION_NUMBER < 0x10000000L + CRYPTO_set_id_callback(NULL); +#else + CRYPTO_THREADID_set_callback(NULL); +#endif + CRYPTO_set_locking_callback(NULL); + for (i = 0; i < CRYPTO_num_locks(); i++) + mutex_destroy(&mutex_buf[i]); + free(mutex_buf); + mutex_buf = NULL; + } + + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + SSL_COMP_free_compression_methods(); + openssl_remove_thread_state(); +#endif +#elif defined(HAVE_GNUTLS) + gnutls_global_deinit(); +#elif defined(HAVE_MBEDTLS) + // NO-OP +#endif +} + +static thread_once_t init_once = THREAD_ONCE_INIT; +static thread_once_t deinit_once = THREAD_ONCE_INIT; + +#ifndef HAVE_ATTRIBUTE_CONSTRUCTOR + #if defined(__llvm__) || defined(__GNUC__) + #define HAVE_ATTRIBUTE_CONSTRUCTOR + #endif +#endif + +#ifdef HAVE_ATTRIBUTE_CONSTRUCTOR +static void __attribute__((constructor)) libimobiledevice_initialize(void) +{ + thread_once(&init_once, internal_idevice_init); +} + +static void __attribute__((destructor)) libimobiledevice_deinitialize(void) +{ + thread_once(&deinit_once, internal_idevice_deinit); +} +#elif defined(WIN32) +BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) { + case DLL_PROCESS_ATTACH: + thread_once(&init_once, internal_idevice_init); + break; + case DLL_PROCESS_DETACH: + thread_once(&deinit_once, internal_idevice_deinit); + break; + default: + break; + } + return 1; +} +#else +#warning No compiler support for constructor/destructor attributes, some features might not be available. +#endif + +const char* libimobiledevice_version() +{ +#ifndef PACKAGE_VERSION +#error PACKAGE_VERSION is not defined! +#endif + return PACKAGE_VERSION; +} + +struct idevice_subscription_context { + idevice_event_cb_t callback; + void *user_data; + usbmuxd_subscription_context_t ctx; +}; -static idevice_event_cb_t event_cb = NULL; +static idevice_subscription_context_t event_ctx = NULL; static void usbmux_event_cb(const usbmuxd_event_t *event, void *user_data) { + idevice_subscription_context_t context = (idevice_subscription_context_t)user_data; idevice_event_t ev; ev.event = event->event; - ev.uuid = event->device.uuid; - ev.conn_type = CONNECTION_USBMUXD; + ev.udid = event->device.udid; + ev.conn_type = 0; + if (event->device.conn_type == CONNECTION_TYPE_USB) { + ev.conn_type = CONNECTION_USBMUXD; + } else if (event->device.conn_type == CONNECTION_TYPE_NETWORK) { + ev.conn_type = CONNECTION_NETWORK; + } else { + debug_info("Unknown connection type %d", event->device.conn_type); + } - if (event_cb) { - event_cb(&ev, user_data); + if (context->callback) { + context->callback(&ev, context->user_data); } } -/** - * Register a callback function that will be called when device add/remove - * events occur. - * - * @param callback Callback function to call. - * @param user_data Application-specific data passed as parameter - * to the registered callback function. - * - * @return IDEVICE_E_SUCCESS on success or an error value when an error occured. - */ -idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data) +idevice_error_t idevice_events_subscribe(idevice_subscription_context_t *context, idevice_event_cb_t callback, void *user_data) { - event_cb = callback; - int res = usbmuxd_subscribe(usbmux_event_cb, user_data); - if (res != 0) { - event_cb = NULL; - debug_info("Error %d when subscribing usbmux event callback!", res); + if (!context || !callback) { + return IDEVICE_E_INVALID_ARG; + } + *context = malloc(sizeof(struct idevice_subscription_context)); + if (!*context) { + debug_info("ERROR: %s: Failed to allocate subscription context\n", __func__); + return IDEVICE_E_UNKNOWN_ERROR; + } + (*context)->callback = callback; + (*context)->user_data = user_data; + int res = usbmuxd_events_subscribe(&(*context)->ctx, usbmux_event_cb, *context); + if (res != 0) { + free(*context); + *context = NULL; + debug_info("ERROR: usbmuxd_subscribe() returned %d!", res); return IDEVICE_E_UNKNOWN_ERROR; } return IDEVICE_E_SUCCESS; } -/** - * Release the event callback function that has been registered with - * idevice_event_subscribe(). - * - * @return IDEVICE_E_SUCCESS on success or an error value when an error occured. - */ -idevice_error_t idevice_event_unsubscribe() +idevice_error_t idevice_events_unsubscribe(idevice_subscription_context_t context) { - event_cb = NULL; - int res = usbmuxd_unsubscribe(); + if (!context) { + return IDEVICE_E_INVALID_ARG; + } + int res = usbmuxd_events_unsubscribe(context->ctx); if (res != 0) { - debug_info("Error %d when unsubscribing usbmux event callback!", res); + debug_info("ERROR: usbmuxd_unsubscribe() returned %d!", res); return IDEVICE_E_UNKNOWN_ERROR; } + if (context == event_ctx) { + event_ctx = NULL; + } + free(context); + return IDEVICE_E_SUCCESS; +} + +idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data) +{ + if (event_ctx) { + idevice_events_unsubscribe(event_ctx); + } + return idevice_events_subscribe(&event_ctx, callback, user_data); +} + +idevice_error_t idevice_event_unsubscribe(void) +{ + if (!event_ctx) { + return IDEVICE_E_SUCCESS; + } + event_ctx->callback = NULL; + return idevice_events_unsubscribe(event_ctx); +} + +idevice_error_t idevice_get_device_list_extended(idevice_info_t **devices, int *count) +{ + usbmuxd_device_info_t *dev_list; + + *devices = NULL; + *count = 0; + + if (usbmuxd_get_device_list(&dev_list) < 0) { + debug_info("ERROR: usbmuxd is not running!", __func__); + return IDEVICE_E_NO_DEVICE; + } + + idevice_info_t *newlist = NULL; + int i, newcount = 0; + + for (i = 0; dev_list[i].handle > 0; i++) { + newlist = realloc(*devices, sizeof(idevice_info_t) * (newcount+1)); + newlist[newcount] = malloc(sizeof(struct idevice_info)); + newlist[newcount]->udid = strdup(dev_list[i].udid); + if (dev_list[i].conn_type == CONNECTION_TYPE_USB) { + newlist[newcount]->conn_type = CONNECTION_USBMUXD; + newlist[newcount]->conn_data = NULL; + } else if (dev_list[i].conn_type == CONNECTION_TYPE_NETWORK) { + newlist[newcount]->conn_type = CONNECTION_NETWORK; + struct sockaddr* saddr = (struct sockaddr*)(dev_list[i].conn_data); + size_t addrlen = 0; + switch (saddr->sa_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; +#ifdef AF_INET6 + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; +#endif + default: + debug_info("Unsupported address family 0x%02x\n", saddr->sa_family); + continue; + } + newlist[newcount]->conn_data = malloc(addrlen); + memcpy(newlist[newcount]->conn_data, dev_list[i].conn_data, addrlen); + } + newcount++; + *devices = newlist; + } + usbmuxd_device_list_free(&dev_list); + + *count = newcount; + newlist = realloc(*devices, sizeof(idevice_info_t) * (newcount+1)); + newlist[newcount] = NULL; + *devices = newlist; + + return IDEVICE_E_SUCCESS; +} + +idevice_error_t idevice_device_list_extended_free(idevice_info_t *devices) +{ + if (devices) { + int i = 0; + while (devices[i]) { + free(devices[i]->udid); + free(devices[i]->conn_data); + free(devices[i]); + i++; + } + free(devices); + } return IDEVICE_E_SUCCESS; } -/** - * Get a list of currently available devices. - * - * @param devices List of uuids of devices that are currently available. - * This list is terminated by a NULL pointer. - * @param count Number of devices found. - * - * @return IDEVICE_E_SUCCESS on success or an error value when an error occured. - */ idevice_error_t idevice_get_device_list(char ***devices, int *count) { usbmuxd_device_info_t *dev_list; @@ -101,7 +390,7 @@ idevice_error_t idevice_get_device_list(char ***devices, int *count) *count = 0; if (usbmuxd_get_device_list(&dev_list) < 0) { - debug_info("ERROR: usbmuxd is not running!\n", __func__); + debug_info("ERROR: usbmuxd is not running!", __func__); return IDEVICE_E_NO_DEVICE; } @@ -109,9 +398,11 @@ idevice_error_t idevice_get_device_list(char ***devices, int *count) int i, newcount = 0; for (i = 0; dev_list[i].handle > 0; i++) { - newlist = realloc(*devices, sizeof(char*) * (newcount+1)); - newlist[newcount++] = strdup(dev_list[i].uuid); - *devices = newlist; + if (dev_list[i].conn_type == CONNECTION_TYPE_USB) { + newlist = realloc(*devices, sizeof(char*) * (newcount+1)); + newlist[newcount++] = strdup(dev_list[i].udid); + *devices = newlist; + } } usbmuxd_device_list_free(&dev_list); @@ -123,62 +414,101 @@ idevice_error_t idevice_get_device_list(char ***devices, int *count) return IDEVICE_E_SUCCESS; } -/** - * Free a list of device uuids. - * - * @param devices List of uuids to free. - * - * @return Always returnes IDEVICE_E_SUCCESS. - */ idevice_error_t idevice_device_list_free(char **devices) { if (devices) { int i = 0; - while (devices[i++]) { + while (devices[i]) { free(devices[i]); + i++; } free(devices); } return IDEVICE_E_SUCCESS; } -/** - * Creates an idevice_t structure for the device specified by uuid, - * if the device is available. - * - * @note The resulting idevice_t structure has to be freed with - * idevice_free() if it is no longer used. - * - * @param device Upon calling this function, a pointer to a location of type - * idevice_t. On successful return, this location will be populated. - * @param uuid The UUID to match. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ -idevice_error_t idevice_new(idevice_t * device, const char *uuid) +void idevice_set_debug_level(int level) +{ + internal_set_debug_level(level); +} + +static idevice_t idevice_from_mux_device(usbmuxd_device_info_t *muxdev) +{ + if (!muxdev) + return NULL; + + idevice_t device = (idevice_t)malloc(sizeof(struct idevice_private)); + if (!device) + return NULL; + + device->udid = strdup(muxdev->udid); + device->mux_id = muxdev->handle; + device->version = 0; + device->device_class = 0; + switch (muxdev->conn_type) { + case CONNECTION_TYPE_USB: + device->conn_type = CONNECTION_USBMUXD; + device->conn_data = NULL; + break; + case CONNECTION_TYPE_NETWORK: + device->conn_type = CONNECTION_NETWORK; + struct sockaddr* saddr = (struct sockaddr*)(muxdev->conn_data); + size_t addrlen = 0; + switch (saddr->sa_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; +#ifdef AF_INET6 + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; +#endif + default: + debug_info("Unsupported address family 0x%02x\n", saddr->sa_family); + free(device->udid); + free(device); + return NULL; + } + device->conn_data = malloc(addrlen); + memcpy(device->conn_data, muxdev->conn_data, addrlen); + break; + default: + device->conn_type = 0; + device->conn_data = NULL; + break; + } + return device; +} + +idevice_error_t idevice_new_with_options(idevice_t * device, const char *udid, enum idevice_options options) { usbmuxd_device_info_t muxdev; - int res = usbmuxd_get_device_by_uuid(uuid, &muxdev); + int usbmux_options = 0; + if (options & IDEVICE_LOOKUP_USBMUX) { + usbmux_options |= DEVICE_LOOKUP_USBMUX; + } + if (options & IDEVICE_LOOKUP_NETWORK) { + usbmux_options |= DEVICE_LOOKUP_NETWORK; + } + if (options & IDEVICE_LOOKUP_PREFER_NETWORK) { + usbmux_options |= DEVICE_LOOKUP_PREFER_NETWORK; + } + int res = usbmuxd_get_device(udid, &muxdev, usbmux_options); if (res > 0) { - idevice_t phone = (idevice_t) malloc(sizeof(struct idevice_private)); - phone->uuid = strdup(muxdev.uuid); - phone->conn_type = CONNECTION_USBMUXD; - phone->conn_data = (void*)(long)muxdev.handle; - *device = phone; + *device = idevice_from_mux_device(&muxdev); + if (!*device) { + return IDEVICE_E_UNKNOWN_ERROR; + } return IDEVICE_E_SUCCESS; } - /* other connection types could follow here */ - return IDEVICE_E_NO_DEVICE; } -/** - * Cleans up an idevice structure, then frees the structure itself. - * This is a library-level function; deals directly with the device to tear - * down relations, but otherwise is mostly internal. - * - * @param device idevice_t to free. - */ +idevice_error_t idevice_new(idevice_t * device, const char *udid) +{ + return idevice_new_with_options(device, udid, 0); +} + idevice_error_t idevice_free(idevice_t device) { if (!device) @@ -187,11 +517,8 @@ idevice_error_t idevice_free(idevice_t device) ret = IDEVICE_E_SUCCESS; - free(device->uuid); + free(device->udid); - if (device->conn_type == CONNECTION_USBMUXD) { - device->conn_data = 0; - } if (device->conn_data) { free(device->conn_data); } @@ -199,16 +526,6 @@ idevice_error_t idevice_free(idevice_t device) return ret; } -/** - * Set up a connection to the given device. - * - * @param device The device to connect to. - * @param port The destination port to connect to. - * @param connection Pointer to an idevice_connection_t that will be filled - * with the necessary data of the connection. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connection_t *connection) { if (!device) { @@ -216,31 +533,80 @@ idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connect } if (device->conn_type == CONNECTION_USBMUXD) { - int sfd = usbmuxd_connect((uint32_t)(long)device->conn_data, port); + int sfd = usbmuxd_connect(device->mux_id, port); if (sfd < 0) { - debug_info("ERROR: Connecting to usbmuxd failed: %d (%s)", sfd, strerror(-sfd)); + debug_info("ERROR: Connecting to usbmux device failed: %d (%s)", sfd, strerror(-sfd)); + switch (-sfd) { + case ECONNREFUSED: + return IDEVICE_E_CONNREFUSED; + case ENODEV: + return IDEVICE_E_NO_DEVICE; + default: + break; + } return IDEVICE_E_UNKNOWN_ERROR; } idevice_connection_t new_connection = (idevice_connection_t)malloc(sizeof(struct idevice_connection_private)); new_connection->type = CONNECTION_USBMUXD; new_connection->data = (void*)(long)sfd; new_connection->ssl_data = NULL; + new_connection->device = device; + new_connection->ssl_recv_timeout = (unsigned int)-1; + new_connection->status = IDEVICE_E_SUCCESS; *connection = new_connection; return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", device->conn_type); + } + if (device->conn_type == CONNECTION_NETWORK) { + struct sockaddr* saddr = (struct sockaddr*)(device->conn_data); + switch (saddr->sa_family) { + case AF_INET: +#ifdef AF_INET6 + case AF_INET6: +#endif + break; + default: + debug_info("Unsupported address family 0x%02x", saddr->sa_family); + return IDEVICE_E_UNKNOWN_ERROR; + } + + char addrtxt[48]; + addrtxt[0] = '\0'; + + if (!socket_addr_to_string(saddr, addrtxt, sizeof(addrtxt))) { + debug_info("Failed to convert network address: %d (%s)", errno, strerror(errno)); + } + + debug_info("Connecting to %s port %d...", addrtxt, port); + + int sfd = socket_connect_addr(saddr, port); + if (sfd < 0) { + int result = errno; + debug_info("ERROR: Connecting to network device failed: %d (%s)", result, strerror(result)); + switch (result) { + case ECONNREFUSED: + return IDEVICE_E_CONNREFUSED; + default: + break; + } + return IDEVICE_E_NO_DEVICE; + } + + idevice_connection_t new_connection = (idevice_connection_t)malloc(sizeof(struct idevice_connection_private)); + new_connection->type = CONNECTION_NETWORK; + new_connection->data = (void*)(long)sfd; + new_connection->ssl_data = NULL; + new_connection->device = device; + new_connection->ssl_recv_timeout = (unsigned int)-1; + + *connection = new_connection; + + return IDEVICE_E_SUCCESS; } + debug_info("Unknown connection type %d", device->conn_type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Disconnect from the device and clean up the connection structure. - * - * @param connection The connection to close. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_disconnect(idevice_connection_t connection) { if (!connection) { @@ -253,11 +619,19 @@ idevice_error_t idevice_disconnect(idevice_connection_t connection) idevice_error_t result = IDEVICE_E_UNKNOWN_ERROR; if (connection->type == CONNECTION_USBMUXD) { usbmuxd_disconnect((int)(long)connection->data); + connection->data = NULL; + result = IDEVICE_E_SUCCESS; + } else if (connection->type == CONNECTION_NETWORK) { + socket_close((int)(long)connection->data); + connection->data = NULL; result = IDEVICE_E_SUCCESS; } else { debug_info("Unknown connection type %d", connection->type); } + free(connection); + connection = NULL; + return result; } @@ -271,46 +645,111 @@ static idevice_error_t internal_connection_send(idevice_connection_t connection, } if (connection->type == CONNECTION_USBMUXD) { - int res = usbmuxd_send((int)(long)connection->data, data, len, sent_bytes); + int res; + do { + res = usbmuxd_send((int)(long)connection->data, data, len, sent_bytes); + } while (res == -EAGAIN); if (res < 0) { debug_info("ERROR: usbmuxd_send returned %d (%s)", res, strerror(-res)); return IDEVICE_E_UNKNOWN_ERROR; } return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", connection->type); } + if (connection->type == CONNECTION_NETWORK) { + int s = socket_send((int)(long)connection->data, (void*)data, len); + if (s < 0) { + *sent_bytes = 0; + return IDEVICE_E_UNKNOWN_ERROR; + } + *sent_bytes = s; + return IDEVICE_E_SUCCESS; + } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Send data to a device via the given connection. - * - * @param connection The connection to send data over. - * @param data Buffer with data to send. - * @param len Size of the buffer to send. - * @param sent_bytes Pointer to an uint32_t that will be filled - * with the number of bytes actually sent. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connection_send(idevice_connection_t connection, const char *data, uint32_t len, uint32_t *sent_bytes) { - if (!connection || !data || (connection->ssl_data && !connection->ssl_data->session)) { + if (!connection || !data +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) + || (connection->ssl_data && !connection->ssl_data->session) +#endif + ) { return IDEVICE_E_INVALID_ARG; } if (connection->ssl_data) { - ssize_t sent = gnutls_record_send(connection->ssl_data->session, (void*)data, (size_t)len); - if ((uint32_t)sent == (uint32_t)len) { - *sent_bytes = sent; - return IDEVICE_E_SUCCESS; + connection->status = IDEVICE_E_SUCCESS; + uint32_t sent = 0; + while (sent < len) { +#if defined(HAVE_OPENSSL) + int s = SSL_write(connection->ssl_data->session, (const void*)(data+sent), (int)(len-sent)); + if (s <= 0) { + int sslerr = SSL_get_error(connection->ssl_data->session, s); + if (sslerr == SSL_ERROR_WANT_WRITE) { + continue; + } + break; + } +#elif defined(HAVE_GNUTLS) + ssize_t s = gnutls_record_send(connection->ssl_data->session, (void*)(data+sent), (size_t)(len-sent)); +#elif defined(HAVE_MBEDTLS) + int s = mbedtls_ssl_write(&connection->ssl_data->ctx, (const unsigned char*)(data+sent), (size_t)(len-sent)); +#endif + if (s < 0) { + break; + } + sent += s; + } + debug_info("SSL_write %d, sent %d", len, sent); + if (sent < len) { + *sent_bytes = 0; + return connection->status == IDEVICE_E_SUCCESS ? IDEVICE_E_SSL_ERROR : connection->status; + } + *sent_bytes = sent; + return IDEVICE_E_SUCCESS; + } + uint32_t sent = 0; + while (sent < len) { + uint32_t bytes = 0; + int s = internal_connection_send(connection, data+sent, len-sent, &bytes); + if (s < 0) { + break; + } + sent += bytes; + } + debug_info("internal_connection_send %d, sent %d", len, sent); + if (sent < len) { + *sent_bytes = sent; + if (sent == 0) { + return IDEVICE_E_UNKNOWN_ERROR; + } + return IDEVICE_E_NOT_ENOUGH_DATA; + } + *sent_bytes = sent; + return IDEVICE_E_SUCCESS; +} + +static inline idevice_error_t socket_recv_to_idevice_error(int conn_error, uint32_t len, uint32_t received) +{ + if (conn_error < 0) { + switch (conn_error) { + case -EAGAIN: + if (len) { + debug_info("ERROR: received partial data %d/%d (%s)", received, len, strerror(-conn_error)); + } else { + debug_info("ERROR: received partial data (%s)", strerror(-conn_error)); + } + return IDEVICE_E_NOT_ENOUGH_DATA; + case -ETIMEDOUT: + return IDEVICE_E_TIMEOUT; + default: + return IDEVICE_E_UNKNOWN_ERROR; } - *sent_bytes = 0; - return IDEVICE_E_SSL_ERROR; } - return internal_connection_send(connection, data, len, sent_bytes); + return IDEVICE_E_SUCCESS; } /** @@ -324,47 +763,92 @@ static idevice_error_t internal_connection_receive_timeout(idevice_connection_t } if (connection->type == CONNECTION_USBMUXD) { - int res = usbmuxd_recv_timeout((int)(long)connection->data, data, len, recv_bytes, timeout); - if (res < 0) { - debug_info("ERROR: usbmuxd_recv_timeout returned %d (%s)", res, strerror(-res)); - return IDEVICE_E_UNKNOWN_ERROR; + int conn_error = usbmuxd_recv_timeout((int)(long)connection->data, data, len, recv_bytes, timeout); + idevice_error_t error = socket_recv_to_idevice_error(conn_error, len, *recv_bytes); + if (error == IDEVICE_E_UNKNOWN_ERROR) { + debug_info("ERROR: usbmuxd_recv_timeout returned %d (%s)", conn_error, strerror(-conn_error)); } - return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", connection->type); + return error; } + if (connection->type == CONNECTION_NETWORK) { + int res = socket_receive_timeout((int)(long)connection->data, data, len, 0, timeout); + idevice_error_t error = socket_recv_to_idevice_error(res, 0, 0); + if (error == IDEVICE_E_SUCCESS) { + *recv_bytes = (uint32_t)res; + } else if (error == IDEVICE_E_UNKNOWN_ERROR) { + debug_info("ERROR: socket_receive_timeout returned %d (%s)", res, strerror(-res)); + } + return error; + } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Receive data from a device via the given connection. - * This function will return after the given timeout even if no data has been - * received. - * - * @param connection The connection to receive data from. - * @param data Buffer that will be filled with the received data. - * This buffer has to be large enough to hold len bytes. - * @param len Buffer size or number of bytes to receive. - * @param recv_bytes Number of bytes actually received. - * @param timeout Timeout in milliseconds after which this function should - * return even if no data has been received. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout) { - if (!connection || (connection->ssl_data && !connection->ssl_data->session)) { + if (!connection +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) + || (connection->ssl_data && !connection->ssl_data->session) +#endif + || len == 0 + ) { return IDEVICE_E_INVALID_ARG; } if (connection->ssl_data) { - ssize_t received = gnutls_record_recv(connection->ssl_data->session, (void*)data, (size_t)len); - if (received > 0) { + uint32_t received = 0; + + if (connection->ssl_recv_timeout != (unsigned int)-1) { + debug_info("WARNING: ssl_recv_timeout was not properly reset in idevice_connection_receive_timeout"); + } + + // this should be reset after the SSL_read call on all codepaths, as + // the supplied timeout should only apply to the current read. + connection->ssl_recv_timeout = timeout; + connection->status = IDEVICE_E_SUCCESS; + while (received < len) { +#if defined(HAVE_OPENSSL) + int r = SSL_read(connection->ssl_data->session, (void*)((char*)(data+received)), (int)len-received); + if (r > 0) { + received += r; + } else { + int sslerr = SSL_get_error(connection->ssl_data->session, r); + if (sslerr == SSL_ERROR_WANT_READ) { + continue; + } else if (sslerr == SSL_ERROR_ZERO_RETURN) { + if (connection->status == IDEVICE_E_TIMEOUT) { + SSL_set_shutdown(connection->ssl_data->session, 0); + } + } + break; + } +#elif defined(HAVE_GNUTLS) + ssize_t r = gnutls_record_recv(connection->ssl_data->session, (void*)(data+received), (size_t)len-received); + if (r > 0) { + received += r; + } else { + break; + } +#elif defined(HAVE_MBEDTLS) + int r = mbedtls_ssl_read(&connection->ssl_data->ctx, (void*)(data+received), (size_t)len-received); + if (r > 0) { + received += r; + } else { + break; + } +#endif + } + connection->ssl_recv_timeout = (unsigned int)-1; + + debug_info("SSL_read %d, received %d", len, received); + if (received < len) { *recv_bytes = received; - return IDEVICE_E_SUCCESS; + return connection->status == IDEVICE_E_SUCCESS ? IDEVICE_E_SSL_ERROR : connection->status; } - *recv_bytes = 0; - return IDEVICE_E_SSL_ERROR; + + *recv_bytes = received; + return IDEVICE_E_SUCCESS; } return internal_connection_receive_timeout(connection, data, len, recv_bytes, timeout); } @@ -384,35 +868,45 @@ static idevice_error_t internal_connection_receive(idevice_connection_t connecti debug_info("ERROR: usbmuxd_recv returned %d (%s)", res, strerror(-res)); return IDEVICE_E_UNKNOWN_ERROR; } - return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", connection->type); } + if (connection->type == CONNECTION_NETWORK) { + int res = socket_receive((int)(long)connection->data, data, len); + if (res < 0) { + debug_info("ERROR: socket_receive returned %d (%s)", res, strerror(-res)); + return IDEVICE_E_UNKNOWN_ERROR; + } + *recv_bytes = (uint32_t)res; + return IDEVICE_E_SUCCESS; + } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Receive data from a device via the given connection. - * This function is like idevice_connection_receive_timeout, but with a - * predefined reasonable timeout. - * - * @param connection The connection to receive data from. - * @param data Buffer that will be filled with the received data. - * This buffer has to be large enough to hold len bytes. - * @param len Buffer size or number of bytes to receive. - * @param recv_bytes Number of bytes actually received. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes) { - if (!connection || (connection->ssl_data && !connection->ssl_data->session)) { + if (!connection +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) + || (connection->ssl_data && !connection->ssl_data->session) +#endif + ) { return IDEVICE_E_INVALID_ARG; } if (connection->ssl_data) { + if (connection->ssl_recv_timeout != (unsigned int)-1) { + debug_info("WARNING: ssl_recv_timeout was not properly reset in idevice_connection_receive_timeout"); + connection->ssl_recv_timeout = (unsigned int)-1; + } +#if defined(HAVE_OPENSSL) + int received = SSL_read(connection->ssl_data->session, (void*)data, (int)len); + debug_info("SSL_read %d, received %d", len, received); +#elif defined(HAVE_GNUTLS) ssize_t received = gnutls_record_recv(connection->ssl_data->session, (void*)data, (size_t)len); +#elif defined(HAVE_MBEDTLS) + int received = mbedtls_ssl_read(&connection->ssl_data->ctx, (unsigned char*)data, (size_t)len); +#endif if (received > 0) { *recv_bytes = received; return IDEVICE_E_SUCCESS; @@ -423,89 +917,105 @@ idevice_error_t idevice_connection_receive(idevice_connection_t connection, char return internal_connection_receive(connection, data, len, recv_bytes); } -/** - * Gets the handle of the device. Depends on the connection type. - */ -idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle) +idevice_error_t idevice_connection_get_fd(idevice_connection_t connection, int *fd) { - if (!device) + if (!connection || !fd) { return IDEVICE_E_INVALID_ARG; + } - if (device->conn_type == CONNECTION_USBMUXD) { - *handle = (uint32_t)(long)device->conn_data; + if (connection->type == CONNECTION_USBMUXD) { + *fd = (int)(long)connection->data; + return IDEVICE_E_SUCCESS; + } + if (connection->type == CONNECTION_NETWORK) { + *fd = (int)(long)connection->data; return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", device->conn_type); } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Gets the unique id for the device. - */ -idevice_error_t idevice_get_uuid(idevice_t device, char **uuid) +idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle) +{ + if (!device || !handle) + return IDEVICE_E_INVALID_ARG; + + *handle = device->mux_id; + return IDEVICE_E_SUCCESS; +} + +idevice_error_t idevice_get_udid(idevice_t device, char **udid) { - if (!device || !uuid) + if (!device || !udid) return IDEVICE_E_INVALID_ARG; - *uuid = strdup(device->uuid); + if (device->udid) { + *udid = strdup(device->udid); + } return IDEVICE_E_SUCCESS; } +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) +typedef ssize_t ssl_cb_ret_type_t; +#elif defined(HAVE_MBEDTLS) +typedef int ssl_cb_ret_type_t; +#endif + /** - * Internally used gnutls callback function for receiving encrypted data. + * Internally used SSL callback function for receiving encrypted data. */ -static ssize_t internal_ssl_read(gnutls_transport_ptr_t transport, char *buffer, size_t length) +static ssl_cb_ret_type_t internal_ssl_read(idevice_connection_t connection, char *buffer, size_t length) { - int bytes = 0, pos_start_fill = 0; - size_t tbytes = 0; - int this_len = length; + uint32_t bytes = 0; + uint32_t pos = 0; idevice_error_t res; - idevice_connection_t connection = (idevice_connection_t)transport; - char *recv_buffer; - - debug_info("pre-read client wants %zi bytes", length); + unsigned int timeout = connection->ssl_recv_timeout; - recv_buffer = (char *) malloc(sizeof(char) * this_len); + debug_info("pre-read length = %zi bytes", length); /* repeat until we have the full data or an error occurs */ do { - if ((res = internal_connection_receive(connection, recv_buffer, this_len, (uint32_t*)&bytes)) != IDEVICE_E_SUCCESS) { - debug_info("ERROR: idevice_connection_receive returned %d", res); - return res; + bytes = 0; + if (timeout == (unsigned int)-1) { + res = internal_connection_receive(connection, buffer + pos, (uint32_t)length - pos, &bytes); + } else { + res = internal_connection_receive_timeout(connection, buffer + pos, (uint32_t)length - pos, &bytes, (unsigned int)timeout); } - debug_info("post-read we got %i bytes", bytes); + if (res != IDEVICE_E_SUCCESS) { + if (res != IDEVICE_E_TIMEOUT) { + debug_info("ERROR: %s returned %d", (timeout == (unsigned int)-1) ? "internal_connection_receive" : "internal_connection_receive_timeout", res); + } + connection->status = res; + return -1; + } + debug_info("read %i bytes", bytes); /* increase read count */ - tbytes += bytes; - - /* fill the buffer with what we got right now */ - memcpy(buffer + pos_start_fill, recv_buffer, bytes); - pos_start_fill += bytes; - - if (tbytes >= length) { - break; + pos += bytes; + if (pos < (uint32_t)length) { + debug_info("re-read trying to read missing %i bytes", (uint32_t)length - pos); } + } while (pos < (uint32_t)length); - this_len = length - tbytes; - debug_info("re-read trying to read missing %i bytes", this_len); - } while (tbytes < length); + debug_info("post-read received %i bytes", pos); - if (recv_buffer) { - free(recv_buffer); - } - return tbytes; + return pos; } /** - * Internally used gnutls callback function for sending encrypted data. + * Internally used SSL callback function for sending encrypted data. */ -static ssize_t internal_ssl_write(gnutls_transport_ptr_t transport, char *buffer, size_t length) +static ssl_cb_ret_type_t internal_ssl_write(idevice_connection_t connection, const char *buffer, size_t length) { uint32_t bytes = 0; - idevice_connection_t connection = (idevice_connection_t)transport; - debug_info("pre-send length = %zi", length); - internal_connection_send(connection, buffer, length, &bytes); + idevice_error_t res; + debug_info("pre-send length = %zi bytes", length); + if ((res = internal_connection_send(connection, buffer, length, &bytes)) != IDEVICE_E_SUCCESS) { + debug_info("ERROR: internal_connection_send returned %d", res); + connection->status = res; + return -1; + } debug_info("post-send sent %i bytes", bytes); return bytes; } @@ -518,6 +1028,14 @@ static void internal_ssl_cleanup(ssl_data_t ssl_data) if (!ssl_data) return; +#if defined(HAVE_OPENSSL) + if (ssl_data->session) { + SSL_free(ssl_data->session); + } + if (ssl_data->ctx) { + SSL_CTX_free(ssl_data->ctx); + } +#elif defined(HAVE_GNUTLS) if (ssl_data->session) { gnutls_deinit(ssl_data->session); } @@ -536,20 +1054,118 @@ static void internal_ssl_cleanup(ssl_data_t ssl_data) if (ssl_data->host_privkey) { gnutls_x509_privkey_deinit(ssl_data->host_privkey); } +#elif defined(HAVE_MBEDTLS) + mbedtls_pk_free(&ssl_data->root_privkey); + mbedtls_x509_crt_free(&ssl_data->certificate); + mbedtls_entropy_free(&ssl_data->entropy); + mbedtls_ctr_drbg_free(&ssl_data->ctr_drbg); + mbedtls_ssl_config_free(&ssl_data->config); + mbedtls_ssl_free(&ssl_data->ctx); +#endif +} + +#ifdef HAVE_OPENSSL +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +static long ssl_idevice_bio_callback(BIO *b, int oper, const char *argp, size_t len, int argi, long argl, int retvalue, size_t *processed) +#else +static long ssl_idevice_bio_callback(BIO *b, int oper, const char *argp, int argi, long argl, long retvalue) +#endif +{ + ssize_t bytes = 0; + idevice_connection_t conn = (idevice_connection_t)BIO_get_callback_arg(b); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + size_t len = (size_t)argi; + size_t *processed = (size_t*)&bytes; +#endif + switch (oper) { + case (BIO_CB_READ|BIO_CB_RETURN): + if (argp) { + bytes = internal_ssl_read(conn, (char *)argp, len); + *processed = bytes; + return (long)bytes; + } + return 0; + case (BIO_CB_PUTS|BIO_CB_RETURN): + len = strlen(argp); + // fallthrough + case (BIO_CB_WRITE|BIO_CB_RETURN): + bytes = internal_ssl_write(conn, argp, len); + *processed = bytes; + return (long)bytes; + default: + return retvalue; + } +} + +static BIO *ssl_idevice_bio_new(idevice_connection_t conn) +{ + BIO *b = BIO_new(BIO_s_null()); + if (!b) return NULL; + BIO_set_callback_arg(b, (char *)conn); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIO_set_callback_ex(b, ssl_idevice_bio_callback); +#else + BIO_set_callback(b, ssl_idevice_bio_callback); +#endif + return b; +} + +static int ssl_verify_callback(int ok, X509_STORE_CTX *ctx) +{ + return 1; +} + +#ifndef STRIP_DEBUG_CODE +static const char *ssl_error_to_string(int e) +{ + switch(e) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_SSL: + return ERR_error_string(ERR_get_error(), NULL); + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; + default: + return "UNKOWN_ERROR_VALUE"; + } } +#endif +#endif +#if defined(HAVE_GNUTLS) /** * Internally used gnutls callback function that gets called during handshake. */ -static int internal_cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) +#if GNUTLS_VERSION_NUMBER >= 0x020b07 +static int internal_cert_callback(gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr2_st * st) +#else +static int internal_cert_callback(gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) +#endif { int res = -1; - gnutls_certificate_type_t type = gnutls_certificate_type_get (session); + gnutls_certificate_type_t type = gnutls_certificate_type_get(session); if (type == GNUTLS_CRT_X509) { - ssl_data_t ssl_data = (ssl_data_t)gnutls_session_get_ptr (session); + ssl_data_t ssl_data = (ssl_data_t)gnutls_session_get_ptr(session); if (ssl_data && ssl_data->host_privkey && ssl_data->host_cert) { debug_info("Passing certificate"); +#if GNUTLS_VERSION_NUMBER >= 0x020b07 + st->cert_type = type; + st->key_type = GNUTLS_PRIVKEY_X509; +#else st->type = type; +#endif st->ncerts = 1; st->cert.x509 = &ssl_data->host_cert; st->key.x509 = ssl_data->host_privkey; @@ -559,46 +1175,204 @@ static int internal_cert_callback (gnutls_session_t session, const gnutls_datum_ } return res; } +#elif defined(HAVE_MBEDTLS) +static void _mbedtls_log_cb(void* ctx, int level, const char* filename, int line, const char* message) +{ + fprintf(stderr, "[mbedtls][%d] %s:%d => %s", level, filename, line, message); +} + +static int cert_verify_cb(void* ctx, mbedtls_x509_crt* cert, int depth, uint32_t *flags) +{ + *flags = 0; + return 0; +} + +static int _mbedtls_f_rng(void* p_rng, unsigned char* buf, size_t len) +{ + memset(buf, 4, len); + return 0; +} +#endif -/** - * Enables SSL for the given connection. - * - * @param connection The connection to enable SSL for. - * - * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection - * is NULL or connection->ssl_data is non-NULL, or IDEVICE_E_SSL_ERROR when - * SSL initialization, setup, or handshake fails. - */ idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection) { if (!connection || connection->ssl_data) return IDEVICE_E_INVALID_ARG; idevice_error_t ret = IDEVICE_E_SSL_ERROR; - uint32_t return_me = 0; + plist_t pair_record = NULL; + + userpref_error_t uerr = userpref_read_pair_record(connection->device->udid, &pair_record); + if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed enabling SSL. Unable to read pair record for udid %s (%d)", connection->device->udid, uerr); + return ret; + } + +#if defined(HAVE_OPENSSL) + key_data_t root_cert = { NULL, 0 }; + key_data_t root_privkey = { NULL, 0 }; + + pair_record_import_crt_with_name(pair_record, USERPREF_ROOT_CERTIFICATE_KEY, &root_cert); + pair_record_import_key_with_name(pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY, &root_privkey); + if (pair_record) + plist_free(pair_record); + + BIO *ssl_bio = ssl_idevice_bio_new(connection); + if (!ssl_bio) { + debug_info("ERROR: Could not create SSL bio."); + return ret; + } + + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + if (ssl_ctx == NULL) { + debug_info("ERROR: Could not create SSL context."); + BIO_free(ssl_bio); + return ret; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3060000fL)) + SSL_CTX_set_security_level(ssl_ctx, 0); +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100002L || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2060000fL)) + /* force use of TLSv1 for older devices */ + if (connection->device->version < DEVICE_VERSION(10,0,0)) { +#ifdef SSL_OP_NO_TLSv1_1 + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_1); +#endif +#ifdef SSL_OP_NO_TLSv1_2 + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_2); +#endif +#ifdef SSL_OP_NO_TLSv1_3 + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_3); +#endif + } +#else + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_VERSION); + if (connection->device->version < DEVICE_VERSION(10,0,0)) { + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_VERSION); + if (connection->device->version == 0) { + /* + iOS 1 doesn't understand TLS1_VERSION, it can only speak SSL3_VERSION. + However, modern OpenSSL is usually compiled without SSLv3 support. + So if we set min_proto_version to SSL3_VERSION on an OpenSSL instance which doesn't support it, + it will just ignore min_proto_version altogether and fall back to an even higher version. + To avoid accidentally breaking iOS 2.0+, we set min version to 0 instead. + Here is what documentation says: + Setting the minimum or maximum version to 0, + will enable protocol versions down to the lowest version, + or up to the highest version supported by the library, respectively. + */ + SSL_CTX_set_min_proto_version(ssl_ctx, 0); + } + } +#endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if defined(SSL_OP_IGNORE_UNEXPECTED_EOF) + /* + * For OpenSSL 3 and later, mark close_notify alerts as optional. + * For prior versions of OpenSSL we check for SSL_ERROR_SYSCALL when + * reading instead (this error changes to SSL_ERROR_SSL in OpenSSL 3). + */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_IGNORE_UNEXPECTED_EOF); +#endif +#if defined(SSL_OP_LEGACY_SERVER_CONNECT) + /* + * Without setting SSL_OP_LEGACY_SERVER_CONNECT, OpenSSL 3 fails with + * error "unsafe legacy renegotiation disabled" when talking to iOS 5 + */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_LEGACY_SERVER_CONNECT); +#endif +#endif + + BIO* membp; + X509* rootCert = NULL; + membp = BIO_new_mem_buf(root_cert.data, root_cert.size); + PEM_read_bio_X509(membp, &rootCert, NULL, NULL); + BIO_free(membp); + if (SSL_CTX_use_certificate(ssl_ctx, rootCert) != 1) { + debug_info("WARNING: Could not load RootCertificate"); + } + X509_free(rootCert); + free(root_cert.data); + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY* rootPrivKey = NULL; + membp = BIO_new_mem_buf(root_privkey.data, root_privkey.size); + PEM_read_bio_PrivateKey(membp, &rootPrivKey, NULL, NULL); + BIO_free(membp); + if (SSL_CTX_use_PrivateKey(ssl_ctx, rootPrivKey) != 1) { + debug_info("WARNING: Could not load RootPrivateKey"); + } + EVP_PKEY_free(rootPrivKey); +#else + RSA* rootPrivKey = NULL; + membp = BIO_new_mem_buf(root_privkey.data, root_privkey.size); + PEM_read_bio_RSAPrivateKey(membp, &rootPrivKey, NULL, NULL); + BIO_free(membp); + if (SSL_CTX_use_RSAPrivateKey(ssl_ctx, rootPrivKey) != 1) { + debug_info("WARNING: Could not load RootPrivateKey"); + } + RSA_free(rootPrivKey); +#endif + free(root_privkey.data); + + SSL *ssl = SSL_new(ssl_ctx); + if (!ssl) { + debug_info("ERROR: Could not create SSL object"); + BIO_free(ssl_bio); + SSL_CTX_free(ssl_ctx); + return ret; + } + SSL_set_connect_state(ssl); + SSL_set_verify(ssl, 0, ssl_verify_callback); + SSL_set_bio(ssl, ssl_bio, ssl_bio); + + debug_info("Performing SSL handshake"); + int ssl_error = 0; + do { + ssl_error = SSL_get_error(ssl, SSL_do_handshake(ssl)); + if (ssl_error == 0 || ssl_error != SSL_ERROR_WANT_READ) { + break; + } +#ifdef WIN32 + Sleep(100); +#else + struct timespec ts = { 0, 100000000 }; + nanosleep(&ts, NULL); +#endif + } while (1); + if (ssl_error != 0) { + debug_info("ERROR during SSL handshake: %s", ssl_error_to_string(ssl_error)); + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + } else { + ssl_data_t ssl_data_loc = (ssl_data_t)malloc(sizeof(struct ssl_data_private)); + ssl_data_loc->session = ssl; + ssl_data_loc->ctx = ssl_ctx; + connection->ssl_data = ssl_data_loc; + ret = IDEVICE_E_SUCCESS; + debug_info("SSL mode enabled, %s, cipher: %s", SSL_get_version(ssl), SSL_get_cipher(ssl)); + } + /* required for proper multi-thread clean up to prevent leaks */ + openssl_remove_thread_state(); +#elif defined(HAVE_GNUTLS) ssl_data_t ssl_data_loc = (ssl_data_t)malloc(sizeof(struct ssl_data_private)); /* Set up GnuTLS... */ debug_info("enabling SSL mode"); errno = 0; - gnutls_global_init(); gnutls_certificate_allocate_credentials(&ssl_data_loc->certificate); - gnutls_certificate_client_set_retrieve_function (ssl_data_loc->certificate, internal_cert_callback); +#if GNUTLS_VERSION_NUMBER >= 0x020b07 + gnutls_certificate_set_retrieve_function(ssl_data_loc->certificate, internal_cert_callback); +#else + gnutls_certificate_client_set_retrieve_function(ssl_data_loc->certificate, internal_cert_callback); +#endif gnutls_init(&ssl_data_loc->session, GNUTLS_CLIENT); - { - int protocol_priority[16] = { GNUTLS_SSL3, 0 }; - int kx_priority[16] = { GNUTLS_KX_ANON_DH, GNUTLS_KX_RSA, 0 }; - int cipher_priority[16] = { GNUTLS_CIPHER_AES_128_CBC, GNUTLS_CIPHER_AES_256_CBC, 0 }; - int mac_priority[16] = { GNUTLS_MAC_SHA1, GNUTLS_MAC_MD5, 0 }; - int comp_priority[16] = { GNUTLS_COMP_NULL, 0 }; - - gnutls_cipher_set_priority(ssl_data_loc->session, cipher_priority); - gnutls_compression_set_priority(ssl_data_loc->session, comp_priority); - gnutls_kx_set_priority(ssl_data_loc->session, kx_priority); - gnutls_protocol_set_priority(ssl_data_loc->session, protocol_priority); - gnutls_mac_set_priority(ssl_data_loc->session, mac_priority); - } + gnutls_priority_set_direct(ssl_data_loc->session, "NONE:+VERS-TLS1.0:+ANON-DH:+RSA:+AES-128-CBC:+AES-256-CBC:+SHA1:+MD5:+COMP-NULL", NULL); gnutls_credentials_set(ssl_data_loc->session, GNUTLS_CRD_CERTIFICATE, ssl_data_loc->certificate); gnutls_session_set_ptr(ssl_data_loc->session, ssl_data_loc); @@ -607,10 +1381,13 @@ idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection) gnutls_x509_privkey_init(&ssl_data_loc->root_privkey); gnutls_x509_privkey_init(&ssl_data_loc->host_privkey); - userpref_error_t uerr = userpref_get_keys_and_certs(ssl_data_loc->root_privkey, ssl_data_loc->root_cert, ssl_data_loc->host_privkey, ssl_data_loc->host_cert); - if (uerr != USERPREF_E_SUCCESS) { - debug_info("Error %d when loading keys and certificates! %d", uerr); - } + pair_record_import_crt_with_name(pair_record, USERPREF_ROOT_CERTIFICATE_KEY, ssl_data_loc->root_cert); + pair_record_import_crt_with_name(pair_record, USERPREF_HOST_CERTIFICATE_KEY, ssl_data_loc->host_cert); + pair_record_import_key_with_name(pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY, ssl_data_loc->root_privkey); + pair_record_import_key_with_name(pair_record, USERPREF_HOST_PRIVATE_KEY_KEY, ssl_data_loc->host_privkey); + + if (pair_record) + plist_free(pair_record); debug_info("GnuTLS step 1..."); gnutls_transport_set_ptr(ssl_data_loc->session, (gnutls_transport_ptr_t)connection); @@ -619,46 +1396,146 @@ idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection) debug_info("GnuTLS step 3..."); gnutls_transport_set_pull_function(ssl_data_loc->session, (gnutls_pull_func) & internal_ssl_read); debug_info("GnuTLS step 4 -- now handshaking..."); - if (errno) - debug_info("WARN: errno says %s before handshake!", strerror(errno)); - return_me = gnutls_handshake(ssl_data_loc->session); + if (errno) { + debug_info("WARNING: errno says %s before handshake!", strerror(errno)); + } + + int return_me = 0; + do { + return_me = gnutls_handshake(ssl_data_loc->session); + } while(return_me == GNUTLS_E_AGAIN || return_me == GNUTLS_E_INTERRUPTED); + debug_info("GnuTLS handshake done..."); if (return_me != GNUTLS_E_SUCCESS) { internal_ssl_cleanup(ssl_data_loc); free(ssl_data_loc); - debug_info("GnuTLS reported something wrong."); - gnutls_perror(return_me); + debug_info("GnuTLS reported something wrong: %s", gnutls_strerror(return_me)); debug_info("oh.. errno says %s", strerror(errno)); } else { connection->ssl_data = ssl_data_loc; ret = IDEVICE_E_SUCCESS; debug_info("SSL mode enabled"); } +#elif defined(HAVE_MBEDTLS) + key_data_t root_cert = { NULL, 0 }; + key_data_t root_privkey = { NULL, 0 }; + + pair_record_import_crt_with_name(pair_record, USERPREF_ROOT_CERTIFICATE_KEY, &root_cert); + pair_record_import_key_with_name(pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY, &root_privkey); + + plist_free(pair_record); + + ssl_data_t ssl_data_loc = (ssl_data_t)malloc(sizeof(struct ssl_data_private)); + + mbedtls_ssl_init(&ssl_data_loc->ctx); + mbedtls_ssl_config_init(&ssl_data_loc->config); + mbedtls_entropy_init(&ssl_data_loc->entropy); + mbedtls_ctr_drbg_init(&ssl_data_loc->ctr_drbg); + + int r = mbedtls_ctr_drbg_seed(&ssl_data_loc->ctr_drbg, mbedtls_entropy_func, &ssl_data_loc->entropy, NULL, 0); + if (r != 0) { + debug_info("ERROR: [mbedtls] mbedtls_ctr_drbg_seed failed: %d", r); + return ret; + } + + if (mbedtls_ssl_config_defaults(&ssl_data_loc->config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) { + debug_info("ERROR: [mbedtls] Failed to set config defaults"); + return ret; + } + + mbedtls_ssl_conf_rng(&ssl_data_loc->config, mbedtls_ctr_drbg_random, &ssl_data_loc->ctr_drbg); + + mbedtls_ssl_conf_dbg(&ssl_data_loc->config, _mbedtls_log_cb, NULL); + + mbedtls_ssl_conf_verify(&ssl_data_loc->config, cert_verify_cb, NULL); + + mbedtls_ssl_setup(&ssl_data_loc->ctx, &ssl_data_loc->config); + + mbedtls_ssl_set_bio(&ssl_data_loc->ctx, connection, (mbedtls_ssl_send_t*)&internal_ssl_write, (mbedtls_ssl_recv_t*)&internal_ssl_read, NULL); + + mbedtls_x509_crt_init(&ssl_data_loc->certificate); + + int crterr = mbedtls_x509_crt_parse(&ssl_data_loc->certificate, root_cert.data, root_cert.size); + if (crterr < 0) { + debug_info("ERROR: [mbedtls] parsing root cert failed: %d", crterr); + return ret; + } + + mbedtls_ssl_conf_ca_chain(&ssl_data_loc->config, &ssl_data_loc->certificate, NULL); + + mbedtls_pk_init(&ssl_data_loc->root_privkey); + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + int pkerr = mbedtls_pk_parse_key(&ssl_data_loc->root_privkey, root_privkey.data, root_privkey.size, NULL, 0, &_mbedtls_f_rng, NULL); +#else + int pkerr = mbedtls_pk_parse_key(&ssl_data_loc->root_privkey, root_privkey.data, root_privkey.size, NULL, 0); +#endif + if (pkerr < 0) { + debug_info("ERROR: [mbedtls] parsing private key failed: %d (size=%d)", pkerr, root_privkey.size); + return ret; + } + + mbedtls_ssl_conf_own_cert(&ssl_data_loc->config, &ssl_data_loc->certificate, &ssl_data_loc->root_privkey); + + int return_me = 0; + do { + return_me = mbedtls_ssl_handshake(&ssl_data_loc->ctx); + } while (return_me == MBEDTLS_ERR_SSL_WANT_READ || return_me == MBEDTLS_ERR_SSL_WANT_WRITE); + + if (return_me != 0) { + debug_info("ERROR during SSL handshake: %d", return_me); + internal_ssl_cleanup(ssl_data_loc); + free(ssl_data_loc); + } else { + connection->ssl_data = ssl_data_loc; + ret = IDEVICE_E_SUCCESS; + debug_info("SSL mode enabled, %s, cipher: %s", mbedtls_ssl_get_version(&ssl_data_loc->ctx), mbedtls_ssl_get_ciphersuite(&ssl_data_loc->ctx)); + debug_info("SSL mode enabled"); + } +#endif return ret; } -/** - * Disable SSL for the given connection. - * - * @param connection The connection to disable SSL for. - * - * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection - * is NULL. This function also returns IDEVICE_E_SUCCESS when SSL is not - * enabled and does no further error checking on cleanup. - */ idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection) { + return idevice_connection_disable_bypass_ssl(connection, 0); +} + +idevice_error_t idevice_connection_disable_bypass_ssl(idevice_connection_t connection, uint8_t sslBypass) +{ if (!connection) return IDEVICE_E_INVALID_ARG; if (!connection->ssl_data) { - /* ignore if ssl is not enabled */ + /* ignore if ssl is not enabled */ return IDEVICE_E_SUCCESS; } - if (connection->ssl_data->session) { - gnutls_bye(connection->ssl_data->session, GNUTLS_SHUT_RDWR); + // some services require plain text communication after SSL handshake + // sending out SSL_shutdown will cause bytes + if (!sslBypass) { +#if defined(HAVE_OPENSSL) + if (connection->ssl_data->session) { + /* see: https://www.openssl.org/docs/ssl/SSL_shutdown.html#RETURN_VALUES */ + if (SSL_shutdown(connection->ssl_data->session) == 0) { + /* Only try bidirectional shutdown if we know it can complete */ + int ssl_error; + if ((ssl_error = SSL_get_error(connection->ssl_data->session, 0)) == SSL_ERROR_NONE) { + SSL_shutdown(connection->ssl_data->session); + } else { + debug_info("Skipping bidirectional SSL shutdown. SSL error code: %i", ssl_error); + } + } + } +#elif defined(HAVE_GNUTLS) + if (connection->ssl_data->session) { + gnutls_bye(connection->ssl_data->session, GNUTLS_SHUT_RDWR); + } +#elif defined(HAVE_MBEDTLS) + mbedtls_ssl_close_notify(&connection->ssl_data->ctx); +#endif } + internal_ssl_cleanup(connection->ssl_data); free(connection->ssl_data); connection->ssl_data = NULL; @@ -667,4 +1544,3 @@ idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection) return IDEVICE_E_SUCCESS; } - diff --git a/src/idevice.h b/src/idevice.h index 231b3ab..dd72f9d 100644 --- a/src/idevice.h +++ b/src/idevice.h @@ -8,51 +8,97 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IDEVICE_H -#define IDEVICE_H +#ifndef __DEVICE_H +#define __DEVICE_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(HAVE_OPENSSL) +#include <openssl/ssl.h> +#elif defined(HAVE_GNUTLS) #include <gnutls/gnutls.h> #include <gnutls/x509.h> +#elif defined(HAVE_MBEDTLS) +#include <mbedtls/ssl.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#endif +#ifdef LIBIMOBILEDEVICE_STATIC + #define LIBIMOBILEDEVICE_API +#elif defined(_WIN32) + #define LIBIMOBILEDEVICE_API __declspec( dllexport ) +#else + #if __GNUC__ >= 4 + #define LIBIMOBILEDEVICE_API __attribute__((visibility("default"))) + #else + #define LIBIMOBILEDEVICE_API + #endif +#endif + +#include "common/userpref.h" #include "libimobiledevice/libimobiledevice.h" -enum connection_type { - CONNECTION_USBMUXD = 1 -}; +#define DEVICE_VERSION(maj, min, patch) (((maj & 0xFF) << 16) | ((min & 0xFF) << 8) | (patch & 0xFF)) + +#define DEVICE_CLASS_IPHONE 1 +#define DEVICE_CLASS_IPAD 2 +#define DEVICE_CLASS_IPOD 3 +#define DEVICE_CLASS_APPLETV 4 +#define DEVICE_CLASS_WATCH 5 +#define DEVICE_CLASS_UNKNOWN 255 struct ssl_data_private { +#if defined(HAVE_OPENSSL) + SSL *session; + SSL_CTX *ctx; +#elif defined(HAVE_GNUTLS) gnutls_certificate_credentials_t certificate; gnutls_session_t session; gnutls_x509_privkey_t root_privkey; gnutls_x509_crt_t root_cert; gnutls_x509_privkey_t host_privkey; gnutls_x509_crt_t host_cert; +#elif defined(HAVE_MBEDTLS) + mbedtls_ssl_context ctx; + mbedtls_ssl_config config; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_x509_crt certificate; + mbedtls_pk_context root_privkey; +#endif }; typedef struct ssl_data_private *ssl_data_t; struct idevice_connection_private { - enum connection_type type; + idevice_t device; + enum idevice_connection_type type; void *data; ssl_data_t ssl_data; + unsigned int ssl_recv_timeout; + idevice_error_t status; }; struct idevice_private { - char *uuid; - enum connection_type conn_type; + char *udid; + uint32_t mux_id; + enum idevice_connection_type conn_type; void *conn_data; + int version; + int device_class; }; -idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection); -idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection); - #endif diff --git a/src/installation_proxy.c b/src/installation_proxy.c index 4a76dd2..ec19da0 100644 --- a/src/installation_proxy.c +++ b/src/installation_proxy.c @@ -2,63 +2,210 @@ * installation_proxy.c * com.apple.mobile.installation_proxy service implementation. * - * Copyright (c) 2009 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2013 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> +#include <inttypes.h> #include <unistd.h> #include <plist/plist.h> #include "installation_proxy.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" + +typedef enum { + INSTPROXY_COMMAND_TYPE_ASYNC, + INSTPROXY_COMMAND_TYPE_SYNC +} instproxy_command_type_t; struct instproxy_status_data { instproxy_client_t client; + plist_t command; instproxy_status_cb_t cbfunc; - char *operation; void *user_data; }; /** + * Converts an error string identifier to a instproxy_error_t value. + * Used internally to get correct error codes from a response. + * + * @param name The error name to convert. + * @param error_detail Pointer to store error detail text if available. The + * caller is reponsible for freeing the allocated buffer after use. If NULL + * is passed no error detail will be returned. + * + * @return A matching instproxy_error_t error code or + * INSTPROXY_E_UNKNOWN_ERROR otherwise. + */ +static instproxy_error_t instproxy_strtoerr(const char* name) +{ + instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; + + if (strcmp(name, "AlreadyArchived") == 0) { + err = INSTPROXY_E_ALREADY_ARCHIVED; + } else if (strcmp(name, "APIInternalError") == 0) { + err = INSTPROXY_E_API_INTERNAL_ERROR; + } else if (strcmp(name, "ApplicationAlreadyInstalled") == 0) { + err = INSTPROXY_E_APPLICATION_ALREADY_INSTALLED; + } else if (strcmp(name, "ApplicationMoveFailed") == 0) { + err = INSTPROXY_E_APPLICATION_MOVE_FAILED; + } else if (strcmp(name, "ApplicationSINFCaptureFailed") == 0) { + err = INSTPROXY_E_APPLICATION_SINF_CAPTURE_FAILED; + } else if (strcmp(name, "ApplicationSandboxFailed") == 0) { + err = INSTPROXY_E_APPLICATION_SANDBOX_FAILED; + } else if (strcmp(name, "ApplicationVerificationFailed") == 0) { + err = INSTPROXY_E_APPLICATION_VERIFICATION_FAILED; + } else if (strcmp(name, "ArchiveDestructionFailed") == 0) { + err = INSTPROXY_E_ARCHIVE_DESTRUCTION_FAILED; + } else if (strcmp(name, "BundleVerificationFailed") == 0) { + err = INSTPROXY_E_BUNDLE_VERIFICATION_FAILED; + } else if (strcmp(name, "CarrierBundleCopyFailed") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_COPY_FAILED; + } else if (strcmp(name, "CarrierBundleDirectoryCreationFailed") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_DIRECTORY_CREATION_FAILED; + } else if (strcmp(name, "CarrierBundleMissingSupportedSIMs") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_MISSING_SUPPORTED_SIMS; + } else if (strcmp(name, "CommCenterNotificationFailed") == 0) { + err = INSTPROXY_E_COMM_CENTER_NOTIFICATION_FAILED; + } else if (strcmp(name, "ContainerCreationFailed") == 0) { + err = INSTPROXY_E_CONTAINER_CREATION_FAILED; + } else if (strcmp(name, "ContainerP0wnFailed") == 0) { + err = INSTPROXY_E_CONTAINER_P0WN_FAILED; + } else if (strcmp(name, "ContainerRemovalFailed") == 0) { + err = INSTPROXY_E_CONTAINER_REMOVAL_FAILED; + } else if (strcmp(name, "EmbeddedProfileInstallFailed") == 0) { + err = INSTPROXY_E_EMBEDDED_PROFILE_INSTALL_FAILED; + } else if (strcmp(name, "ExecutableTwiddleFailed") == 0) { + err = INSTPROXY_E_EXECUTABLE_TWIDDLE_FAILED; + } else if (strcmp(name, "ExistenceCheckFailed") == 0) { + err = INSTPROXY_E_EXISTENCE_CHECK_FAILED; + } else if (strcmp(name, "InstallMapUpdateFailed") == 0) { + err = INSTPROXY_E_INSTALL_MAP_UPDATE_FAILED; + } else if (strcmp(name, "ManifestCaptureFailed") == 0) { + err = INSTPROXY_E_MANIFEST_CAPTURE_FAILED; + } else if (strcmp(name, "MapGenerationFailed") == 0) { + err = INSTPROXY_E_MAP_GENERATION_FAILED; + } else if (strcmp(name, "MissingBundleExecutable") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_EXECUTABLE; + } else if (strcmp(name, "MissingBundleIdentifier") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_IDENTIFIER; + } else if (strcmp(name, "MissingBundlePath") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_PATH; + } else if (strcmp(name, "MissingContainer") == 0) { + err = INSTPROXY_E_MISSING_CONTAINER; + } else if (strcmp(name, "NotificationFailed") == 0) { + err = INSTPROXY_E_NOTIFICATION_FAILED; + } else if (strcmp(name, "PackageExtractionFailed") == 0) { + err = INSTPROXY_E_PACKAGE_EXTRACTION_FAILED; + } else if (strcmp(name, "PackageInspectionFailed") == 0) { + err = INSTPROXY_E_PACKAGE_INSPECTION_FAILED; + } else if (strcmp(name, "PackageMoveFailed") == 0) { + err = INSTPROXY_E_PACKAGE_MOVE_FAILED; + } else if (strcmp(name, "PathConversionFailed") == 0) { + err = INSTPROXY_E_PATH_CONVERSION_FAILED; + } else if (strcmp(name, "RestoreContainerFailed") == 0) { + err = INSTPROXY_E_RESTORE_CONTAINER_FAILED; + } else if (strcmp(name, "SeatbeltProfileRemovalFailed") == 0) { + err = INSTPROXY_E_SEATBELT_PROFILE_REMOVAL_FAILED; + } else if (strcmp(name, "StageCreationFailed") == 0) { + err = INSTPROXY_E_STAGE_CREATION_FAILED; + } else if (strcmp(name, "SymlinkFailed") == 0) { + err = INSTPROXY_E_SYMLINK_FAILED; + } else if (strcmp(name, "UnknownCommand") == 0) { + err = INSTPROXY_E_UNKNOWN_COMMAND; + } else if (strcmp(name, "iTunesArtworkCaptureFailed") == 0) { + err = INSTPROXY_E_ITUNES_ARTWORK_CAPTURE_FAILED; + } else if (strcmp(name, "iTunesMetadataCaptureFailed") == 0) { + err = INSTPROXY_E_ITUNES_METADATA_CAPTURE_FAILED; + } else if (strcmp(name, "DeviceOSVersionTooLow") == 0) { + err = INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW; + } else if (strcmp(name, "DeviceFamilyNotSupported") == 0) { + err = INSTPROXY_E_DEVICE_FAMILY_NOT_SUPPORTED; + } else if (strcmp(name, "PackagePatchFailed") == 0) { + err = INSTPROXY_E_PACKAGE_PATCH_FAILED; + } else if (strcmp(name, "IncorrectArchitecture") == 0) { + err = INSTPROXY_E_INCORRECT_ARCHITECTURE; + } else if (strcmp(name, "PluginCopyFailed") == 0) { + err = INSTPROXY_E_PLUGIN_COPY_FAILED; + } else if (strcmp(name, "BreadcrumbFailed") == 0) { + err = INSTPROXY_E_BREADCRUMB_FAILED; + } else if (strcmp(name, "BreadcrumbUnlockFailed") == 0) { + err = INSTPROXY_E_BREADCRUMB_UNLOCK_FAILED; + } else if (strcmp(name, "GeoJSONCaptureFailed") == 0) { + err = INSTPROXY_E_GEOJSON_CAPTURE_FAILED; + } else if (strcmp(name, "NewsstandArtworkCaptureFailed") == 0) { + err = INSTPROXY_E_NEWSSTAND_ARTWORK_CAPTURE_FAILED; + } else if (strcmp(name, "MissingCommand") == 0) { + err = INSTPROXY_E_MISSING_COMMAND; + } else if (strcmp(name, "NotEntitled") == 0) { + err = INSTPROXY_E_NOT_ENTITLED; + } else if (strcmp(name, "MissingPackagePath") == 0) { + err = INSTPROXY_E_MISSING_PACKAGE_PATH; + } else if (strcmp(name, "MissingContainerPath") == 0) { + err = INSTPROXY_E_MISSING_CONTAINER_PATH; + } else if (strcmp(name, "MissingApplicationIdentifier") == 0) { + err = INSTPROXY_E_MISSING_APPLICATION_IDENTIFIER; + } else if (strcmp(name, "MissingAttributeValue") == 0) { + err = INSTPROXY_E_MISSING_ATTRIBUTE_VALUE; + } else if (strcmp(name, "LookupFailed") == 0) { + err = INSTPROXY_E_LOOKUP_FAILED; + } else if (strcmp(name, "DictCreationFailed") == 0) { + err = INSTPROXY_E_DICT_CREATION_FAILED; + } else if (strcmp(name, "InstallProhibited") == 0) { + err = INSTPROXY_E_INSTALL_PROHIBITED; + } else if (strcmp(name, "UninstallProhibited") == 0) { + err = INSTPROXY_E_UNINSTALL_PROHIBITED; + } else if (strcmp(name, "MissingBundleVersion") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_VERSION; + } + + return err; +} + +/** * Locks an installation_proxy client, used for thread safety. * * @param client The installation_proxy client to lock */ static void instproxy_lock(instproxy_client_t client) { - debug_info("InstallationProxy: Locked"); - g_mutex_lock(client->mutex); + debug_info("Locked"); + mutex_lock(&client->mutex); } /** * Unlocks an installation_proxy client, used for thread safety. - * + * * @param client The installation_proxy client to lock */ static void instproxy_unlock(instproxy_client_t client) { - debug_info("InstallationProxy: Unlocked"); - g_mutex_unlock(client->mutex); + debug_info("Unlocked"); + mutex_unlock(&client->mutex); } /** - * Convert a property_list_service_error_t value to an instproxy_error_t value. + * Converts a property_list_service_error_t value to an instproxy_error_t value. * Used internally to get correct error codes. * * @param err A property_list_service_error_t error code @@ -77,186 +224,80 @@ static instproxy_error_t instproxy_error(property_list_service_error_t err) return INSTPROXY_E_PLIST_ERROR; case PROPERTY_LIST_SERVICE_E_MUX_ERROR: return INSTPROXY_E_CONN_FAILED; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return INSTPROXY_E_RECEIVE_TIMEOUT; default: break; } return INSTPROXY_E_UNKNOWN_ERROR; } -/** - * Connects to the installation_proxy service on the specified device. - * - * @param device The device to connect to - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * instproxy_client_t upon successful return. - * - * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error value - * when an error occured. - */ -instproxy_error_t instproxy_client_new(idevice_t device, uint16_t port, instproxy_client_t *client) +instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, instproxy_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return INSTPROXY_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return INSTPROXY_E_CONN_FAILED; + instproxy_error_t err = instproxy_error(property_list_service_client_new(device, service, &plistclient)); + if (err != INSTPROXY_E_SUCCESS) { + return err; } instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); - client_loc->status_updater = NULL; + mutex_init(&client_loc->mutex); + client_loc->receive_status_thread = THREAD_T_NULL; *client = client_loc; return INSTPROXY_E_SUCCESS; } -/** - * Disconnects an installation_proxy client from the device and frees up the - * installation_proxy client data. - * - * @param client The installation_proxy client to disconnect and free. - * - * @return INSTPROXY_E_SUCCESS on success - * or INSTPROXY_E_INVALID_ARG if client is NULL. - */ +instproxy_error_t instproxy_client_start_service(idevice_t device, instproxy_client_t * client, const char* label) +{ + instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, INSTPROXY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(instproxy_client_new), &err); + return err; +} + instproxy_error_t instproxy_client_free(instproxy_client_t client) { if (!client) return INSTPROXY_E_INVALID_ARG; - property_list_service_client_free(client->parent); + property_list_service_client_t parent = client->parent; client->parent = NULL; - if (client->status_updater) { - debug_info("joining status_updater"); - g_thread_join(client->status_updater); - } - if (client->mutex) { - g_mutex_free(client->mutex); + if (client->receive_status_thread) { + debug_info("joining receive_status_thread"); + thread_join(client->receive_status_thread); + thread_free(client->receive_status_thread); + client->receive_status_thread = THREAD_T_NULL; } + property_list_service_client_free(parent); + mutex_destroy(&client->mutex); free(client); return INSTPROXY_E_SUCCESS; } /** - * Send a command with specified options to the device. + * Sends a command to the device. * Only used internally. * * @param client The connected installation_proxy client. * @param command The command to execute. Required. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * @param appid The ApplicationIdentifier to add or NULL if not required. - * @param package_path The installation package path or NULL if not required. * * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. + * an error occurred. */ -static instproxy_error_t instproxy_send_command(instproxy_client_t client, const char *command, plist_t client_options, const char *appid, const char *package_path) +static instproxy_error_t instproxy_send_command(instproxy_client_t client, plist_t command) { - if (!client || !command || (client_options && (plist_get_node_type(client_options) != PLIST_DICT))) + if (!client || !command) return INSTPROXY_E_INVALID_ARG; - plist_t dict = plist_new_dict(); - if (appid) { - plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); - } - if (client_options && (plist_dict_get_size(client_options) > 0)) { - plist_dict_insert_item(dict, "ClientOptions", plist_copy(client_options)); - } - plist_dict_insert_item(dict, "Command", plist_new_string(command)); - if (package_path) { - plist_dict_insert_item(dict, "PackagePath", plist_new_string(package_path)); - } - - instproxy_error_t err = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); - plist_free(dict); - return err; -} + instproxy_error_t res = instproxy_error(property_list_service_send_xml_plist(client->parent, command)); -/** - * List installed applications. This function runs synchronously. - * - * @param client The connected installation_proxy client - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid client options include: - * "ApplicationType" -> "User" - * "ApplicationType" -> "System" - * @param result Pointer that will be set to a plist that will hold an array - * of PLIST_DICT holding information about the applications found. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - */ -instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) -{ - if (!client || !client->parent || !result) - return INSTPROXY_E_INVALID_ARG; - - instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - - instproxy_lock(client); - res = instproxy_send_command(client, "Browse", client_options, NULL, NULL); if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist"); - goto leave_unlock; - } - - int browsing = 0; - plist_t apps_array = plist_new_array(); - plist_t dict = NULL; - - do { - browsing = 0; - dict = NULL; - res = instproxy_error(property_list_service_receive_plist(client->parent, &dict)); - if (res != INSTPROXY_E_SUCCESS) { - break; - } - if (dict) { - uint64_t i; - uint64_t current_amount = 0; - char *status = NULL; - plist_t camount = plist_dict_get_item(dict, "CurrentAmount"); - plist_t pstatus = plist_dict_get_item(dict, "Status"); - if (camount) { - plist_get_uint_val(camount, ¤t_amount); - } - if (current_amount > 0) { - plist_t current_list = plist_dict_get_item(dict, "CurrentList"); - for (i = 0; current_list && (i < current_amount); i++) { - plist_t item = plist_array_get_item(current_list, i); - plist_array_append_item(apps_array, plist_copy(item)); - } - } - if (pstatus) { - plist_get_string_val(pstatus, &status); - } - if (status) { - if (!strcmp(status, "BrowsingApplications")) { - browsing = 1; - } else if (!strcmp(status, "Complete")) { - debug_info("Browsing applications completed"); - res = INSTPROXY_E_SUCCESS; - } - free(status); - } - plist_free(dict); - } - } while (browsing); - - if (res == INSTPROXY_E_SUCCESS) { - *result = apps_array; + debug_info("could not send command plist, error %d", res); + return res; } -leave_unlock: - instproxy_unlock(client); return res; } @@ -269,78 +310,99 @@ leave_unlock: * * @param client The connected installation proxy client * @param status_cb Pointer to a callback function or NULL - * @param operation Operation name. Will be passed to the callback function - * in async mode or shown in debug messages in sync mode. + * @param command Operation specificiation in plist. Will be passed to the + * status_cb callback. * @param user_data Callback data passed to status_cb. */ -static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) +static instproxy_error_t instproxy_receive_status_loop(instproxy_client_t client, plist_t command, instproxy_status_cb_t status_cb, void *user_data) { instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - int ok = 1; - plist_t dict = NULL; + int complete = 0; + plist_t node = NULL; + char* command_name = NULL; + char* status_name = NULL; + char* error_name = NULL; + char* error_description = NULL; + uint64_t error_code = 0; +#ifndef STRIP_DEBUG_CODE + int percent_complete = 0; +#endif + + instproxy_command_get_name(command, &command_name); do { + /* receive status response */ instproxy_lock(client); - res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &dict, 30000)); + res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000)); instproxy_unlock(client); - if (res != INSTPROXY_E_SUCCESS) { + + /* break out if we have a communication problem */ + if (res != INSTPROXY_E_SUCCESS && res != INSTPROXY_E_RECEIVE_TIMEOUT) { debug_info("could not receive plist, error %d", res); break; } - if (dict) { - /* invoke callback function */ - if (status_cb) { - status_cb(operation, dict, user_data); + + /* parse status response */ + if (node) { + /* check status for possible error to allow reporting it and aborting it gracefully */ + res = instproxy_status_get_error(node, &error_name, &error_description, &error_code); + if (res != INSTPROXY_E_SUCCESS) { + debug_info("command: %s, error %d, code 0x%08"PRIx64", name: %s, description: \"%s\"", command_name, res, error_code, error_name, error_description ? error_description: "N/A"); + complete = 1; + } + + if (error_name) { + free(error_name); + error_name = NULL; } - /* check for 'Error', so we can abort cleanly */ - plist_t err = plist_dict_get_item(dict, "Error"); - if (err) { + + if (error_description) { + free(error_description); + error_description = NULL; + } + + /* check status from response */ + instproxy_status_get_name(node, &status_name); + if (!status_name) { + debug_info("ignoring message without Status key:"); + debug_plist(node); + } else { + if (!strcmp(status_name, "Complete")) { + complete = 1; + } else { + res = INSTPROXY_E_OP_IN_PROGRESS; + } #ifndef STRIP_DEBUG_CODE - char *err_msg = NULL; - plist_get_string_val(err, &err_msg); - if (err_msg) { - debug_info("(%s): ERROR: %s", operation, err_msg); - free(err_msg); + percent_complete = -1; + instproxy_status_get_percent_complete(node, &percent_complete); + if (percent_complete >= 0) { + debug_info("command: %s, status: %s, percent (%d%%)", command_name, status_name, percent_complete); + } else { + debug_info("command: %s, status: %s", command_name, status_name); } #endif - ok = 0; - res = INSTPROXY_E_OP_FAILED; + free(status_name); + status_name = NULL; } - /* get 'Status' */ - plist_t status = plist_dict_get_item(dict, "Status"); - if (status) { - char *status_msg = NULL; - plist_get_string_val(status, &status_msg); - if (status_msg) { - if (!strcmp(status_msg, "Complete")) { - ok = 0; - res = INSTPROXY_E_SUCCESS; - } -#ifndef STRIP_DEBUG_CODE - plist_t npercent = plist_dict_get_item(dict, "PercentComplete"); - if (npercent) { - uint64_t val = 0; - int percent; - plist_get_uint_val(npercent, &val); - percent = val; - debug_info("(%s): %s (%d%%)", operation, status_msg, percent); - } else { - debug_info("(%s): %s", operation, status_msg); - } -#endif - free(status_msg); - } + + /* invoke status callback function */ + if (status_cb) { + status_cb(command, node, user_data); } - plist_free(dict); - dict = NULL; + + plist_free(node); + node = NULL; } - } while (ok && client->parent); + } while (!complete && client->parent); + + if (command_name) + free(command_name); return res; } /** - * Internally used status updater thread function that will call the specified + * Internally used "receive status" thread function that will call the specified * callback function when status update messages (or error messages) are * received. * @@ -349,20 +411,27 @@ static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, * * @return Always NULL. */ -static gpointer instproxy_status_updater(gpointer arg) -{ +static void* instproxy_receive_status_loop_thread(void* arg) +{ struct instproxy_status_data *data = (struct instproxy_status_data*)arg; - /* run until the operation is complete or an error occurs */ - (void)instproxy_perform_operation(data->client, data->cbfunc, data->operation, data->user_data); + /* run until the command is complete or an error occurs */ + (void)instproxy_receive_status_loop(data->client, data->command, data->cbfunc, data->user_data); /* cleanup */ instproxy_lock(data->client); + debug_info("done, cleaning up."); - if (data->operation) { - free(data->operation); + + if (data->command) { + plist_free(data->command); + } + + if (data->client->receive_status_thread) { + thread_free(data->client->receive_status_thread); + data->client->receive_status_thread = THREAD_T_NULL; } - data->client->status_updater = NULL; + instproxy_unlock(data->client); free(data); @@ -370,379 +439,493 @@ static gpointer instproxy_status_updater(gpointer arg) } /** - * Internally used helper function that creates a status updater thread which - * will call the passed callback function when status updates occur. - * If status_cb is NULL no thread will be created, but the operation will - * run synchronously until it completes or an error occurs. + * Internally used helper function that creates a "receive status" thread which + * will call the passed callback function when a status is received. + * + * If async is 0 no thread will be created and the command will run + * synchronously until it completes or an error occurs. * * @param client The connected installation proxy client - * @param status_cb Pointer to a callback function or NULL - * @param operation Operation name. Will be passed to the callback function + * @param command Operation name. Will be passed to the callback function * in async mode or shown in debug messages in sync mode. + * @param async A boolean indicating if receive loop should be run + * asynchronously or block. + * @param status_cb Pointer to a callback function or NULL. * @param user_data Callback data passed to status_cb. * * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or - * when the operation completed successfully (sync). - * An INSTPROXY_E_* error value is returned if an error occured. + * when the command completed successfully (sync). + * An INSTPROXY_E_* error value is returned if an error occurred. */ -static instproxy_error_t instproxy_create_status_updater(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) +static instproxy_error_t instproxy_receive_status_loop_with_callback(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data) { + if (!client || !client->parent || !command) { + return INSTPROXY_E_INVALID_ARG; + } + + if (client->receive_status_thread) { + return INSTPROXY_E_OP_IN_PROGRESS; + } + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - if (status_cb) { + if (async == INSTPROXY_COMMAND_TYPE_ASYNC) { /* async mode */ struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data)); if (data) { data->client = client; + data->command = plist_copy(command); data->cbfunc = status_cb; - data->operation = strdup(operation); data->user_data = user_data; - client->status_updater = g_thread_create(instproxy_status_updater, data, TRUE, NULL); - if (client->status_updater) { + if (thread_new(&client->receive_status_thread, instproxy_receive_status_loop_thread, data) == 0) { res = INSTPROXY_E_SUCCESS; } } } else { - /* sync mode */ - res = instproxy_perform_operation(client, NULL, operation, NULL); + /* sync mode as a fallback */ + res = instproxy_receive_status_loop(client, command, status_cb, user_data); } + return res; } - /** - * Internal function used by instproxy_install and instproxy_upgrade. + * Internal core function to send a command and process the response. * * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param command The command to execute. + * @param command The command specification dictionary. + * @param async A boolean indicating whether the receive loop should be run + * asynchronously or block until completing the command. + * @param status_cb Callback function to call if a command status is received. * @param user_data Callback data passed to status_cb. * * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. + * an error occurred. */ -static instproxy_error_t instproxy_install_or_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, const char *command, void *user_data) +static instproxy_error_t instproxy_perform_command(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data) { - if (!client || !client->parent || !pkg_path) { + if (!client || !client->parent || !command) { return INSTPROXY_E_INVALID_ARG; } - if (client->status_updater) { + + if (client->receive_status_thread) { return INSTPROXY_E_OP_IN_PROGRESS; } + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + /* send command */ instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, command, client_options, NULL, pkg_path); + res = instproxy_send_command(client, command); instproxy_unlock(client); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; - } + /* loop until status or error is received */ + res = instproxy_receive_status_loop_with_callback(client, command, async, status_cb, user_data); - return instproxy_create_status_updater(client, status_cb, command, user_data); + return res; } -/** - * Install an application on the device. - * - * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "iTunesMetadata" -> PLIST_DATA - * "ApplicationSINF" -> PLIST_DATA - * "PackageType" -> "Developer" - * If PackageType -> Developer is specified, then pkg_path points to - * an .app directory instead of an install package. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_browse_with_callback(instproxy_client_t client, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) { - return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Install", user_data); + if (!client || !client->parent || !status_cb) + return INSTPROXY_E_INVALID_ARG; + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Browse")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, (void*)user_data); + + plist_free(command); + + return res; } -/** - * Upgrade an application on the device. This function is nearly the same as - * instproxy_install; the difference is that the installation progress on the - * device is faster if the application is already installed. - * - * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "iTunesMetadata" -> PLIST_DATA - * "ApplicationSINF" -> PLIST_DATA - * "PackageType" -> "Developer" - * If PackageType -> Developer is specified, then pkg_path points to - * an .app directory instead of an install package. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +static void instproxy_append_current_list_to_result_cb(plist_t command, plist_t status, void *user_data) { - return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Upgrade", user_data); + plist_t *result_array = (plist_t*)user_data; + uint64_t current_amount = 0; + plist_t current_list = NULL; + uint64_t i; + + instproxy_status_get_current_list(status, NULL, NULL, ¤t_amount, ¤t_list); + + debug_info("current_amount: %d", current_amount); + + if (current_amount > 0) { + for (i = 0; current_list && (i < current_amount); i++) { + plist_t item = plist_array_get_item(current_list, i); + plist_array_append_item(*result_array, plist_copy(item)); + } + } + + if (current_list) + plist_free(current_list); } -/** - * Uninstall an application from the device. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to uninstall - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) { - if (!client || !client->parent || !appid) { + if (!client || !client->parent || !result) return INSTPROXY_E_INVALID_ARG; - } - - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; - } instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); - plist_dict_insert_item(dict, "Command", plist_new_string("Uninstall")); - instproxy_lock(client); - res = instproxy_send_command(client, "Uninstall", client_options, appid, NULL); - instproxy_unlock(client); + plist_t result_array = plist_new_array(); - plist_free(dict); + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Browse")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_append_current_list_to_result_cb, (void*)&result_array); + + if (res == INSTPROXY_E_SUCCESS) { + *result = result_array; + } else { + plist_free(result_array); } - return instproxy_create_status_updater(client, status_cb, "Uninstall", user_data); + plist_free(command); + + return res; } -/** - * List archived applications. This function runs synchronously. - * - * @see instproxy_archive - * - * @param client The connected installation_proxy client - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param result Pointer that will be set to a plist containing a PLIST_DICT - * holding information about the archived applications found. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - */ -instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) +static void instproxy_copy_lookup_result_cb(plist_t command, plist_t status, void *user_data) +{ + plist_t* result = (plist_t*)user_data; + + plist_t node = plist_dict_get_item(status, "LookupResult"); + if (node) { + *result = plist_copy(node); + } +} + +instproxy_error_t instproxy_lookup(instproxy_client_t client, const char** appids, plist_t client_options, plist_t *result) { + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + int i = 0; + plist_t lookup_result = NULL; + plist_t command = NULL; + plist_t appid_array = NULL; + plist_t node = NULL; + if (!client || !client->parent || !result) return INSTPROXY_E_INVALID_ARG; - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "LookupArchives", client_options, NULL, NULL); + command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Lookup")); + if (client_options) { + node = plist_copy(client_options); + } else if (appids) { + node = plist_new_dict(); + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - goto leave_unlock; + /* add bundle identifiers to client options */ + if (appids) { + appid_array = plist_new_array(); + while (appids[i]) { + plist_array_append_item(appid_array, plist_new_string(appids[i])); + i++; + } + plist_dict_set_item(node, "BundleIDs", appid_array); } - res = instproxy_error(property_list_service_receive_plist(client->parent, result)); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not receive plist, error %d", res); - goto leave_unlock; + if (node) { + plist_dict_set_item(command, "ClientOptions", node); } - res = INSTPROXY_E_SUCCESS; + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); + + if (res == INSTPROXY_E_SUCCESS) { + *result = lookup_result; + } else { + plist_free(lookup_result); + } + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Install")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Upgrade")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Uninstall")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("LookupArchives")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)result); + + plist_free(command); -leave_unlock: - instproxy_unlock(client); return res; } -/** - * Archive an application on the device. - * This function tells the device to make an archive of the specified - * application. This results in the device creating a ZIP archive in the - * 'ApplicationArchives' directory and uninstalling the application. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to archive. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "SkipUninstall" -> Boolean - * "ArchiveType" -> "ApplicationOnly" - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) { - if (!client || !client->parent || !appid) + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Archive")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Restore")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("RemoveArchive")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, const char** capabilities, plist_t client_options, plist_t *result) +{ + if (!client || !capabilities || !result) return INSTPROXY_E_INVALID_ARG; - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; + plist_t lookup_result = NULL; + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("CheckCapabilitiesMatch")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + if (capabilities) { + int i = 0; + plist_t capabilities_array = plist_new_array(); + while (capabilities[i]) { + plist_array_append_item(capabilities_array, plist_new_string(capabilities[i])); + i++; + } + plist_dict_set_item(command, "Capabilities", capabilities_array); } - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "Archive", client_options, appid, NULL); - instproxy_unlock(client); + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (res == INSTPROXY_E_SUCCESS) { + *result = lookup_result; + } else { + plist_free(lookup_result); } - return instproxy_create_status_updater(client, status_cb, "Archive", user_data); + + plist_free(command); + + return res; } -/** - * Restore a previously archived application on the device. - * This function is the counterpart to instproxy_archive. - * @see instproxy_archive - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to restore. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code) { - if (!client || !client->parent || !appid) + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + if (!status || !name) return INSTPROXY_E_INVALID_ARG; - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; + plist_t node = plist_dict_get_item(status, "Error"); + if (node) { + plist_get_string_val(node, name); + } else { + /* no error here */ + res = INSTPROXY_E_SUCCESS; } - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "Restore", client_options, appid, NULL); - instproxy_unlock(client); + if (code != NULL) { + *code = 0; + node = plist_dict_get_item(status, "ErrorDetail"); + if (node) { + plist_get_uint_val(node, code); + *code &= 0xffffffff; + } + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (description != NULL) { + node = plist_dict_get_item(status, "ErrorDescription"); + if (node) { + plist_get_string_val(node, description); + } + } + + if (*name) { + res = instproxy_strtoerr(*name); } - return instproxy_create_status_updater(client, status_cb, "Restore", user_data); + + return res; } -/** - * Removes a previously archived application from the device. - * This function removes the ZIP archive from the 'ApplicationArchives' - * directory. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the archived app to remove. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so passing NULL is fine. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +void instproxy_status_get_name(plist_t status, char **name) { - if (!client || !client->parent || !appid) - return INSTPROXY_E_INVALID_ARG; + if (name) { + plist_t node = plist_dict_get_item(status, "Status"); + if (node) { + plist_get_string_val(node, name); + } else { + *name = NULL; + } + } +} - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; +void instproxy_status_get_percent_complete(plist_t status, int *percent) +{ + uint64_t val = 0; + if (percent) { + plist_t node = plist_dict_get_item(status, "PercentComplete"); + if (node) { + plist_get_uint_val(node, &val); + *percent = val; + } } +} - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "RemoveArchive", client_options, appid, NULL); - instproxy_unlock(client); +void instproxy_status_get_current_list(plist_t status, uint64_t* total, uint64_t* current_index, uint64_t* current_amount, plist_t* list) +{ + plist_t node = NULL; + + if (status && plist_get_node_type(status) == PLIST_DICT) { + /* command specific logic: parse browsed list */ + if (list != NULL) { + node = plist_dict_get_item(status, "CurrentList"); + if (node) { + *current_amount = plist_array_get_size(node); + *list = plist_copy(node); + } + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (total != NULL) { + node = plist_dict_get_item(status, "Total"); + if (node) { + plist_get_uint_val(node, total); + } + } + + if (current_amount != NULL) { + node = plist_dict_get_item(status, "CurrentAmount"); + if (node) { + plist_get_uint_val(node, current_amount); + } + } + + if (current_index != NULL) { + node = plist_dict_get_item(status, "CurrentIndex"); + if (node) { + plist_get_uint_val(node, current_index); + } + } } - return instproxy_create_status_updater(client, status_cb, "RemoveArchive", user_data); } -/** - * Create a new client_options plist. - * - * @return A new plist_t of type PLIST_DICT. - */ -plist_t instproxy_client_options_new() +void instproxy_command_get_name(plist_t command, char** name) +{ + if (name) { + plist_t node = plist_dict_get_item(command, "Command"); + if (node) { + plist_get_string_val(node, name); + } else { + *name = NULL; + } + } +} + +plist_t instproxy_client_options_new(void) { return plist_new_dict(); } -/** - * Add one or more new key:value pairs to the given client_options. - * - * @param client_options The client options to modify. - * @param ... KEY, VALUE, [KEY, VALUE], NULL - * - * @note The keys and values passed are expected to be strings, except for - * "ApplicationSINF" and "iTunesMetadata" expecting a plist node of type - * PLIST_DATA as value, or "SkipUninstall" needing int as value. - */ void instproxy_client_options_add(plist_t client_options, ...) { if (!client_options) return; + va_list args; va_start(args, client_options); char *arg = va_arg(args, char*); @@ -750,21 +933,21 @@ void instproxy_client_options_add(plist_t client_options, ...) char *key = strdup(arg); if (!strcmp(key, "SkipUninstall")) { int intval = va_arg(args, int); - plist_dict_insert_item(client_options, key, plist_new_bool(intval)); - } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata")) { + plist_dict_set_item(client_options, key, plist_new_bool(intval)); + } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata") || !strcmp(key, "ReturnAttributes") || !strcmp(key, "BundleIDs")) { plist_t plistval = va_arg(args, plist_t); if (!plistval) { free(key); break; } - plist_dict_insert_item(client_options, key, plist_copy(plistval)); + plist_dict_set_item(client_options, key, plist_copy(plistval)); } else { char *strval = va_arg(args, char*); if (!strval) { free(key); break; } - plist_dict_insert_item(client_options, key, plist_new_string(strval)); + plist_dict_set_item(client_options, key, plist_new_string(strval)); } free(key); arg = va_arg(args, char*); @@ -772,15 +955,106 @@ void instproxy_client_options_add(plist_t client_options, ...) va_end(args); } -/** - * Free client_options plist. - * - * @param client_options The client options plist to free. Does nothing if NULL - * is passed. - */ +void instproxy_client_options_set_return_attributes(plist_t client_options, ...) +{ + if (!client_options) + return; + + plist_t return_attributes = plist_new_array(); + + va_list args; + va_start(args, client_options); + char *arg = va_arg(args, char*); + while (arg) { + char *attribute = strdup(arg); + plist_array_append_item(return_attributes, plist_new_string(attribute)); + free(attribute); + arg = va_arg(args, char*); + } + va_end(args); + + plist_dict_set_item(client_options, "ReturnAttributes", return_attributes); +} + void instproxy_client_options_free(plist_t client_options) { if (client_options) { plist_free(client_options); } } + +instproxy_error_t instproxy_client_get_path_for_bundle_identifier(instproxy_client_t client, const char* bundle_id, char** path) +{ + if (!client || !client->parent || !bundle_id) + return INSTPROXY_E_INVALID_ARG; + + plist_t apps = NULL; + + // create client options for any application types + plist_t client_opts = instproxy_client_options_new(); + instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL); + + // only return attributes we need + instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", "Path", NULL); + + // only query for specific appid + const char* appids[] = {bundle_id, NULL}; + + // query device for list of apps + instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps); + + instproxy_client_options_free(client_opts); + + if (ierr != INSTPROXY_E_SUCCESS) { + return ierr; + } + + plist_t app_found = plist_access_path(apps, 1, bundle_id); + if (!app_found) { + if (apps) + plist_free(apps); + *path = NULL; + return INSTPROXY_E_OP_FAILED; + } + + char* path_str = NULL; + plist_t path_p = plist_dict_get_item(app_found, "Path"); + if (path_p) { + plist_get_string_val(path_p, &path_str); + } + + char* exec_str = NULL; + plist_t exec_p = plist_dict_get_item(app_found, "CFBundleExecutable"); + if (exec_p) { + plist_get_string_val(exec_p, &exec_str); + } + + if (!path_str) { + debug_info("app path not found"); + return INSTPROXY_E_OP_FAILED; + } + + if (!exec_str) { + debug_info("bundle executable not found"); + return INSTPROXY_E_OP_FAILED; + } + + plist_free(apps); + + char* ret = (char*)malloc(strlen(path_str) + 1 + strlen(exec_str) + 1); + strcpy(ret, path_str); + strcat(ret, "/"); + strcat(ret, exec_str); + + *path = ret; + + if (path_str) { + free(path_str); + } + + if (exec_str) { + free(exec_str); + } + + return INSTPROXY_E_SUCCESS; +} diff --git a/src/installation_proxy.h b/src/installation_proxy.h index b497d62..5bdbb71 100644 --- a/src/installation_proxy.h +++ b/src/installation_proxy.h @@ -2,34 +2,36 @@ * installation_proxy.h * com.apple.mobile.installation_proxy service header file. * - * Copyright (c) 2009 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2013 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IINSTALLATION_PROXY_H -#define IINSTALLATION_PROXY_H -#include <glib.h> +#ifndef __INSTALLATION_PROXY_H +#define __INSTALLATION_PROXY_H +#include "idevice.h" #include "libimobiledevice/installation_proxy.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct instproxy_client_private { property_list_service_client_t parent; - GMutex *mutex; - GThread *status_updater; + mutex_t mutex; + THREAD_T receive_status_thread; }; #endif diff --git a/src/libimobiledevice-1.0.pc.in b/src/libimobiledevice-1.0.pc.in new file mode 100644 index 0000000..f00c392 --- /dev/null +++ b/src/libimobiledevice-1.0.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: @PACKAGE_NAME@ +Description: A library to communicate with services running on Apple iOS devices. +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -limobiledevice-1.0 +Cflags: -I${includedir} +Requires: libplist-2.0 >= @LIBPLIST_VERSION@ +Requires.private: libusbmuxd-2.0 >= @LIBUSBMUXD_VERSION@ libimobiledevice-glue-1.0 >= @LIMD_GLUE_VERSION@ @ssl_requires@ diff --git a/src/lockdown-cu.c b/src/lockdown-cu.c new file mode 100644 index 0000000..1afc2c5 --- /dev/null +++ b/src/lockdown-cu.c @@ -0,0 +1,1193 @@ +/* + * lockdown-cu.c + * com.apple.mobile.lockdownd service CU additions + * + * Copyright (c) 2021 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <plist/plist.h> + +#include "idevice.h" +#include "lockdown.h" +#include "common/debug.h" + +#ifdef HAVE_WIRELESS_PAIRING + +#include <libimobiledevice-glue/utils.h> +#include <libimobiledevice-glue/socket.h> +#include <libimobiledevice-glue/opack.h> +#include <libimobiledevice-glue/tlv.h> + +#if defined(HAVE_OPENSSL) +#include <openssl/hmac.h> +#include <openssl/evp.h> +#include <openssl/rand.h> +#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2030200fL) +#include <openssl/chacha.h> +#include <openssl/poly1305.h> +#endif +#elif defined(HAVE_GCRYPT) +#include <gcrypt.h> +#elif defined(HAVE_MBEDTLS) +#include <mbedtls/md.h> +#include <mbedtls/chachapoly.h> +#endif + +#ifdef __APPLE__ +#include <sys/sysctl.h> +#include <SystemConfiguration/SystemConfiguration.h> +#include <CoreFoundation/CoreFoundation.h> +#include <TargetConditionals.h> +#endif + +#include "property_list_service.h" +#include "common/userpref.h" + +#include "endianness.h" + +#include "srp.h" +#include "ed25519.h" + +/* {{{ SRP6a parameters */ +static const unsigned char kSRPModulus3072[384] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, + 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, + 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, + 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37, + 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, + 0xee, 0x38, 0x6b, 0xfb, 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, 0x1f, 0xe6, + 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, + 0x98, 0xda, 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, 0xfd, 0x24, 0xcf, 0x5f, + 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, + 0xf1, 0x74, 0x6c, 0x08, 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, 0xce, 0x3b, + 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, + 0xb5, 0xc5, 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xaa, 0xc4, 0x2d, 0xad, 0x33, 0x17, 0x0d, 0x04, 0x50, 0x7a, 0x33, + 0xa8, 0x55, 0x21, 0xab, 0xdf, 0x1c, 0xba, 0x64, 0xec, 0xfb, 0x85, 0x04, 0x58, 0xdb, 0xef, 0x0a, + 0x8a, 0xea, 0x71, 0x57, 0x5d, 0x06, 0x0c, 0x7d, 0xb3, 0x97, 0x0f, 0x85, 0xa6, 0xe1, 0xe4, 0xc7, + 0xab, 0xf5, 0xae, 0x8c, 0xdb, 0x09, 0x33, 0xd7, 0x1e, 0x8c, 0x94, 0xe0, 0x4a, 0x25, 0x61, 0x9d, + 0xce, 0xe3, 0xd2, 0x26, 0x1a, 0xd2, 0xee, 0x6b, 0xf1, 0x2f, 0xfa, 0x06, 0xd9, 0x8a, 0x08, 0x64, + 0xd8, 0x76, 0x02, 0x73, 0x3e, 0xc8, 0x6a, 0x64, 0x52, 0x1f, 0x2b, 0x18, 0x17, 0x7b, 0x20, 0x0c, + 0xbb, 0xe1, 0x17, 0x57, 0x7a, 0x61, 0x5d, 0x6c, 0x77, 0x09, 0x88, 0xc0, 0xba, 0xd9, 0x46, 0xe2, + 0x08, 0xe2, 0x4f, 0xa0, 0x74, 0xe5, 0xab, 0x31, 0x43, 0xdb, 0x5b, 0xfc, 0xe0, 0xfd, 0x10, 0x8e, + 0x4b, 0x82, 0xd1, 0x20, 0xa9, 0x3a, 0xd2, 0xca, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const unsigned char kSRPGenerator5 = 5; +/* }}} */ + +/* {{{ HKDF */ +#if defined(HAVE_OPENSSL) +#define MD_ALGO_SHA512 EVP_sha512() +typedef const EVP_MD* MD_ALGO_TYPE_T; +#define MD_ALGO_DIGEST_SIZE EVP_MD_size +#define MD_MAX_DIGEST_SIZE EVP_MAX_MD_SIZE + +#elif defined(HAVE_GCRYPT) +#define MD_ALGO_SHA512 GCRY_MD_SHA512 +typedef int MD_ALGO_TYPE_T; +#define MD_ALGO_DIGEST_SIZE gcry_md_get_algo_dlen +#define MD_MAX_DIGEST_SIZE 64 + +static void HMAC(MD_ALGO_TYPE_T md, unsigned char* key, unsigned int key_len, unsigned char* data, unsigned int data_len, unsigned char* out, unsigned int* out_len) +{ + gcry_md_hd_t hd; + if (gcry_md_open(&hd, md, GCRY_MD_FLAG_HMAC)) { + debug_info("gcry_md_open() failed"); + return; + } + if (gcry_md_setkey(hd, key, key_len)) { + gcry_md_close (hd); + debug_info("gcry_md_setkey() failed"); + return; + } + gcry_md_write(hd, data, data_len); + + unsigned char* digest = gcry_md_read(hd, md); + if (!digest) { + gcry_md_close(hd); + debug_info("gcry_md_read() failed"); + return; + } + + *out_len = gcry_md_get_algo_dlen(md); + memcpy(out, digest, *out_len); + gcry_md_close(hd); +} +#elif defined(HAVE_MBEDTLS) +#define MD_ALGO_SHA512 MBEDTLS_MD_SHA512 +typedef mbedtls_md_type_t MD_ALGO_TYPE_T; +#define MD_ALGO_DIGEST_SIZE(x) mbedtls_md_get_size(mbedtls_md_info_from_type(x)) +#define MD_MAX_DIGEST_SIZE MBEDTLS_MD_MAX_SIZE + +static void HMAC(MD_ALGO_TYPE_T md, unsigned char* key, unsigned int key_len, unsigned char* data, unsigned int data_len, unsigned char* out, unsigned int* out_len) +{ + mbedtls_md_context_t mdctx; + mbedtls_md_init(&mdctx); + int mr = mbedtls_md_setup(&mdctx, mbedtls_md_info_from_type(md), 1); + if (mr != 0) { + debug_info("mbedtls_md_setup() failed: %d", mr); + return; + } + + mr = mbedtls_md_hmac_starts(&mdctx, key, key_len); + if (mr != 0) { + mbedtls_md_free(&mdctx); + debug_info("mbedtls_md_hmac_starts() failed: %d", mr); + return; + } + + mbedtls_md_hmac_update(&mdctx, data, data_len); + + mr = mbedtls_md_hmac_finish(&mdctx, out); + if (mr == 0) { + *out_len = mbedtls_md_get_size(mbedtls_md_info_from_type(md)); + } else { + debug_info("mbedtls_md_hmac_finish() failed: %d", mr); + } + mbedtls_md_free(&mdctx); +} +#endif + +static void hkdf_md_extract(MD_ALGO_TYPE_T md, unsigned char* salt, unsigned int salt_len, unsigned char* input_key_material, unsigned int input_key_material_len, unsigned char* out, unsigned int* out_len) +{ + unsigned char empty_salt[MD_MAX_DIGEST_SIZE]; + if (!md || !out || !out_len || !*out_len) return; + if (salt_len == 0) { + salt_len = MD_ALGO_DIGEST_SIZE(md); + salt = (unsigned char*)empty_salt; + } + HMAC(md, salt, salt_len, input_key_material, input_key_material_len, out, out_len); +} + +static void hkdf_md_expand(MD_ALGO_TYPE_T md, unsigned char* prk, unsigned int prk_len, unsigned char* info, unsigned int info_len, unsigned char* out, unsigned int* out_len) +{ + if (!md || !out || !out_len || !*out_len) return; + unsigned int md_size = MD_ALGO_DIGEST_SIZE(md); + if (*out_len > 255 * md_size) { + *out_len = 0; + return; + } + int blocks_needed = (*out_len) / md_size; + if (((*out_len) % md_size) != 0) blocks_needed++; + unsigned int okm_len = 0; + unsigned char okm_block[MD_MAX_DIGEST_SIZE]; + unsigned int okm_block_len = 0; + int i; + for (i = 0; i < blocks_needed; i++) { + unsigned int output_block_len = okm_block_len + info_len + 1; + unsigned char* output_block = malloc(output_block_len); + if (okm_block_len > 0) { + memcpy(output_block, okm_block, okm_block_len); + } + memcpy(output_block + okm_block_len, info, info_len); + output_block[okm_block_len + info_len] = (uint8_t)(i+1); + + HMAC(md, prk, prk_len, output_block, output_block_len, okm_block, &okm_block_len); + if (okm_len < *out_len) { + memcpy(out + okm_len, okm_block, (okm_len + okm_block_len > *out_len) ? *out_len - okm_len : okm_block_len); + } + okm_len += okm_block_len; + free(output_block); + } +} + +static void hkdf_md(MD_ALGO_TYPE_T md, unsigned char* salt, unsigned int salt_len, unsigned char* info, unsigned int info_len, unsigned char* initial_key_material, unsigned int initial_key_material_size, unsigned char* out, unsigned int *out_len) +{ + if (!md || !initial_key_material || !out || !out_len || !*out_len) return; + + unsigned char prk[MD_MAX_DIGEST_SIZE]; + unsigned int prk_len = MD_ALGO_DIGEST_SIZE(md); + + hkdf_md_extract(md, salt, salt_len, initial_key_material, initial_key_material_size, prk, &prk_len); + if (prk_len > 0) { + hkdf_md_expand(md, prk, prk_len, info, info_len, out, out_len); + } else { + *out_len = 0; + } +} +/* }}} */ + +/* {{{ chacha20 poly1305 encryption/decryption */ +#if defined(HAVE_OPENSSL) && defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2030200fL) +/* {{{ From: OpenBSD's e_chacha20poly1305.c */ +/* + * Copyright (c) 2015 Reyk Floter <reyk@openbsd.org> + * Copyright (c) 2014, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +static void +poly1305_update_with_length(poly1305_state *poly1305, + const unsigned char *data, size_t data_len) +{ + size_t j = data_len; + unsigned char length_bytes[8]; + unsigned i; + + for (i = 0; i < sizeof(length_bytes); i++) { + length_bytes[i] = j; + j >>= 8; + } + + if (data != NULL) + CRYPTO_poly1305_update(poly1305, data, data_len); + CRYPTO_poly1305_update(poly1305, length_bytes, sizeof(length_bytes)); +} + +static void +poly1305_update_with_pad16(poly1305_state *poly1305, + const unsigned char *data, size_t data_len) +{ + static const unsigned char zero_pad16[16]; + size_t pad_len; + + CRYPTO_poly1305_update(poly1305, data, data_len); + + /* pad16() is defined in RFC 7539 2.8.1. */ + if ((pad_len = data_len % 16) == 0) + return; + + CRYPTO_poly1305_update(poly1305, zero_pad16, 16 - pad_len); +} +/* }}} */ +#endif + +static void chacha20_poly1305_encrypt_96(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ +#if defined(HAVE_OPENSSL) +#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL) +#if (LIBRESSL_VERSION_NUMBER >= 0x2040000fL) + const EVP_AEAD *aead = EVP_aead_chacha20_poly1305(); + EVP_AEAD_CTX ctx; + EVP_AEAD_CTX_init(&ctx, aead, key, EVP_AEAD_key_length(aead), EVP_AEAD_DEFAULT_TAG_LENGTH, NULL); + EVP_AEAD_CTX_seal(&ctx, out, out_len, *out_len, nonce, 12, in, in_len, ad, ad_len); +#else + unsigned char poly1305_key[32]; + poly1305_state poly1305; + uint64_t ctr = (uint64_t)(nonce[0] | nonce[1] << 8 | nonce[2] << 16 | nonce[3] << 24) << 32; + const unsigned char* iv = nonce + 4; + + memset(poly1305_key, 0, sizeof(poly1305_key)); + CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key, iv, ctr); + + CRYPTO_poly1305_init(&poly1305, poly1305_key); + poly1305_update_with_pad16(&poly1305, ad, ad_len); + CRYPTO_chacha_20(out, in, in_len, key, iv, ctr + 1); + poly1305_update_with_pad16(&poly1305, out, in_len); + poly1305_update_with_length(&poly1305, NULL, ad_len); + poly1305_update_with_length(&poly1305, NULL, in_len); + + CRYPTO_poly1305_finish(&poly1305, out + in_len); + + *out_len = in_len + 16; +#endif +#elif defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L) + int outl = 0; + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce); + EVP_EncryptUpdate(ctx, out, &outl, in, in_len); + *out_len = outl; + outl = 0; + EVP_EncryptFinal_ex(ctx, out + *out_len, &outl); + *out_len += outl; + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, out + *out_len); + EVP_CIPHER_CTX_free(ctx); + *out_len += 16; +#else +#error Please use a newer version of OpenSSL (>= 1.1.0) +#endif +#elif defined(HAVE_GCRYPT) +#if defined(GCRYPT_VERSION_NUMBER) && (GCRYPT_VERSION_NUMBER >= 0x010700) + gcry_cipher_hd_t hd; + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0)) { + debug_info("gcry_cipher_open() failed"); + return; + } + gcry_cipher_setkey(hd, key, 32); + gcry_cipher_setiv(hd, nonce, 12); + gcry_cipher_authenticate(hd, ad, ad_len); + *out_len = in_len + 16; + if (gcry_cipher_encrypt(hd, out, *out_len, in, in_len)) { + *out_len = 0; + } + gcry_cipher_gettag(hd, out+in_len, 16); + gcry_cipher_close(hd); +#else +#error Please use a newer version of libgcrypt (>= 1.7.0) +#endif +#elif defined (HAVE_MBEDTLS) + mbedtls_chachapoly_context ctx; + mbedtls_chachapoly_init(&ctx); + mbedtls_chachapoly_setkey(&ctx, key); + if (mbedtls_chachapoly_encrypt_and_tag(&ctx, in_len, nonce, ad, ad_len, in, out, out+in_len) != 0) { + *out_len = 0; + } + mbedtls_chachapoly_free(&ctx); +#else +#error chacha20_poly1305_encrypt_96 is not implemented +#endif +} + +static void chacha20_poly1305_encrypt_64(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ + unsigned char _nonce[12]; + *(uint32_t*)(&_nonce[0]) = 0; + memcpy(&_nonce[4], nonce, 8); + chacha20_poly1305_encrypt_96(key, _nonce, ad, ad_len, in, in_len, out, out_len); +} + +static void chacha20_poly1305_decrypt_96(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ +#if defined(HAVE_OPENSSL) +#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL) +#if (LIBRESSL_VERSION_NUMBER >= 0x2040000fL) + const EVP_AEAD *aead = EVP_aead_chacha20_poly1305(); + EVP_AEAD_CTX ctx; + EVP_AEAD_CTX_init(&ctx, aead, key, EVP_AEAD_key_length(aead), EVP_AEAD_DEFAULT_TAG_LENGTH, NULL); + EVP_AEAD_CTX_open(&ctx, out, out_len, *out_len, nonce, 12, in, in_len, ad, ad_len); +#else + unsigned char mac[16]; + unsigned char poly1305_key[32]; + poly1305_state poly1305; + size_t plaintext_len = in_len - 16; + uint64_t ctr = (uint64_t)(nonce[0] | nonce[1] << 8 | nonce[2] << 16 | nonce[3] << 24) << 32; + const unsigned char *iv = nonce + 4; + + memset(poly1305_key, 0, sizeof(poly1305_key)); + CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key, iv, ctr); + + CRYPTO_poly1305_init(&poly1305, poly1305_key); + poly1305_update_with_pad16(&poly1305, ad, ad_len); + poly1305_update_with_pad16(&poly1305, in, plaintext_len); + poly1305_update_with_length(&poly1305, NULL, ad_len); + poly1305_update_with_length(&poly1305, NULL, plaintext_len); + + CRYPTO_poly1305_finish(&poly1305, mac); + + if (memcmp(mac, in + plaintext_len, 16) != 0) { + *out_len = 0; + return; + } + + CRYPTO_chacha_20(out, in, plaintext_len, key, iv, ctr + 1); + *out_len = plaintext_len; +#endif +#elif defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L) + int outl = 0; + size_t plaintext_len = in_len - 16; + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, in + plaintext_len); + EVP_DecryptUpdate(ctx, out, &outl, in, plaintext_len); + *out_len = outl; + outl = 0; + if (EVP_DecryptFinal_ex(ctx, out + *out_len, &outl) == 1) { + *out_len += outl; + } else { + *out_len = 0; + } + EVP_CIPHER_CTX_free(ctx); +#else +#error Please use a newer version of OpenSSL (>= 1.1.0) +#endif +#elif defined(HAVE_GCRYPT) +#if defined(GCRYPT_VERSION_NUMBER) && (GCRYPT_VERSION_NUMBER >= 0x010700) + gcry_cipher_hd_t hd; + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0)) { + debug_info("gcry_cipher_open() failed"); + return; + } + gcry_cipher_setkey(hd, key, 32); + gcry_cipher_setiv(hd, nonce, 12); + gcry_cipher_authenticate(hd, ad, ad_len); + unsigned int plaintext_len = in_len - 16; + gcry_cipher_decrypt(hd, out, *out_len, in, plaintext_len); + if (gcry_cipher_checktag(hd, in + plaintext_len, 16) == 0) { + *out_len = plaintext_len; + } else { + *out_len = 0; + } + gcry_cipher_close(hd); +#else +#error Please use a newer version of libgcrypt (>= 1.7.0) +#endif +#elif defined(HAVE_MBEDTLS) + mbedtls_chachapoly_context ctx; + mbedtls_chachapoly_init(&ctx); + mbedtls_chachapoly_setkey(&ctx, key); + unsigned int plaintext_len = in_len - 16; + if (mbedtls_chachapoly_auth_decrypt(&ctx, plaintext_len, nonce, ad, ad_len, in + plaintext_len, in, out) == 0) { + *out_len = plaintext_len; + } else { + *out_len = 0; + } + mbedtls_chachapoly_free(&ctx); +#else +#error chacha20_poly1305_decrypt_96 is not implemented +#endif +} + +static void chacha20_poly1305_decrypt_64(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ + unsigned char _nonce[12]; + *(uint32_t*)(&_nonce[0]) = 0; + memcpy(&_nonce[4], nonce, 8); + chacha20_poly1305_decrypt_96(key, _nonce, ad, ad_len, in, in_len, out, out_len); +} +/* }}} */ + +#define PAIRING_ERROR(x) \ + debug_info(x); \ + if (pairing_callback) { \ + pairing_callback(LOCKDOWN_CU_PAIRING_ERROR, cb_user_data, (char*)x, NULL); \ + } + +#define PAIRING_ERROR_FMT(...) \ + sprintf(tmp, __VA_ARGS__); \ + debug_info(tmp); \ + if (pairing_callback) { \ + pairing_callback(LOCKDOWN_CU_PAIRING_ERROR, cb_user_data, tmp, NULL); \ + } + +#endif /* HAVE_WIRELESS_PAIRING */ + +lockdownd_error_t lockdownd_cu_pairing_create(lockdownd_client_t client, lockdownd_cu_pairing_cb_t pairing_callback, void* cb_user_data, plist_t host_info, plist_t acl) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client || !pairing_callback || (host_info && plist_get_node_type(host_info) != PLIST_DICT) || (acl && plist_get_node_type(acl) != PLIST_DICT)) + return LOCKDOWN_E_INVALID_ARG; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + if (client->device && client->device->version == 0) { + plist_t p_version = NULL; + if (lockdownd_get_value(client, NULL, "ProductVersion", &p_version) == LOCKDOWN_E_SUCCESS) { + int vers[3] = {0, 0, 0}; + char *s_version = NULL; + plist_get_string_val(p_version, &s_version); + if (s_version && sscanf(s_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) { + client->device->version = DEVICE_VERSION(vers[0], vers[1], vers[2]); + } + free(s_version); + } + plist_free(p_version); + } + + char* pairing_uuid = NULL; + if (host_info) { + plist_t accountid = plist_dict_get_item(host_info, "accountID"); + if (accountid && plist_get_node_type(accountid) == PLIST_STRING) { + plist_get_string_val(accountid, &pairing_uuid); + } + } + if (!pairing_uuid) { + userpref_read_system_buid(&pairing_uuid); + } + if (!pairing_uuid) { + pairing_uuid = generate_uuid(); + } + unsigned int pairing_uuid_len = strlen(pairing_uuid); + + SRP_initialize_library(); + + SRP* srp = SRP_new(SRP6a_sha512_client_method()); + if (!srp) { + PAIRING_ERROR("Failed to initialize SRP") + return LOCKDOWN_E_UNKNOWN_ERROR; + } + + char tmp[256]; + plist_t dict = NULL; + uint8_t current_state = 0; + uint8_t final_state = 6; + + unsigned char* salt = NULL; + unsigned int salt_size = 0; + unsigned char* pubkey = NULL; + unsigned int pubkey_size = 0; + + unsigned char setup_encryption_key[32]; + + cstr *thekey = NULL; + + do { + current_state++; + + dict = plist_new_dict(); + plist_dict_set_item(dict, "Request", plist_new_string("CUPairingCreate")); + if (current_state == 1) { + plist_dict_set_item(dict, "Flags", plist_new_uint(1)); + } else { + plist_dict_set_item(dict, "Flags", plist_new_uint(0)); + } + + tlv_buf_t tlv = tlv_buf_new(); + + if (current_state == 1) { + /* send method */ + tlv_buf_append(tlv, 0x00, 1, (void*)"\x00"); // 0x00 (Method), 1 bytes, 00 + } else if (current_state == 3) { + /* generate public key */ + cstr* own_pub = NULL; + SRP_gen_pub(srp, &own_pub); + + if (!own_pub) { + PAIRING_ERROR("[SRP] Failed to generate public key") + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* compute key from remote's public key */ + if (SRP_compute_key(srp, &thekey, pubkey, pubkey_size) != 0) { + cstr_free(own_pub); + PAIRING_ERROR("[SRP] Failed to compute key") + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* compute response */ + cstr *response = NULL; + SRP_respond(srp, &response); + + /* send our public key + response */ + tlv_buf_append(tlv, 0x03, own_pub->length, own_pub->data); + tlv_buf_append(tlv, 0x04, response->length, response->data); + cstr_free(response); + cstr_free(own_pub); + } else if (current_state == 5) { + /* send encrypted info */ + + static const char PAIR_SETUP_ENCRYPT_SALT[] = "Pair-Setup-Encrypt-Salt"; + static const char PAIR_SETUP_ENCRYPT_INFO[] = "Pair-Setup-Encrypt-Info"; + static const char PAIR_SETUP_CONTROLLER_SIGN_SALT[] = "Pair-Setup-Controller-Sign-Salt"; + static const char PAIR_SETUP_CONTROLLER_SIGN_INFO[] = "Pair-Setup-Controller-Sign-Info"; + + // HKDF with above computed key (SRP_compute_key) + Pair-Setup-Encrypt-Salt + Pair-Setup-Encrypt-Info + // result used as key for chacha20-poly1305 + unsigned int setup_encryption_key_len = sizeof(setup_encryption_key); + hkdf_md(MD_ALGO_SHA512, (unsigned char*)PAIR_SETUP_ENCRYPT_SALT, sizeof(PAIR_SETUP_ENCRYPT_SALT)-1, (unsigned char*)PAIR_SETUP_ENCRYPT_INFO, sizeof(PAIR_SETUP_ENCRYPT_INFO)-1, (unsigned char*)thekey->data, thekey->length, setup_encryption_key, &setup_encryption_key_len); + + unsigned char ed25519_pubkey[32]; + unsigned char ed25519_privkey[64]; + unsigned char ed25519seed[32]; + ed25519_create_seed(ed25519seed); + + ed25519_create_keypair(ed25519_pubkey, ed25519_privkey, ed25519seed); + + unsigned int signbuf_len = pairing_uuid_len + 64; + unsigned char* signbuf = malloc(signbuf_len); + unsigned int hkdf_len = 32; + // HKDF with above computed key (SRP_compute_key) + Pair-Setup-Controller-Sign-Salt + Pair-Setup-Controller-Sign-Info + hkdf_md(MD_ALGO_SHA512, (unsigned char*)PAIR_SETUP_CONTROLLER_SIGN_SALT, sizeof(PAIR_SETUP_CONTROLLER_SIGN_SALT)-1, (unsigned char*)PAIR_SETUP_CONTROLLER_SIGN_INFO, sizeof(PAIR_SETUP_CONTROLLER_SIGN_INFO)-1, (unsigned char*)thekey->data, thekey->length, signbuf, &hkdf_len); + + memcpy(signbuf + 32, pairing_uuid, pairing_uuid_len); + memcpy(signbuf + 32 + pairing_uuid_len, ed25519_pubkey, 32); + + unsigned char ed_sig[64]; + ed25519_sign(ed_sig, signbuf, 0x64, ed25519_pubkey, ed25519_privkey); + + tlv_buf_t tlvbuf = tlv_buf_new(); + tlv_buf_append(tlvbuf, 0x01, pairing_uuid_len, (void*)pairing_uuid); + tlv_buf_append(tlvbuf, 0x03, sizeof(ed25519_pubkey), ed25519_pubkey); + tlv_buf_append(tlvbuf, 0x0a, sizeof(ed_sig), ed_sig); + + /* ACL */ + unsigned char* odata = NULL; + unsigned int olen = 0; + if (acl) { + opack_encode_from_plist(acl, &odata, &olen); + } else { + /* defaut ACL */ + plist_t acl_plist = plist_new_dict(); + plist_dict_set_item(acl_plist, "com.apple.ScreenCapture", plist_new_bool(1)); + plist_dict_set_item(acl_plist, "com.apple.developer", plist_new_bool(1)); + opack_encode_from_plist(acl_plist, &odata, &olen); + plist_free(acl_plist); + } + tlv_buf_append(tlvbuf, 0x12, olen, odata); + free(odata); + + /* HOST INFORMATION */ + char hostname[256]; +#if defined(__APPLE__) && !defined(TARGET_OS_IPHONE) + CFStringRef cname = SCDynamicStoreCopyComputerName(NULL, NULL); + CFStringGetCString(cname, hostname, sizeof(hostname), kCFStringEncodingUTF8); + CFRelease(cname); +#else +#ifdef WIN32 + DWORD hostname_len = sizeof(hostname); + GetComputerName(hostname, &hostname_len); +#else + gethostname(hostname, sizeof(hostname)); +#endif +#endif + + char modelname[256]; + modelname[0] = '\0'; +#ifdef __APPLE__ + size_t len = sizeof(modelname); + sysctlbyname("hw.model", &modelname, &len, NULL, 0); +#endif + if (strlen(modelname) == 0) { + strcpy(modelname, "HackbookPro13,37"); + } + + unsigned char primary_mac_addr[6] = { 0, 0, 0, 0, 0, 0 }; + if (get_primary_mac_address(primary_mac_addr) != 0) { + debug_info("Failed to get primary mac address"); + } + debug_info("Primary mac address: %02x:%02x:%02x:%02x:%02x:%02x\n", primary_mac_addr[0], primary_mac_addr[1], primary_mac_addr[2], primary_mac_addr[3], primary_mac_addr[4], primary_mac_addr[5]); + + // "OPACK" encoded device info + plist_t info_plist = plist_new_dict(); + //plist_dict_set_item(info_plist, "altIRK", plist_new_data((char*)altIRK, 16)); + plist_dict_set_item(info_plist, "accountID", plist_new_string(pairing_uuid)); + plist_dict_set_item(info_plist, "model", plist_new_string(modelname)); + plist_dict_set_item(info_plist, "name", plist_new_string(hostname)); + plist_dict_set_item(info_plist, "mac", plist_new_data((char*)primary_mac_addr, 6)); + if (host_info) { + plist_dict_merge(&info_plist, host_info); + } + opack_encode_from_plist(info_plist, &odata, &olen); + plist_free(info_plist); + tlv_buf_append(tlvbuf, 0x11, olen, odata); + free(odata); + + size_t encrypted_len = tlvbuf->length + 16; + unsigned char* encrypted_buf = (unsigned char*)malloc(encrypted_len); + + chacha20_poly1305_encrypt_64(setup_encryption_key, (unsigned char*)"PS-Msg05", NULL, 0, tlvbuf->data, tlvbuf->length, encrypted_buf, &encrypted_len); + + tlv_buf_free(tlvbuf); + + tlv_buf_append(tlv, 0x05, encrypted_len, encrypted_buf); + free(encrypted_buf); + } else { + tlv_buf_free(tlv); + PAIRING_ERROR("[SRP] Invalid state"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + tlv_buf_append(tlv, 0x06, 1, ¤t_state); + plist_dict_set_item(dict, "Payload", plist_new_data((char*)tlv->data, tlv->length)); + tlv_buf_free(tlv); + + plist_dict_set_item(dict, "Label", plist_new_string(client->label)); + plist_dict_set_item(dict, "ProtocolVersion", plist_new_uint(2)); + + ret = lockdownd_send(client, dict); + plist_free(dict); + dict = NULL; + + if (ret != LOCKDOWN_E_SUCCESS) { + break; + } + + current_state++; + + ret = lockdownd_receive(client, &dict); + if (ret != LOCKDOWN_E_SUCCESS) { + break; + } + ret = lockdown_check_result(dict, "CUPairingCreate"); + if (ret != LOCKDOWN_E_SUCCESS) { + break; + } + + plist_t extresp = plist_dict_get_item(dict, "ExtendedResponse"); + if (!extresp) { + ret = LOCKDOWN_E_PLIST_ERROR; + break; + } + plist_t blob = plist_dict_get_item(extresp, "Payload"); + if (!blob) { + ret = LOCKDOWN_E_PLIST_ERROR; + break; + } + uint64_t data_len = 0; + const char* data = plist_get_data_ptr(blob, &data_len); + + uint8_t state = 0; + if (!tlv_data_get_uint8(data, data_len, 0x06, &state)) { + PAIRING_ERROR("[SRP] ERROR: Could not find state in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + if (state != current_state) { + PAIRING_ERROR_FMT("[SRP] ERROR: Unexpected state %d, expected %d", state, current_state); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + unsigned int errval = 0; + uint64_t u64val = 0; + tlv_data_get_uint(data, data_len, 0x07, &u64val); +debug_buffer(data, data_len); + errval = (unsigned int)u64val; + if (errval > 0) { + if (errval == 3) { + u64val = 0; + tlv_data_get_uint(data, data_len, 0x08, &u64val); + if (u64val > 0) { + uint32_t retry_delay = (uint32_t)u64val; + PAIRING_ERROR_FMT("[SRP] Pairing is blocked for another %u seconds", retry_delay) + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + } else if (errval == 2 && state == 4) { + PAIRING_ERROR_FMT("[SRP] Invalid PIN") + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } else { + PAIRING_ERROR_FMT("[SRP] Received error %u in state %d.", errval, state); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + } + + if (state == 2) { + /* receive salt and public key */ + if (!tlv_data_copy_data(data, data_len, 0x02, (void**)&salt, &salt_size)) { + PAIRING_ERROR("[SRP] ERROR: Could not find salt in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + if (!tlv_data_copy_data(data, data_len, 0x03, (void**)&pubkey, &pubkey_size)) { + PAIRING_ERROR("[SRP] ERROR: Could not find public key in response"); + + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + const char PAIR_SETUP[] = "Pair-Setup"; + if (SRP_set_user_raw(srp, (const unsigned char*)PAIR_SETUP, sizeof(PAIR_SETUP)-1) != 0) { + PAIRING_ERROR("[SRP] Failed to set SRP user"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* kSRPParameters_3072_SHA512 */ + if (SRP_set_params(srp, kSRPModulus3072, sizeof(kSRPModulus3072), &kSRPGenerator5, 1, salt, salt_size) != 0) { + PAIRING_ERROR("[SRP] Failed to set SRP parameters"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + + } + + if (pairing_callback) { + char pin[64]; + unsigned int pin_len = sizeof(pin); + pairing_callback(LOCKDOWN_CU_PAIRING_PIN_REQUESTED, cb_user_data, pin, &pin_len); + + SRP_set_auth_password_raw(srp, (const unsigned char*)pin, pin_len); + } + } else if (state == 4) { + /* receive proof */ + unsigned char* proof = NULL; + unsigned int proof_len = 0; + + if (!tlv_data_copy_data(data, data_len, 0x04, (void**)&proof, &proof_len)) { + PAIRING_ERROR("[SRP] ERROR: Could not find proof data in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* verify */ + int vrfy_result = SRP_verify(srp, proof, proof_len); + free(proof); + + if (vrfy_result == 0) { + debug_info("[SRP] PIN verified successfully"); + } else { + PAIRING_ERROR("[SRP] PIN verification failure"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + } else if (state == 6) { + int srp_pair_success = 0; + plist_t node = plist_dict_get_item(extresp, "doSRPPair"); + if (node) { + const char* strv = plist_get_string_ptr(node, NULL); + if (strcmp(strv, "succeed") == 0) { + srp_pair_success = 1; + } + } + if (!srp_pair_success) { + PAIRING_ERROR("SRP Pairing failed"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* receive encrypted info */ + unsigned char* encrypted_buf = NULL; + unsigned int enc_len = 0; + if (!tlv_data_copy_data(data, data_len, 0x05, (void**)&encrypted_buf, &enc_len)) { + PAIRING_ERROR("[SRP] ERROR: Could not find encrypted data in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + size_t plain_len = enc_len-16; + unsigned char* plain_buf = malloc(plain_len); + chacha20_poly1305_decrypt_64(setup_encryption_key, (unsigned char*)"PS-Msg06", NULL, 0, encrypted_buf, enc_len, plain_buf, &plain_len); + free(encrypted_buf); + + unsigned char* dev_info = NULL; + unsigned int dev_info_len = 0; + int res = tlv_data_copy_data(plain_buf, plain_len, 0x11, (void**)&dev_info, &dev_info_len); + free(plain_buf); + if (!res) { + PAIRING_ERROR("[SRP] ERROR: Failed to locate device info in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + plist_t device_info = NULL; + opack_decode_to_plist(dev_info, dev_info_len, &device_info); + free(dev_info); + + if (!device_info) { + PAIRING_ERROR("[SRP] ERROR: Failed to parse device info"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + if (pairing_callback) { + pairing_callback(LOCKDOWN_CU_PAIRING_DEVICE_INFO, cb_user_data, device_info, NULL); + } + plist_free(device_info); + } else { + PAIRING_ERROR("[SRP] ERROR: Invalid state"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + plist_free(dict); + dict = NULL; + + } while (current_state != final_state); + + plist_free(dict); + + free(salt); + free(pubkey); + + SRP_free(srp); + srp = NULL; + + if (ret != LOCKDOWN_E_SUCCESS) { + if (thekey) { + cstr_free(thekey); + } + return ret; + } + + free(client->cu_key); + client->cu_key = malloc(thekey->length); + memcpy(client->cu_key, thekey->data, thekey->length); + client->cu_key_len = thekey->length; + cstr_free(thekey); + + return LOCKDOWN_E_SUCCESS; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} + +lockdownd_error_t lockdownd_cu_send_request_and_get_reply(lockdownd_client_t client, const char* request, plist_t request_payload, plist_t* reply) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client || !request) + return LOCKDOWN_E_INVALID_ARG; + + if (!client->cu_key) + return LOCKDOWN_E_NO_RUNNING_SESSION; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + /* derive keys */ + unsigned char cu_write_key[32]; + unsigned int cu_write_key_len = sizeof(cu_write_key); + static const char WRITE_KEY_SALT_MDLD[] = "WriteKeySaltMDLD"; + static const char WRITE_KEY_INFO_MDLD[] = "WriteKeyInfoMDLD"; + hkdf_md(MD_ALGO_SHA512, (unsigned char*)WRITE_KEY_SALT_MDLD, sizeof(WRITE_KEY_SALT_MDLD)-1, (unsigned char*)WRITE_KEY_INFO_MDLD, sizeof(WRITE_KEY_INFO_MDLD)-1, client->cu_key, client->cu_key_len, cu_write_key, &cu_write_key_len); + + unsigned char cu_read_key[32]; + unsigned int cu_read_key_len = sizeof(cu_write_key); + static const char READ_KEY_SALT_MDLD[] = "ReadKeySaltMDLD"; + static const char READ_KEY_INFO_MDLD[] = "ReadKeyInfoMDLD"; + hkdf_md(MD_ALGO_SHA512, (unsigned char*)READ_KEY_SALT_MDLD, sizeof(READ_KEY_SALT_MDLD)-1, (unsigned char*)READ_KEY_INFO_MDLD, sizeof(READ_KEY_INFO_MDLD)-1, client->cu_key, client->cu_key_len, cu_read_key, &cu_read_key_len); + + // Starting with iOS/tvOS 11.2 and WatchOS 4.2, this nonce is random and sent along with the request. Before, the request doesn't have a nonce and it uses hardcoded nonce "sendone01234". + unsigned char cu_nonce[12] = "sendone01234"; // guaranteed to be random by fair dice troll + if (client->device->version >= DEVICE_VERSION(11,2,0)) { +#if defined(HAVE_OPENSSL) + RAND_bytes(cu_nonce, sizeof(cu_nonce)); +#elif defined(HAVE_GCRYPT) + gcry_create_nonce(cu_nonce, sizeof(cu_nonce)); +#endif + } + + debug_plist(request_payload); + + /* convert request payload to binary */ + uint32_t bin_len = 0; + char* bin = NULL; + plist_to_bin(request_payload, &bin, &bin_len); + + /* encrypt request */ + size_t encrypted_len = bin_len + 16; + unsigned char* encrypted_buf = malloc(encrypted_len); + chacha20_poly1305_encrypt_96(cu_write_key, cu_nonce, NULL, 0, (unsigned char*)bin, bin_len, encrypted_buf, &encrypted_len); + free(bin); + bin = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"Request", plist_new_string(request)); + plist_dict_set_item(dict, "Payload", plist_new_data((char*)encrypted_buf, encrypted_len)); + free(encrypted_buf); + plist_dict_set_item(dict, "Nonce", plist_new_data((char*)cu_nonce, sizeof(cu_nonce))); + plist_dict_set_item(dict, "Label", plist_new_string(client->label)); + plist_dict_set_item(dict, "ProtocolVersion", plist_new_uint(2)); + + /* send to device */ + ret = lockdownd_send(client, dict); + plist_free(dict); + dict = NULL; + + if (ret != LOCKDOWN_E_SUCCESS) + return ret; + + /* Now get device's answer */ + ret = lockdownd_receive(client, &dict); + if (ret != LOCKDOWN_E_SUCCESS) + return ret; + + ret = lockdown_check_result(dict, request); + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(dict); + return ret; + } + + /* get payload */ + plist_t blob = plist_dict_get_item(dict, "Payload"); + if (!blob) { + plist_free(dict); + return LOCKDOWN_E_DICT_ERROR; + } + + uint64_t dl = 0; + const char* dt = plist_get_data_ptr(blob, &dl); + + /* see if we have a nonce */ + blob = plist_dict_get_item(dict, "Nonce"); + const unsigned char* rnonce = (unsigned char*)"receiveone01"; + if (blob) { + uint64_t rl = 0; + rnonce = (const unsigned char*)plist_get_data_ptr(blob, &rl); + } + + /* decrypt payload */ + size_t decrypted_len = dl-16; + unsigned char* decrypted = malloc(decrypted_len); + chacha20_poly1305_decrypt_96(cu_read_key, (unsigned char*)rnonce, NULL, 0, (unsigned char*)dt, dl, decrypted, &decrypted_len); + plist_free(dict); + dict = NULL; + + plist_from_memory((const char*)decrypted, decrypted_len, &dict, NULL); + if (!dict) { + ret = LOCKDOWN_E_PLIST_ERROR; + debug_info("Failed to parse PLIST from decrypted payload:"); + debug_buffer((const char*)decrypted, decrypted_len); + free(decrypted); + return ret; + } + free(decrypted); + + debug_plist(dict); + + if (reply) { + *reply = dict; + } else { + plist_free(dict); + } + + return LOCKDOWN_E_SUCCESS; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} + +lockdownd_error_t lockdownd_get_value_cu(lockdownd_client_t client, const char* domain, const char* key, plist_t* value) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client) + return LOCKDOWN_E_INVALID_ARG; + + if (!client->cu_key) + return LOCKDOWN_E_NO_RUNNING_SESSION; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + plist_t request = plist_new_dict(); + if (domain) { + plist_dict_set_item(request, "Domain", plist_new_string(domain)); + } + if (key) { + plist_dict_set_item(request, "Key", plist_new_string(key)); + } + + plist_t reply = NULL; + ret = lockdownd_cu_send_request_and_get_reply(client, "GetValueCU", request, &reply); + plist_free(request); + if (ret != LOCKDOWN_E_SUCCESS) { + return ret; + } + + plist_t value_node = plist_dict_get_item(reply, "Value"); + if (value_node) { + debug_info("has a value"); + *value = plist_copy(value_node); + } + plist_free(reply); + + return ret; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} + +lockdownd_error_t lockdownd_pair_cu(lockdownd_client_t client) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client) + return LOCKDOWN_E_INVALID_ARG; + + if (!client->cu_key) + return LOCKDOWN_E_NO_RUNNING_SESSION; + + lockdownd_error_t ret; + + plist_t wifi_mac = NULL; + ret = lockdownd_get_value_cu(client, NULL, "WiFiAddress", &wifi_mac); + if (ret != LOCKDOWN_E_SUCCESS) { + return ret; + } + + plist_t pubkey = NULL; + ret = lockdownd_get_value_cu(client, NULL, "DevicePublicKey", &pubkey); + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(wifi_mac); + return ret; + } + + key_data_t public_key = { NULL, 0 }; + uint64_t data_len = 0; + plist_get_data_val(pubkey, (char**)&public_key.data, &data_len); + public_key.size = (unsigned int)data_len; + plist_free(pubkey); + + plist_t pair_record_plist = plist_new_dict(); + pair_record_generate_keys_and_certs(pair_record_plist, public_key); + + char* host_id = NULL; + char* system_buid = NULL; + + /* set SystemBUID */ + userpref_read_system_buid(&system_buid); + if (system_buid) { + plist_dict_set_item(pair_record_plist, USERPREF_SYSTEM_BUID_KEY, plist_new_string(system_buid)); + free(system_buid); + } + + /* set HostID */ + host_id = generate_uuid(); + pair_record_set_host_id(pair_record_plist, host_id); + free(host_id); + + plist_t request_pair_record = plist_copy(pair_record_plist); + /* remove stuff that is private */ + plist_dict_remove_item(request_pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY); + plist_dict_remove_item(request_pair_record, USERPREF_HOST_PRIVATE_KEY_KEY); + + plist_t request = plist_new_dict(); + plist_dict_set_item(request, "PairRecord", request_pair_record); + plist_t pairing_opts = plist_new_dict(); + plist_dict_set_item(pairing_opts, "ExtendedPairingErrors", plist_new_bool(1)); + plist_dict_set_item(request, "PairingOptions", pairing_opts); + + plist_t reply = NULL; + ret = lockdownd_cu_send_request_and_get_reply(client, "PairCU", request, &reply); + plist_free(request); + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(wifi_mac); + return ret; + } + + char *s_udid = NULL; + plist_t p_udid = plist_dict_get_item(reply, "UDID"); + if (p_udid) { + plist_get_string_val(p_udid, &s_udid); + } + plist_t ebag = plist_dict_get_item(reply, "EscrowBag"); + if (ebag) { + plist_dict_set_item(pair_record_plist, USERPREF_ESCROW_BAG_KEY, plist_copy(ebag)); + } + plist_dict_set_item(pair_record_plist, USERPREF_WIFI_MAC_ADDRESS_KEY, wifi_mac); + plist_free(reply); + + if (userpref_save_pair_record(s_udid, 0, pair_record_plist) != 0) { + printf("Failed to save pair record for UDID %s\n", s_udid); + } + free(s_udid); + s_udid = NULL; + plist_free(pair_record_plist); + + ret = LOCKDOWN_E_SUCCESS; + + return ret; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} diff --git a/src/lockdown.c b/src/lockdown.c index 935f24e..256bff0 100644 --- a/src/lockdown.c +++ b/src/lockdown.c @@ -2,6 +2,8 @@ * lockdown.c * com.apple.mobile.lockdownd service implementation. * + * Copyright (c) 2009-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2014-2015 Nikias Bassen All Rights Reserved. * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. * @@ -20,36 +22,125 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #include <string.h> #include <stdlib.h> #define _GNU_SOURCE 1 #define __USE_GNU 1 #include <stdio.h> #include <ctype.h> -#include <glib.h> -#include <libtasn1.h> -#include <gnutls/x509.h> +#include <unistd.h> #include <plist/plist.h> +#include <libimobiledevice-glue/utils.h> #include "property_list_service.h" #include "lockdown.h" #include "idevice.h" -#include "debug.h" -#include "userpref.h" - -#define RESULT_SUCCESS 0 -#define RESULT_FAILURE 1 - -const ASN1_ARRAY_TYPE pkcs1_asn1_tab[] = { - {"PKCS1", 536872976, 0}, - {0, 1073741836, 0}, - {"RSAPublicKey", 536870917, 0}, - {"modulus", 1073741827, 0}, - {"publicExponent", 3, 0}, - {0, 0, 0} +#include "common/debug.h" +#include "common/userpref.h" +#include "asprintf.h" + +#ifdef WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#endif + +struct st_lockdownd_error_str_map { + const char *lockdown_errstr; + const char *errstr; + lockdownd_error_t errcode; +}; + +static struct st_lockdownd_error_str_map lockdownd_error_str_map[] = { + { "InvalidResponse", "Invalid response", LOCKDOWN_E_INVALID_RESPONSE }, + { "MissingKey", "Missing key", LOCKDOWN_E_MISSING_KEY }, + { "MissingValue", "Missing value", LOCKDOWN_E_MISSING_VALUE }, + { "GetProhibited", "Get value prohibited", LOCKDOWN_E_GET_PROHIBITED }, + { "SetProhibited", "Set value prohibited", LOCKDOWN_E_SET_PROHIBITED }, + { "RemoveProhibited", "Remove value prohibited", LOCKDOWN_E_REMOVE_PROHIBITED }, + { "ImmutableValue", "Immutable value", LOCKDOWN_E_IMMUTABLE_VALUE }, + { "PasswordProtected", "Password protected", LOCKDOWN_E_PASSWORD_PROTECTED }, + { "UserDeniedPairing", "User denied pairing", LOCKDOWN_E_USER_DENIED_PAIRING }, + { "PairingDialogResponsePending", "Pairing dialog response pending", LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING }, + { "MissingHostID", "Missing HostID", LOCKDOWN_E_MISSING_HOST_ID }, + { "InvalidHostID", "Invalid HostID", LOCKDOWN_E_INVALID_HOST_ID }, + { "SessionActive", "Session active", LOCKDOWN_E_SESSION_ACTIVE }, + { "SessionInactive", "Session inactive", LOCKDOWN_E_SESSION_INACTIVE }, + { "MissingSessionID", "Missing session ID", LOCKDOWN_E_MISSING_SESSION_ID }, + { "InvalidSessionID", "Invalid session ID", LOCKDOWN_E_INVALID_SESSION_ID }, + { "MissingService", "Missing service", LOCKDOWN_E_MISSING_SERVICE }, + { "InvalidService", "Invalid service", LOCKDOWN_E_INVALID_SERVICE }, + { "ServiceLimit", "Service limit reached", LOCKDOWN_E_SERVICE_LIMIT }, + { "MissingPairRecord", "Missing pair record", LOCKDOWN_E_MISSING_PAIR_RECORD }, + { "SavePairRecordFailed", "Saving pair record failed", LOCKDOWN_E_SAVE_PAIR_RECORD_FAILED }, + { "InvalidPairRecord", "Invalid pair record", LOCKDOWN_E_INVALID_PAIR_RECORD }, + { "InvalidActivationRecord", "Invalid activation record", LOCKDOWN_E_INVALID_ACTIVATION_RECORD }, + { "MissingActivationRecord", "Missing activation record", LOCKDOWN_E_MISSING_ACTIVATION_RECORD }, + { "ServiceProhibited", "Service prohibited", LOCKDOWN_E_SERVICE_PROHIBITED }, + { "EscrowLocked", "Escrow lockded", LOCKDOWN_E_ESCROW_LOCKED }, + { "PairingProhibitedOverThisConnection", "Pairing prohibited over this connection", LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION }, + { "FMiPProtected", "Find My iPhone/iPod/iPad protected", LOCKDOWN_E_FMIP_PROTECTED }, + { "MCProtected", "MC protected" , LOCKDOWN_E_MC_PROTECTED }, + { "MCChallengeRequired", "MC challenge required", LOCKDOWN_E_MC_CHALLENGE_REQUIRED }, + { NULL, NULL, 0 } }; /** + * Convert an error string identifier to a lockdownd_error_t value. + * Used internally to get correct error codes from a response. + * + * @param name The error name to convert. + * + * @return A matching lockdownd_error_t error code, + * LOCKDOWN_E_UNKNOWN_ERROR otherwise. + */ +static lockdownd_error_t lockdownd_strtoerr(const char* name) +{ + lockdownd_error_t err = LOCKDOWN_E_UNKNOWN_ERROR; + int i = 0; + while (lockdownd_error_str_map[i].lockdown_errstr) { + if (strcmp(lockdownd_error_str_map[i].lockdown_errstr, name) == 0) { + return lockdownd_error_str_map[i].errcode; + } + i++; + } + return err; +} + +/** + * Convert a property_list_service_error_t value to a lockdownd_error_t + * value. Used internally to get correct error codes. + * + * @param err A property_list_service_error_t error code + * + * @return A matching lockdownd_error_t error code, + * LOCKDOWND_E_UNKNOWN_ERROR otherwise. + */ +static lockdownd_error_t lockdownd_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return LOCKDOWN_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return LOCKDOWN_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return LOCKDOWN_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return LOCKDOWN_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return LOCKDOWN_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return LOCKDOWN_E_RECEIVE_TIMEOUT; + default: + break; + } + return LOCKDOWN_E_UNKNOWN_ERROR; +} + +/** * Internally used function for checking the result from lockdown's answer * plist to a previously sent request. * @@ -57,58 +148,66 @@ const ASN1_ARRAY_TYPE pkcs1_asn1_tab[] = { * @param query_match Name of the request to match or NULL if no match is * required. * - * @return RESULT_SUCCESS when the result is 'Success', - * RESULT_FAILURE when the result is 'Failure', - * or a negative value if an error occured during evaluation. + * @return LOCKDOWN_E_SUCCESS when the result is 'Success', + * LOCKDOWN_E_UNKNOWN_ERROR when the result is 'Failure', + * or a specific error code if derieved from the result. */ -static int lockdown_check_result(plist_t dict, const char *query_match) +lockdownd_error_t lockdown_check_result(plist_t dict, const char *query_match) { - int ret = -1; + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t query_node = plist_dict_get_item(dict, "Request"); if (!query_node) { return ret; } + if (plist_get_node_type(query_node) != PLIST_STRING) { return ret; - } else { - char *query_value = NULL; - plist_get_string_val(query_node, &query_value); - if (!query_value) { - return ret; - } - if (query_match && (strcmp(query_value, query_match) != 0)) { - free(query_value); - return ret; - } - free(query_value); } - plist_t result_node = plist_dict_get_item(dict, "Result"); - if (!result_node) { + const char *query_value = plist_get_string_ptr(query_node, NULL); + if (!query_value) { return ret; } - plist_type result_type = plist_get_node_type(result_node); - - if (result_type == PLIST_STRING) { - - char *result_value = NULL; + if (query_match && (strcmp(query_value, query_match) != 0)) { + return ret; + } - plist_get_string_val(result_node, &result_value); + /* Check for 'Error' in reply */ + plist_t err_node = plist_dict_get_item(dict, "Error"); + if (err_node) { + if (plist_get_node_type(err_node) == PLIST_STRING) { + const char *err_value = plist_get_string_ptr(err_node, NULL); + if (err_value) { + debug_info("ERROR: %s", err_value); + ret = lockdownd_strtoerr(err_value); + } else { + debug_info("ERROR: unknown error occurred"); + } + } + return ret; + } + plist_t result_node = plist_dict_get_item(dict, "Result"); + if (!result_node) { + /* With iOS 5+ 'Result' is not present anymore. + If there is no 'Error', we can just assume success. */ + return LOCKDOWN_E_SUCCESS; + } + if (plist_get_node_type(result_node) == PLIST_STRING) { + const char *result_value = plist_get_string_ptr(result_node, NULL); if (result_value) { if (!strcmp(result_value, "Success")) { - ret = RESULT_SUCCESS; + ret = LOCKDOWN_E_SUCCESS; } else if (!strcmp(result_value, "Failure")) { - ret = RESULT_FAILURE; + ret = LOCKDOWN_E_UNKNOWN_ERROR; } else { debug_info("ERROR: unknown result value '%s'", result_value); } } - if (result_value) - free(result_value); } + return ret; } @@ -123,20 +222,10 @@ static void plist_dict_add_label(plist_t plist, const char *label) { if (plist && label) { if (plist_get_node_type(plist) == PLIST_DICT) - plist_dict_insert_item(plist, "Label", plist_new_string(label)); + plist_dict_set_item(plist, "Label", plist_new_string(label)); } } -/** - * Closes the lockdownd session by sending the StopSession request. - * - * @see lockdownd_start_session - * - * @param client The lockdown client - * @param session_id The id of a running session - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char *session_id) { if (!client) @@ -151,8 +240,8 @@ lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char * plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("StopSession")); - plist_dict_insert_item(dict,"SessionID", plist_new_string(session_id)); + plist_dict_set_item(dict,"Request", plist_new_string("StopSession")); + plist_dict_set_item(dict,"SessionID", plist_new_string(session_id)); debug_info("stopping session %s", session_id); @@ -168,64 +257,74 @@ lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char * return LOCKDOWN_E_PLIST_ERROR; } - ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "StopSession") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "StopSession"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; + + if (client->session_id) { + free(client->session_id); + client->session_id = NULL; + } + if (client->ssl_enabled) { property_list_service_disable_ssl(client->parent); + client->ssl_enabled = 0; } + return ret; } -/** - * Closes the lockdownd client session if one is running and frees up the - * lockdownd_client struct. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ -lockdownd_error_t lockdownd_client_free(lockdownd_client_t client) +static lockdownd_error_t lockdownd_client_free_simple(lockdownd_client_t client) { if (!client) return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (client->session_id) { - lockdownd_stop_session(client, client->session_id); - free(client->session_id); - } + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; if (client->parent) { - lockdownd_goodbye(client); - if (property_list_service_client_free(client->parent) == PROPERTY_LIST_SERVICE_E_SUCCESS) { ret = LOCKDOWN_E_SUCCESS; } } - if (client->uuid) { - free(client->uuid); + if (client->session_id) { + free(client->session_id); + client->session_id = NULL; } if (client->label) { free(client->label); } + if (client->cu_key) { + free(client->cu_key); + client->cu_key = NULL; + } free(client); + client = NULL; + + return ret; +} + +lockdownd_error_t lockdownd_client_free(lockdownd_client_t client) +{ + if (!client) + return LOCKDOWN_E_INVALID_ARG; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + if (client->session_id) { + lockdownd_stop_session(client, client->session_id); + } + + ret = lockdownd_client_free_simple(client); + return ret; } -/** - * Sets the label to send for requests to lockdownd. - * - * @param client The lockdown client - * @param label The label to set or NULL to disable sending a label - * - */ void lockdownd_client_set_label(lockdownd_client_t client, const char *label) { if (client) { @@ -236,69 +335,22 @@ void lockdownd_client_set_label(lockdownd_client_t client, const char *label) } } -/** - * Receives a plist from lockdownd. - * - * @param client The lockdownd client - * @param plist The plist to store the received data - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ lockdownd_error_t lockdownd_receive(lockdownd_client_t client, plist_t *plist) { if (!client || !plist || (plist && *plist)) return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; - property_list_service_error_t err; - - err = property_list_service_receive_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = LOCKDOWN_E_UNKNOWN_ERROR; - } - - if (!*plist) - ret = LOCKDOWN_E_PLIST_ERROR; - return ret; + return lockdownd_error(property_list_service_receive_plist(client->parent, plist)); } -/** - * Sends a plist to lockdownd. - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The lockdownd client - * @param plist The plist to send - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ lockdownd_error_t lockdownd_send(lockdownd_client_t client, plist_t plist) { if (!client || !plist) return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; - idevice_error_t err; - - err = property_list_service_send_xml_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = LOCKDOWN_E_UNKNOWN_ERROR; - } - return ret; + return lockdownd_error(property_list_service_send_xml_plist(client->parent, plist)); } -/** - * Query the type of the service daemon. Depending on whether the device is - * queried in normal mode or restore mode, different types will be returned. - * - * @param client The lockdownd client - * @param type The type returned by the service daemon. Pass NULL to ignore. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) { if (!client) @@ -308,7 +360,7 @@ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("QueryType")); + plist_dict_set_item(dict,"Request", plist_new_string("QueryType")); debug_info("called"); ret = lockdownd_send(client, dict); @@ -322,14 +374,21 @@ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) return ret; ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "QueryType") == RESULT_SUCCESS) { + plist_t type_node = plist_dict_get_item(dict, "Type"); + if (type_node && (plist_get_node_type(type_node) == PLIST_STRING)) { + char* typestr = NULL; + plist_get_string_val(type_node, &typestr); + debug_info("success with type %s", typestr); /* return the type if requested */ if (type != NULL) { - plist_t type_node = plist_dict_get_item(dict, "Type"); - plist_get_string_val(type_node, type); + *type = typestr; + } else { + free(typestr); } - debug_info("success with type %s", *type); ret = LOCKDOWN_E_SUCCESS; + } else { + debug_info("hmm. QueryType response does not contain a type?!"); + debug_plist(dict); } plist_free(dict); dict = NULL; @@ -337,16 +396,6 @@ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) return ret; } -/** - * Retrieves a preferences plist using an optional domain and/or key name. - * - * @param client An initialized lockdownd client. - * @param domain The domain to query on or NULL for global domain - * @param key The key name to request or NULL to query for all keys - * @param value A plist node representing the result value node - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *domain, const char *key, plist_t *value) { if (!client) @@ -359,12 +408,12 @@ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *dom dict = plist_new_dict(); plist_dict_add_label(dict, client->label); if (domain) { - plist_dict_insert_item(dict,"Domain", plist_new_string(domain)); + plist_dict_set_item(dict,"Domain", plist_new_string(domain)); } if (key) { - plist_dict_insert_item(dict,"Key", plist_new_string(key)); + plist_dict_set_item(dict,"Key", plist_new_string(key)); } - plist_dict_insert_item(dict,"Request", plist_new_string("GetValue")); + plist_dict_set_item(dict,"Request", plist_new_string("GetValue")); /* send to device */ ret = lockdownd_send(client, dict); @@ -380,10 +429,11 @@ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *dom if (ret != LOCKDOWN_E_SUCCESS) return ret; - if (lockdown_check_result(dict, "GetValue") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "GetValue"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + if (ret != LOCKDOWN_E_SUCCESS) { plist_free(dict); return ret; @@ -400,17 +450,6 @@ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *dom return ret; } -/** - * Sets a preferences value using a plist and optional by domain and/or key name. - * - * @param client an initialized lockdownd client. - * @param domain the domain to query on or NULL for global domain - * @param key the key name to set the value or NULL to set a value dict plist - * @param value a plist node of any node type representing the value to set - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * value is NULL - */ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *domain, const char *key, plist_t value) { if (!client || !value) @@ -423,13 +462,13 @@ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *dom dict = plist_new_dict(); plist_dict_add_label(dict, client->label); if (domain) { - plist_dict_insert_item(dict,"Domain", plist_new_string(domain)); + plist_dict_set_item(dict,"Domain", plist_new_string(domain)); } if (key) { - plist_dict_insert_item(dict,"Key", plist_new_string(key)); + plist_dict_set_item(dict,"Key", plist_new_string(key)); } - plist_dict_insert_item(dict,"Request", plist_new_string("SetValue")); - plist_dict_insert_item(dict,"Value", value); + plist_dict_set_item(dict,"Request", plist_new_string("SetValue")); + plist_dict_set_item(dict,"Value", value); /* send to device */ ret = lockdownd_send(client, dict); @@ -445,9 +484,9 @@ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *dom if (ret != LOCKDOWN_E_SUCCESS) return ret; - if (lockdown_check_result(dict, "SetValue") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "SetValue"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } if (ret != LOCKDOWN_E_SUCCESS) { @@ -459,17 +498,6 @@ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *dom return ret; } -/** - * Removes a preference node by domain and/or key name. - * - * @note: Use with caution as this could remove vital information on the device - * - * @param client An initialized lockdownd client. - * @param domain The domain to query on or NULL for global domain - * @param key The key name to remove or NULL remove all keys for the current domain - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char *domain, const char *key) { if (!client) @@ -482,12 +510,12 @@ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char * dict = plist_new_dict(); plist_dict_add_label(dict, client->label); if (domain) { - plist_dict_insert_item(dict,"Domain", plist_new_string(domain)); + plist_dict_set_item(dict,"Domain", plist_new_string(domain)); } if (key) { - plist_dict_insert_item(dict,"Key", plist_new_string(key)); + plist_dict_set_item(dict,"Key", plist_new_string(key)); } - plist_dict_insert_item(dict,"Request", plist_new_string("RemoveValue")); + plist_dict_set_item(dict,"Request", plist_new_string("RemoveValue")); /* send to device */ ret = lockdownd_send(client, dict); @@ -503,9 +531,9 @@ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char * if (ret != LOCKDOWN_E_SUCCESS) return ret; - if (lockdown_check_result(dict, "RemoveValue") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "RemoveValue"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } if (ret != LOCKDOWN_E_SUCCESS) { @@ -517,16 +545,7 @@ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char * return ret; } -/** - * Returns the unique id of the device from lockdownd. - * - * @param client An initialized lockdownd client. - * @param uuid Holds the unique id of the device. The caller is responsible - * for freeing the memory. - * - * @return LOCKDOWN_E_SUCCESS on success - */ -lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t client, char **uuid) +lockdownd_error_t lockdownd_get_device_udid(lockdownd_client_t client, char **udid) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t value = NULL; @@ -535,7 +554,7 @@ lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t client, char **uu if (ret != LOCKDOWN_E_SUCCESS) { return ret; } - plist_get_string_val(value, uuid); + plist_get_string_val(value, udid); plist_free(value); value = NULL; @@ -551,7 +570,7 @@ lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t client, char **uu * * @return LOCKDOWN_E_SUCCESS on success */ -lockdownd_error_t lockdownd_get_device_public_key(lockdownd_client_t client, gnutls_datum_t * public_key) +static lockdownd_error_t lockdownd_get_device_public_key_as_key_data(lockdownd_client_t client, key_data_t *public_key) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t value = NULL; @@ -572,15 +591,6 @@ lockdownd_error_t lockdownd_get_device_public_key(lockdownd_client_t client, gnu return ret; } -/** - * Retrieves the name of the device from lockdownd set by the user. - * - * @param client An initialized lockdownd client. - * @param device_name Holds the name of the device. The caller is - * responsible for freeing the memory. - * - * @return LOCKDOWN_E_SUCCESS on success - */ lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **device_name) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; @@ -598,29 +608,19 @@ lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **de return ret; } -/** - * Creates a new lockdownd client for the device. - * - * @note This function does not pair with the device or start a session. This - * has to be done manually by the caller after the client is created. - * The device disconnects automatically if the lockdown connection idles - * for more than 10 seconds. Make sure to call lockdownd_client_free() as soon - * as the connection is no longer needed. - * - * @param device The device to create a lockdownd client for - * @param client The pointer to the location of the new lockdownd_client - * @param label The label to use for communication. Usually the program name. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *client, const char *label) { - if (!client) + if (!device || !client) return LOCKDOWN_E_INVALID_ARG; + static struct lockdownd_service_descriptor service = { + .port = 0xf27e, + .ssl_enabled = 0 + }; + property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, 0xf27e, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - debug_info("could not connect to lockdownd (device %s)", device->uuid); + if (property_list_service_client_new(device, (lockdownd_service_descriptor_t)&service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + debug_info("could not connect to lockdownd (device %s)", device->udid); return LOCKDOWN_E_MUX_ERROR; } @@ -628,7 +628,14 @@ lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *cli client_loc->parent = plistclient; client_loc->ssl_enabled = 0; client_loc->session_id = NULL; - client_loc->uuid = NULL; + client_loc->device = device; + client_loc->cu_key = NULL; + client_loc->cu_key_len = 0; + + if (device->udid) { + debug_info("device udid: %s", device->udid); + } + client_loc->label = label ? strdup(label) : NULL; *client = client_loc; @@ -636,23 +643,6 @@ lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *cli return LOCKDOWN_E_SUCCESS; } -/** - * Creates a new lockdownd client for the device and starts initial handshake. - * The handshake consists out of query_type, validate_pair, pair and - * start_session calls. It uses the internal pairing record management. - * - * @note The device disconnects automatically if the lockdown connection idles - * for more than 10 seconds. Make sure to call lockdownd_client_free() as soon - * as the connection is no longer needed. - * - * @param device The device to create a lockdownd client for - * @param client The pointer to the location of the new lockdownd_client - * @param label The label to use for communication. Usually the program name. - * Pass NULL to disable sending the label in requests to lockdownd. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_INVALID_CONF if configuration data is wrong - */ lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdownd_client_t *client, const char *label) { if (!client) @@ -660,6 +650,7 @@ lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdown lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; lockdownd_client_t client_loc = NULL; + plist_t pair_record = NULL; char *host_id = NULL; char *type = NULL; @@ -670,60 +661,125 @@ lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdown } /* perform handshake */ - if (LOCKDOWN_E_SUCCESS != lockdownd_query_type(client_loc, &type)) { + ret = lockdownd_query_type(client_loc, &type); + if (LOCKDOWN_E_SUCCESS != ret) { debug_info("QueryType failed in the lockdownd client."); - ret = LOCKDOWN_E_NOT_ENOUGH_DATA; - } else { - if (strcmp("com.apple.mobile.lockdown", type)) { - debug_info("Warning QueryType request returned \"%s\".", type); + } else if (strcmp("com.apple.mobile.lockdown", type) != 0) { + debug_info("Warning QueryType request returned \"%s\".", type); + } + free(type); + + if (device->version == 0) { + plist_t p_version = NULL; + if (lockdownd_get_value(client_loc, NULL, "ProductVersion", &p_version) == LOCKDOWN_E_SUCCESS) { + int vers[3] = {0, 0, 0}; + char *s_version = NULL; + plist_get_string_val(p_version, &s_version); + if (s_version && sscanf(s_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) { + device->version = DEVICE_VERSION(vers[0], vers[1], vers[2]); + } + free(s_version); } - if (type) - free(type); + plist_free(p_version); } - - ret = idevice_get_uuid(device, &client_loc->uuid); - if (LOCKDOWN_E_SUCCESS != ret) { - debug_info("failed to get device uuid."); + if (device->device_class == 0) { + plist_t p_device_class = NULL; + if (lockdownd_get_value(client_loc, NULL, "DeviceClass", &p_device_class) == LOCKDOWN_E_SUCCESS) { + char* s_device_class = NULL; + plist_get_string_val(p_device_class, &s_device_class); + if (s_device_class != NULL) { + if (!strcmp(s_device_class, "iPhone")) { + device->device_class = DEVICE_CLASS_IPHONE; + } else if (!strcmp(s_device_class, "iPad")) { + device->device_class = DEVICE_CLASS_IPAD; + } else if (!strcmp(s_device_class, "iPod")) { + device->device_class = DEVICE_CLASS_IPOD; + } else if (!strcmp(s_device_class, "Watch")) { + device->device_class = DEVICE_CLASS_WATCH; + } else if (!strcmp(s_device_class, "AppleTV")) { + device->device_class = DEVICE_CLASS_APPLETV; + } else { + device->device_class = DEVICE_CLASS_UNKNOWN; + } + free(s_device_class); + } + } + plist_free(p_device_class); } - debug_info("device uuid: %s", client_loc->uuid); - userpref_get_host_id(&host_id); - if (LOCKDOWN_E_SUCCESS == ret && !host_id) { + userpref_error_t uerr = userpref_read_pair_record(client_loc->device->udid, &pair_record); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } + if (pair_record) { + pair_record_get_host_id(pair_record, &host_id); + } + if (LOCKDOWN_E_SUCCESS == ret && pair_record && !host_id) { ret = LOCKDOWN_E_INVALID_CONF; } - if (LOCKDOWN_E_SUCCESS == ret && !userpref_has_device_public_key(client_loc->uuid)) + if (LOCKDOWN_E_SUCCESS == ret && !pair_record) { + /* attempt pairing */ + free(host_id); + host_id = NULL; ret = lockdownd_pair(client_loc, NULL); + } - /* in any case, we need to validate pairing to receive trusted host status */ - ret = lockdownd_validate_pair(client_loc, NULL); + plist_free(pair_record); + pair_record = NULL; - /* if not paired yet, let's do it now */ - if (LOCKDOWN_E_INVALID_HOST_ID == ret) { - ret = lockdownd_pair(client_loc, NULL); - if (LOCKDOWN_E_SUCCESS == ret) { - ret = lockdownd_validate_pair(client_loc, NULL); + if (device->version < DEVICE_VERSION(7,0,0) && device->device_class != DEVICE_CLASS_WATCH) { + /* for older devices, we need to validate pairing to receive trusted host status */ + ret = lockdownd_validate_pair(client_loc, NULL); + + /* if not paired yet, let's do it now */ + if (LOCKDOWN_E_INVALID_HOST_ID == ret) { + free(host_id); + host_id = NULL; + ret = lockdownd_pair(client_loc, NULL); + if (LOCKDOWN_E_SUCCESS == ret) { + ret = lockdownd_validate_pair(client_loc, NULL); + } } } if (LOCKDOWN_E_SUCCESS == ret) { + if (!host_id) { + uerr = userpref_read_pair_record(client_loc->device->udid, &pair_record); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } else if (uerr == USERPREF_E_NOENT) { + debug_info("ERROR: No pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_INVALID_CONF; + } else if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed to retrieve or parse pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_INVALID_CONF; + } + if (pair_record) { + pair_record_get_host_id(pair_record, &host_id); + plist_free(pair_record); + } + } + ret = lockdownd_start_session(client_loc, host_id, NULL, NULL); if (LOCKDOWN_E_SUCCESS != ret) { debug_info("Session opening failed."); } - if (host_id) { - free(host_id); - host_id = NULL; - } } - + if (LOCKDOWN_E_SUCCESS == ret) { *client = client_loc; } else { lockdownd_client_free(client_loc); } - + free(host_id); return ret; } @@ -740,68 +796,77 @@ static plist_t lockdownd_pair_record_to_plist(lockdownd_pair_record_t pair_recor if (!pair_record) return NULL; - char *host_id_loc = pair_record->host_id; - /* setup request plist */ plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "DeviceCertificate", plist_new_data(pair_record->device_certificate, strlen(pair_record->device_certificate))); - plist_dict_insert_item(dict, "HostCertificate", plist_new_data(pair_record->host_certificate, strlen(pair_record->host_certificate))); - if (!pair_record->host_id) - userpref_get_host_id(&host_id_loc); - plist_dict_insert_item(dict, "HostID", plist_new_string(host_id_loc)); - plist_dict_insert_item(dict, "RootCertificate", plist_new_data(pair_record->root_certificate, strlen(pair_record->root_certificate))); - - if (!pair_record->host_id) - free(host_id_loc); + plist_dict_set_item(dict, "DeviceCertificate", plist_new_data(pair_record->device_certificate, strlen(pair_record->device_certificate))); + plist_dict_set_item(dict, "HostCertificate", plist_new_data(pair_record->host_certificate, strlen(pair_record->host_certificate))); + plist_dict_set_item(dict, "HostID", plist_new_string(pair_record->host_id)); + plist_dict_set_item(dict, "RootCertificate", plist_new_data(pair_record->root_certificate, strlen(pair_record->root_certificate))); + plist_dict_set_item(dict, "SystemBUID", plist_new_string(pair_record->system_buid)); return dict; } /** - * Generates a new pairing record plist and required certificates for the - * supplied public key of the device and the host_id of the caller's host - * computer. + * Generates a pair record plist with required certificates for a specific + * device. If a pairing exists, it is loaded from the computer instead of being + * generated. * - * @param public_key The public key of the device. - * @param host_id The HostID to use for the pair record plist. - * @param pair_record_plist Holds the generated pair record. + * @param pair_record_plist Holds the pair record. * * @return LOCKDOWN_E_SUCCESS on success */ -static lockdownd_error_t generate_pair_record_plist(gnutls_datum_t public_key, char *host_id, plist_t *pair_record_plist) +static lockdownd_error_t pair_record_generate(lockdownd_client_t client, plist_t *pair_record) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - gnutls_datum_t device_cert = { NULL, 0 }; - gnutls_datum_t host_cert = { NULL, 0 }; - gnutls_datum_t root_cert = { NULL, 0 }; + key_data_t public_key = { NULL, 0 }; + char* host_id = NULL; + char* system_buid = NULL; - ret = lockdownd_gen_pair_cert(public_key, &device_cert, &host_cert, &root_cert); + /* retrieve device public key */ + ret = lockdownd_get_device_public_key_as_key_data(client, &public_key); if (ret != LOCKDOWN_E_SUCCESS) { - return ret; + debug_info("device refused to send public key."); + goto leave; + } + debug_info("device public key follows:\n%.*s", public_key.size, public_key.data); + + *pair_record = plist_new_dict(); + + /* generate keys and certificates into pair record */ + userpref_error_t uret = USERPREF_E_SUCCESS; + uret = pair_record_generate_keys_and_certs(*pair_record, public_key); + switch(uret) { + case USERPREF_E_INVALID_ARG: + ret = LOCKDOWN_E_INVALID_ARG; + break; + case USERPREF_E_INVALID_CONF: + ret = LOCKDOWN_E_INVALID_CONF; + break; + case USERPREF_E_SSL_ERROR: + ret = LOCKDOWN_E_SSL_ERROR; + default: + break; } - char *host_id_loc = host_id; + /* set SystemBUID */ + userpref_read_system_buid(&system_buid); + if (system_buid) { + plist_dict_set_item(*pair_record, USERPREF_SYSTEM_BUID_KEY, plist_new_string(system_buid)); + } - if (!host_id) - userpref_get_host_id(&host_id_loc); + /* set HostID */ + host_id = generate_uuid(); + pair_record_set_host_id(*pair_record, host_id); - /* setup request plist */ - *pair_record_plist = plist_new_dict(); - plist_dict_insert_item(*pair_record_plist, "DeviceCertificate", plist_new_data((const char*)device_cert.data, device_cert.size)); - plist_dict_insert_item(*pair_record_plist, "HostCertificate", plist_new_data((const char*)host_cert.data, host_cert.size)); - plist_dict_insert_item(*pair_record_plist, "HostID", plist_new_string(host_id_loc)); - plist_dict_insert_item(*pair_record_plist, "RootCertificate", plist_new_data((const char*)root_cert.data, root_cert.size)); - - if (!host_id) - free(host_id_loc); - - if (device_cert.data) - free(device_cert.data); - if (host_cert.data) - free(host_cert.data); - if (root_cert.data) - free(root_cert.data); +leave: + if (host_id) + free(host_id); + if (system_buid) + free(system_buid); + if (public_key.data) + free(public_key.data); return ret; } @@ -809,11 +874,13 @@ static lockdownd_error_t generate_pair_record_plist(gnutls_datum_t public_key, c /** * Function used internally by lockdownd_pair() and lockdownd_validate_pair() * - * @param client The lockdown client to pair with. + * @param client The lockdown client * @param pair_record The pair record to use for pairing. If NULL is passed, then * the pair records from the current machine are used. New records will be * generated automatically when pairing is done for the first time. * @param verb This is either "Pair", "ValidatePair" or "Unpair". + * @param options The pairing options to pass. + * @param response If non-NULL a pointer to lockdownd's response dictionary is returned. * * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, @@ -821,73 +888,103 @@ static lockdownd_error_t generate_pair_record_plist(gnutls_datum_t public_key, c * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id */ -static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record, const char *verb) +static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record, const char *verb, plist_t options, plist_t *result) { if (!client) return LOCKDOWN_E_INVALID_ARG; lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t dict = NULL; - plist_t dict_record = NULL; - gnutls_datum_t public_key = { NULL, 0 }; + plist_t pair_record_plist = NULL; + plist_t wifi_node = NULL; int pairing_mode = 0; /* 0 = libimobiledevice, 1 = external */ - if (pair_record && pair_record->host_id) { + if (pair_record && pair_record->system_buid && pair_record->host_id) { /* valid pair_record passed? */ if (!pair_record->device_certificate || !pair_record->host_certificate || !pair_record->root_certificate) { return LOCKDOWN_E_PLIST_ERROR; } /* use passed pair_record */ - dict_record = lockdownd_pair_record_to_plist(pair_record); + pair_record_plist = lockdownd_pair_record_to_plist(pair_record); pairing_mode = 1; } else { - ret = lockdownd_get_device_public_key(client, &public_key); - if (ret != LOCKDOWN_E_SUCCESS) { - if (public_key.data) - free(public_key.data); - debug_info("device refused to send public key."); - return ret; - } - debug_info("device public key follows:\n%.*s", public_key.size, public_key.data); - /* get libimobiledevice pair_record */ - ret = generate_pair_record_plist(public_key, NULL, &dict_record); - if (ret != LOCKDOWN_E_SUCCESS) { - if (dict_record) - plist_free(dict_record); - return ret; + /* generate a new pair record if pairing */ + if (!strcmp("Pair", verb)) { + ret = pair_record_generate(client, &pair_record_plist); + + if (ret != LOCKDOWN_E_SUCCESS) { + if (pair_record_plist) + plist_free(pair_record_plist); + return ret; + } + + /* get wifi mac now, if we get it later we fail on iOS 7 which causes a reconnect */ + lockdownd_get_value(client, NULL, "WiFiAddress", &wifi_node); + } else { + /* use existing pair record */ + userpref_error_t uerr = userpref_read_pair_record(client->device->udid, &pair_record_plist); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client->device->udid); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } else if (uerr == USERPREF_E_NOENT) { + debug_info("ERROR: No pair record for %s", client->device->udid); + return LOCKDOWN_E_INVALID_CONF; + } else if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed to retrieve or parse pair record for %s", client->device->udid); + return LOCKDOWN_E_INVALID_CONF; + } } } - /* Setup Pair request plist */ + plist_t request_pair_record = plist_copy(pair_record_plist); + + /* remove stuff that is private */ + plist_dict_remove_item(request_pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY); + plist_dict_remove_item(request_pair_record, USERPREF_HOST_PRIVATE_KEY_KEY); + + /* setup pair request plist */ dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"PairRecord", dict_record); - plist_dict_insert_item(dict, "Request", plist_new_string(verb)); + plist_dict_set_item(dict, "PairRecord", request_pair_record); + plist_dict_set_item(dict, "Request", plist_new_string(verb)); + plist_dict_set_item(dict, "ProtocolVersion", plist_new_string(LOCKDOWN_PROTOCOL_VERSION)); + + if (options) { + plist_dict_set_item(dict, "PairingOptions", plist_copy(options)); + } /* send to device */ ret = lockdownd_send(client, dict); plist_free(dict); dict = NULL; - if (ret != LOCKDOWN_E_SUCCESS) + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(pair_record_plist); + if (wifi_node) + plist_free(wifi_node); return ret; + } /* Now get device's answer */ ret = lockdownd_receive(client, &dict); - if (ret != LOCKDOWN_E_SUCCESS) + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(pair_record_plist); + if (wifi_node) + plist_free(wifi_node); return ret; + } if (strcmp(verb, "Unpair") == 0) { /* workaround for Unpair giving back ValidatePair, * seems to be a bug in the device's fw */ - if (lockdown_check_result(dict, NULL) != RESULT_SUCCESS) { + if (lockdown_check_result(dict, NULL) != LOCKDOWN_E_SUCCESS) { ret = LOCKDOWN_E_PAIRING_FAILED; } } else { - if (lockdown_check_result(dict, verb) != RESULT_SUCCESS) { + if (lockdown_check_result(dict, verb) != LOCKDOWN_E_SUCCESS) { ret = LOCKDOWN_E_PAIRING_FAILED; } } @@ -896,13 +993,32 @@ static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_ if (ret == LOCKDOWN_E_SUCCESS) { debug_info("%s success", verb); if (!pairing_mode) { + debug_info("internal pairing mode"); if (!strcmp("Unpair", verb)) { /* remove public key from config */ - userpref_remove_device_public_key(client->uuid); + userpref_delete_pair_record(client->device->udid); } else { - /* store public key in config */ - userpref_set_device_public_key(client->uuid, public_key); + if (!strcmp("Pair", verb)) { + /* add returned escrow bag if available */ + plist_t extra_node = plist_dict_get_item(dict, USERPREF_ESCROW_BAG_KEY); + if (extra_node && plist_get_node_type(extra_node) == PLIST_DATA) { + debug_info("Saving EscrowBag from response in pair record"); + plist_dict_set_item(pair_record_plist, USERPREF_ESCROW_BAG_KEY, plist_copy(extra_node)); + } + + /* save previously retrieved wifi mac address in pair record */ + if (wifi_node) { + debug_info("Saving WiFiAddress from device in pair record"); + plist_dict_set_item(pair_record_plist, USERPREF_WIFI_MAC_ADDRESS_KEY, plist_copy(wifi_node)); + plist_free(wifi_node); + wifi_node = NULL; + } + + userpref_save_pair_record(client->device->udid, client->device->mux_id, pair_record_plist); + } } + } else { + debug_info("external pairing mode"); } } else { debug_info("%s failure", verb); @@ -914,92 +1030,60 @@ static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_ plist_get_string_val(error_node, &value); if (value) { /* the first pairing fails if the device is password protected */ - if (!strcmp(value, "PasswordProtected")) { - ret = LOCKDOWN_E_PASSWORD_PROTECTED; - } else if (!strcmp(value, "InvalidHostID")) { - ret = LOCKDOWN_E_INVALID_HOST_ID; - } + ret = lockdownd_strtoerr(value); free(value); } - - plist_free(error_node); - error_node = NULL; } } - plist_free(dict); - dict = NULL; - if (public_key.data) - free(public_key.data); + + if (pair_record_plist) { + plist_free(pair_record_plist); + pair_record_plist = NULL; + } + + if (wifi_node) { + plist_free(wifi_node); + wifi_node = NULL; + } + + if (result) { + *result = dict; + } else { + plist_free(dict); + dict = NULL; + } + return ret; } -/** - * Pairs the device using the supplied pair record. - * - * @param client The lockdown client to pair with. - * @param pair_record The pair record to use for pairing. If NULL is passed, then - * the pair records from the current machine are used. New records will be - * generated automatically when pairing is done for the first time. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, - * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, - * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id - */ lockdownd_error_t lockdownd_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) { - return lockdownd_do_pair(client, pair_record, "Pair"); + + plist_t options = plist_new_dict(); + plist_dict_set_item(options, "ExtendedPairingErrors", plist_new_bool(1)); + + lockdownd_error_t ret = lockdownd_do_pair(client, pair_record, "Pair", options, NULL); + + plist_free(options); + + return ret; +} + +lockdownd_error_t lockdownd_pair_with_options(lockdownd_client_t client, lockdownd_pair_record_t pair_record, plist_t options, plist_t *response) +{ + return lockdownd_do_pair(client, pair_record, "Pair", options, response); } -/** - * Validates if the device is paired with the given HostID. If succeeded them - * specified host will become trusted host of the device indicated by the - * lockdownd preference named TrustedHostAttached. Otherwise the host must because - * paired using lockdownd_pair() first. - * - * @param client The lockdown client to pair with. - * @param pair_record The pair record to validate pairing with. If NULL is - * passed, then the pair record is read from the internal pairing record - * management. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, - * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, - * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id - */ lockdownd_error_t lockdownd_validate_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) { - return lockdownd_do_pair(client, pair_record, "ValidatePair"); + return lockdownd_do_pair(client, pair_record, "ValidatePair", NULL, NULL); } -/** - * Unpairs the device with the given HostID and removes the pairing records - * from the device and host if the internal pairing record management is used. - * - * @param client The lockdown client to pair with. - * @param pair_record The pair record to use for unpair. If NULL is passed, then - * the pair records from the current machine are used. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, - * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, - * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id - */ lockdownd_error_t lockdownd_unpair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) { - return lockdownd_do_pair(client, pair_record, "Unpair"); + return lockdownd_do_pair(client, pair_record, "Unpair", NULL, NULL); } -/** - * Tells the device to immediately enter recovery mode. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client) { if (!client) @@ -1009,7 +1093,7 @@ lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("EnterRecovery")); + plist_dict_set_item(dict,"Request", plist_new_string("EnterRecovery")); debug_info("telling device to enter recovery mode"); @@ -1019,23 +1103,17 @@ lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client) ret = lockdownd_receive(client, &dict); - if (lockdown_check_result(dict, "EnterRecovery") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "EnterRecovery"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; + return ret; } -/** - * Sends the Goodbye request to lockdownd signaling the end of communication. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the device did not acknowledge the request - */ lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client) { if (!client) @@ -1045,7 +1123,7 @@ lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Goodbye")); + plist_dict_set_item(dict,"Request", plist_new_string("Goodbye")); debug_info("called"); @@ -1059,187 +1137,17 @@ lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client) return LOCKDOWN_E_PLIST_ERROR; } - if (lockdown_check_result(dict, "Goodbye") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "Goodbye"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; - return ret; -} - -/** - * Generates the device certificate from the public key as well as the host - * and root certificates. - * - * @param public_key The public key of the device to use for generation. - * @param odevice_cert Holds the generated device certificate. - * @param ohost_cert Holds the generated host certificate. - * @param oroot_cert Holds the generated root certificate. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when a parameter is NULL, - * LOCKDOWN_E_INVALID_CONF if the internal configuration system failed, - * LOCKDOWN_E_SSL_ERROR if the certificates could not be generated - */ -lockdownd_error_t lockdownd_gen_pair_cert(gnutls_datum_t public_key, gnutls_datum_t * odevice_cert, - gnutls_datum_t * ohost_cert, gnutls_datum_t * oroot_cert) -{ - if (!public_key.data || !odevice_cert || !ohost_cert || !oroot_cert) - return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - userpref_error_t uret = USERPREF_E_UNKNOWN_ERROR; - - gnutls_datum_t modulus = { NULL, 0 }; - gnutls_datum_t exponent = { NULL, 0 }; - - /* now decode the PEM encoded key */ - gnutls_datum_t der_pub_key; - if (GNUTLS_E_SUCCESS == gnutls_pem_base64_decode_alloc("RSA PUBLIC KEY", &public_key, &der_pub_key)) { - - /* initalize asn.1 parser */ - ASN1_TYPE pkcs1 = ASN1_TYPE_EMPTY; - if (ASN1_SUCCESS == asn1_array2tree(pkcs1_asn1_tab, &pkcs1, NULL)) { - - ASN1_TYPE asn1_pub_key = ASN1_TYPE_EMPTY; - asn1_create_element(pkcs1, "PKCS1.RSAPublicKey", &asn1_pub_key); - - if (ASN1_SUCCESS == asn1_der_decoding(&asn1_pub_key, der_pub_key.data, der_pub_key.size, NULL)) { - - /* get size to read */ - int ret1 = asn1_read_value(asn1_pub_key, "modulus", NULL, (int*)&modulus.size); - int ret2 = asn1_read_value(asn1_pub_key, "publicExponent", NULL, (int*)&exponent.size); - - modulus.data = gnutls_malloc(modulus.size); - exponent.data = gnutls_malloc(exponent.size); - - ret1 = asn1_read_value(asn1_pub_key, "modulus", modulus.data, (int*)&modulus.size); - ret2 = asn1_read_value(asn1_pub_key, "publicExponent", exponent.data, (int*)&exponent.size); - if (ASN1_SUCCESS == ret1 && ASN1_SUCCESS == ret2) - ret = LOCKDOWN_E_SUCCESS; - } - if (asn1_pub_key) - asn1_delete_structure(&asn1_pub_key); - } - if (pkcs1) - asn1_delete_structure(&pkcs1); - } - - /* now generate certificates */ - if (LOCKDOWN_E_SUCCESS == ret && 0 != modulus.size && 0 != exponent.size) { - - gnutls_global_init(); - gnutls_datum_t essentially_null = { (unsigned char*)strdup("abababababababab"), strlen("abababababababab") }; - - gnutls_x509_privkey_t fake_privkey, root_privkey, host_privkey; - gnutls_x509_crt_t dev_cert, root_cert, host_cert; - - gnutls_x509_privkey_init(&fake_privkey); - gnutls_x509_crt_init(&dev_cert); - gnutls_x509_crt_init(&root_cert); - gnutls_x509_crt_init(&host_cert); - - if (GNUTLS_E_SUCCESS == - gnutls_x509_privkey_import_rsa_raw(fake_privkey, &modulus, &exponent, &essentially_null, &essentially_null, - &essentially_null, &essentially_null)) { - - gnutls_x509_privkey_init(&root_privkey); - gnutls_x509_privkey_init(&host_privkey); - - uret = userpref_get_keys_and_certs(root_privkey, root_cert, host_privkey, host_cert); - - if (USERPREF_E_SUCCESS == uret) { - /* generate device certificate */ - gnutls_x509_crt_set_key(dev_cert, fake_privkey); - gnutls_x509_crt_set_serial(dev_cert, "\x00", 1); - gnutls_x509_crt_set_version(dev_cert, 3); - gnutls_x509_crt_set_ca_status(dev_cert, 0); - gnutls_x509_crt_set_activation_time(dev_cert, time(NULL)); - gnutls_x509_crt_set_expiration_time(dev_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); - gnutls_x509_crt_sign(dev_cert, root_cert, root_privkey); - - if (LOCKDOWN_E_SUCCESS == ret) { - /* if everything went well, export in PEM format */ - size_t export_size = 0; - gnutls_datum_t dev_pem = { NULL, 0 }; - gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, NULL, &export_size); - dev_pem.data = gnutls_malloc(export_size); - gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, dev_pem.data, &export_size); - dev_pem.size = export_size; - - gnutls_datum_t pem_root_cert = { NULL, 0 }; - gnutls_datum_t pem_host_cert = { NULL, 0 }; - - uret = userpref_get_certs_as_pem(&pem_root_cert, &pem_host_cert); - - if (USERPREF_E_SUCCESS == uret) { - /* copy buffer for output */ - odevice_cert->data = malloc(dev_pem.size); - memcpy(odevice_cert->data, dev_pem.data, dev_pem.size); - odevice_cert->size = dev_pem.size; - - ohost_cert->data = malloc(pem_host_cert.size); - memcpy(ohost_cert->data, pem_host_cert.data, pem_host_cert.size); - ohost_cert->size = pem_host_cert.size; - - oroot_cert->data = malloc(pem_root_cert.size); - memcpy(oroot_cert->data, pem_root_cert.data, pem_root_cert.size); - oroot_cert->size = pem_root_cert.size; - - g_free(pem_root_cert.data); - g_free(pem_host_cert.data); - - if (dev_pem.data) - gnutls_free(dev_pem.data); - } - } - } - - switch(uret) { - case USERPREF_E_INVALID_ARG: - ret = LOCKDOWN_E_INVALID_ARG; - break; - case USERPREF_E_INVALID_CONF: - ret = LOCKDOWN_E_INVALID_CONF; - break; - case USERPREF_E_SSL_ERROR: - ret = LOCKDOWN_E_SSL_ERROR; - default: - break; - } - } - - if (essentially_null.data) - free(essentially_null.data); - gnutls_x509_crt_deinit(dev_cert); - gnutls_x509_crt_deinit(root_cert); - gnutls_x509_crt_deinit(host_cert); - gnutls_x509_privkey_deinit(fake_privkey); - gnutls_x509_privkey_deinit(root_privkey); - gnutls_x509_privkey_deinit(host_privkey); - - } - - gnutls_free(modulus.data); - gnutls_free(exponent.data); - - gnutls_free(der_pub_key.data); return ret; } -/** - * Opens a session with lockdownd and switches to SSL mode if device wants it. - * - * @param client The lockdownd client - * @param host_id The HostID of the computer - * @param session_id The new session_id of the created session - * @param ssl_enabled Whether SSL communication is used in the session - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when a client or - * host_id is NULL, LOCKDOWN_E_PLIST_ERROR if the response plist had errors, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the supplied HostID, - * LOCKDOWN_E_SSL_ERROR if enabling SSL communication failed - */ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char *host_id, char **session_id, int *ssl_enabled) { lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; @@ -1251,14 +1159,28 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char /* if we have a running session, stop current one first */ if (client->session_id) { lockdownd_stop_session(client, client->session_id); - free(client->session_id); } /* setup request plist */ dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"HostID", plist_new_string(host_id)); - plist_dict_insert_item(dict,"Request", plist_new_string("StartSession")); + plist_dict_set_item(dict,"Request", plist_new_string("StartSession")); + + /* add host id */ + if (host_id) { + plist_dict_set_item(dict, "HostID", plist_new_string(host_id)); + } + + /* add system buid */ + char *system_buid = NULL; + userpref_read_system_buid(&system_buid); + if (system_buid) { + plist_dict_set_item(dict, "SystemBUID", plist_new_string(system_buid)); + if (system_buid) { + free(system_buid); + system_buid = NULL; + } + } ret = lockdownd_send(client, dict); plist_free(dict); @@ -1272,17 +1194,8 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char if (!dict) return LOCKDOWN_E_PLIST_ERROR; - if (lockdown_check_result(dict, "StartSession") == RESULT_FAILURE) { - plist_t error_node = plist_dict_get_item(dict, "Error"); - if (error_node && PLIST_STRING == plist_get_node_type(error_node)) { - char *error = NULL; - plist_get_string_val(error_node, &error); - if (!strcmp(error, "InvalidHostID")) { - ret = LOCKDOWN_E_INVALID_HOST_ID; - } - free(error); - } - } else { + ret = lockdown_check_result(dict, "StartSession"); + if (ret == LOCKDOWN_E_SUCCESS) { uint8_t use_ssl = 0; plist_t enable_ssl = plist_dict_get_item(dict, "EnableSessionSSL"); @@ -1299,6 +1212,7 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char if (session_node && (plist_get_node_type(session_node) == PLIST_STRING)) { plist_get_string_val(session_node, &client->session_id); } + if (client->session_id) { debug_info("SessionID: %s", client->session_id); if (session_id != NULL) @@ -1306,18 +1220,15 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char } else { debug_info("Failed to get SessionID!"); } - debug_info("Enable SSL Session: %s", (use_ssl?"true":"false")); + + debug_info("Enable SSL Session: %s", (use_ssl ? "true" : "false")); + if (use_ssl) { - ret = property_list_service_enable_ssl(client->parent); - if (ret == PROPERTY_LIST_SERVICE_E_SUCCESS) { - client->ssl_enabled = 1; - } else { - ret = LOCKDOWN_E_SSL_ERROR; - client->ssl_enabled = 0; - } + ret = lockdownd_error(property_list_service_enable_ssl(client->parent)); + client->ssl_enabled = (ret == LOCKDOWN_E_SUCCESS ? 1 : 0); } else { - client->ssl_enabled = 0; ret = LOCKDOWN_E_SUCCESS; + client->ssl_enabled = 0; } } @@ -1328,40 +1239,96 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char } /** - * Requests to start a service and retrieve it's port on success. + * Internal function used by lockdownd_do_start_service to create the + * StartService request's plist. * * @param client The lockdownd client - * @param service The name of the service to start - * @param port The port number the service was started on - - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG if a parameter + * @param identifier The identifier of the service to start + * @param send_escrow_bag Should we send the device's escrow bag with the request + * @param request The request's plist on success, NULL on failure + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_CONF on failure + * to read the escrow bag from the device's record (when used). + */ +static lockdownd_error_t lockdownd_build_start_service_request(lockdownd_client_t client, const char *identifier, int send_escrow_bag, plist_t *request) +{ + plist_t dict = plist_new_dict(); + + /* create the basic request params */ + plist_dict_add_label(dict, client->label); + plist_dict_set_item(dict, "Request", plist_new_string("StartService")); + plist_dict_set_item(dict, "Service", plist_new_string(identifier)); + + /* if needed - get the escrow bag for the device and send it with the request */ + if (send_escrow_bag) { + /* get the pairing record */ + plist_t pair_record = NULL; + userpref_error_t uerr = userpref_read_pair_record(client->device->udid, &pair_record); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client->device->udid); + plist_free(dict); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } else if (uerr == USERPREF_E_NOENT) { + debug_info("ERROR: No pair record for %s", client->device->udid); + plist_free(dict); + return LOCKDOWN_E_INVALID_CONF; + } else if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed to retrieve or parse pair record for %s", client->device->udid); + plist_free(dict); + return LOCKDOWN_E_INVALID_CONF; + } + + /* try to read the escrow bag from the record */ + plist_t escrow_bag = plist_dict_get_item(pair_record, USERPREF_ESCROW_BAG_KEY); + if (!escrow_bag || (PLIST_DATA != plist_get_node_type(escrow_bag))) { + debug_info("ERROR: Failed to retrieve the escrow bag from the device's record"); + plist_free(dict); + plist_free(pair_record); + return LOCKDOWN_E_INVALID_CONF; + } + + debug_info("Adding escrow bag to StartService for %s", identifier); + plist_dict_set_item(dict, USERPREF_ESCROW_BAG_KEY, plist_copy(escrow_bag)); + plist_free(pair_record); + } + + *request = dict; + return LOCKDOWN_E_SUCCESS; +} + +/** + * Function used internally by lockdownd_start_service and lockdownd_start_service_with_escrow_bag. + * + * @param client The lockdownd client + * @param identifier The identifier of the service to start + * @param send_escrow_bag Should we send the device's escrow bag with the request + * @param descriptor The service descriptor on success or NULL on failure + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if a parameter * is NULL, LOCKDOWN_E_INVALID_SERVICE if the requested service is not known * by the device, LOCKDOWN_E_START_SERVICE_FAILED if the service could not because - * started by the device + * started by the device, LOCKDOWN_E_INVALID_CONF if the host id or escrow bag (when + * used) are missing from the device record. */ -lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *service, uint16_t *port) +static lockdownd_error_t lockdownd_do_start_service(lockdownd_client_t client, const char *identifier, int send_escrow_bag, lockdownd_service_descriptor_t *service) { - if (!client || !service || !port) + if (!client || !identifier || !service) return LOCKDOWN_E_INVALID_ARG; - char *host_id = NULL; - userpref_get_host_id(&host_id); - if (!host_id) - return LOCKDOWN_E_INVALID_CONF; - if (!client->session_id) - return LOCKDOWN_E_NO_RUNNING_SESSION; + if (*service) { + // reset fields if service descriptor is reused + (*service)->port = 0; + (*service)->ssl_enabled = 0; + } plist_t dict = NULL; uint16_t port_loc = 0; lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - free(host_id); - host_id = NULL; - - dict = plist_new_dict(); - plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("StartService")); - plist_dict_insert_item(dict,"Service", plist_new_string(service)); + /* create StartService request */ + ret = lockdownd_build_start_service_request(client, identifier, send_escrow_bag, &dict); + if (LOCKDOWN_E_SUCCESS != ret) + return ret; /* send to device */ ret = lockdownd_send(client, dict); @@ -1379,57 +1346,63 @@ lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char if (!dict) return LOCKDOWN_E_PLIST_ERROR; - ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "StartService") == RESULT_SUCCESS) { - plist_t port_value_node = plist_dict_get_item(dict, "Port"); - - if (port_value_node && (plist_get_node_type(port_value_node) == PLIST_UINT)) { + ret = lockdown_check_result(dict, "StartService"); + if (ret == LOCKDOWN_E_SUCCESS) { + if (*service == NULL) + *service = (lockdownd_service_descriptor_t)malloc(sizeof(struct lockdownd_service_descriptor)); + (*service)->port = 0; + (*service)->ssl_enabled = 0; + (*service)->identifier = strdup(identifier); + + /* read service port number */ + plist_t node = plist_dict_get_item(dict, "Port"); + if (node && (plist_get_node_type(node) == PLIST_UINT)) { uint64_t port_value = 0; - plist_get_uint_val(port_value_node, &port_value); + plist_get_uint_val(node, &port_value); if (port_value) { port_loc = port_value; ret = LOCKDOWN_E_SUCCESS; } - if (port && ret == LOCKDOWN_E_SUCCESS) - *port = port_loc; + if (port_loc && ret == LOCKDOWN_E_SUCCESS) { + (*service)->port = port_loc; + } + } + + /* check if the service requires SSL */ + node = plist_dict_get_item(dict, "EnableServiceSSL"); + if (node && (plist_get_node_type(node) == PLIST_BOOLEAN)) { + uint8_t b = 0; + plist_get_bool_val(node, &b); + (*service)->ssl_enabled = b; } } else { - ret = LOCKDOWN_E_START_SERVICE_FAILED; plist_t error_node = plist_dict_get_item(dict, "Error"); if (error_node && PLIST_STRING == plist_get_node_type(error_node)) { char *error = NULL; plist_get_string_val(error_node, &error); - if (!strcmp(error, "InvalidService")) { - ret = LOCKDOWN_E_INVALID_SERVICE; - } + ret = lockdownd_strtoerr(error); free(error); } } plist_free(dict); dict = NULL; + return ret; } -/** - * Activates the device. Only works within an open session. - * The ActivationRecord plist dictionary must be obtained using the - * activation protocol requesting from Apple's https webservice. - * - * @see http://iphone-docs.org/doku.php?id=docs:protocols:activation - * - * @param client The lockdown client - * @param activation_record The activation record plist dictionary - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * activation_record is NULL, LOCKDOWN_E_NO_RUNNING_SESSION if no session is - * open, LOCKDOWN_E_PLIST_ERROR if the received plist is broken, - * LOCKDOWN_E_ACTIVATION_FAILED if the activation failed, - * LOCKDOWN_E_INVALID_ACTIVATION_RECORD if the device reports that the - * activation_record is invalid - */ -lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activation_record) +lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service) +{ + return lockdownd_do_start_service(client, identifier, 0, service); +} + +lockdownd_error_t lockdownd_start_service_with_escrow_bag(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service) +{ + return lockdownd_do_start_service(client, identifier, 1, service); +} + +lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activation_record) { if (!client) return LOCKDOWN_E_INVALID_ARG; @@ -1444,8 +1417,8 @@ lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activati plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Activate")); - plist_dict_insert_item(dict,"ActivationRecord", plist_copy(activation_record)); + plist_dict_set_item(dict,"Request", plist_new_string("Activate")); + plist_dict_set_item(dict,"ActivationRecord", plist_copy(activation_record)); ret = lockdownd_send(client, dict); plist_free(dict); @@ -1457,39 +1430,17 @@ lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activati return LOCKDOWN_E_PLIST_ERROR; } - ret = LOCKDOWN_E_ACTIVATION_FAILED; - if (lockdown_check_result(dict, "Activate") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "Activate"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; - - } else { - plist_t error_node = plist_dict_get_item(dict, "Error"); - if (error_node && PLIST_STRING == plist_get_node_type(error_node)) { - char *error = NULL; - plist_get_string_val(error_node, &error); - if (!strcmp(error, "InvalidActivationRecord")) { - ret = LOCKDOWN_E_INVALID_ACTIVATION_RECORD; - } - free(error); - } } - + plist_free(dict); dict = NULL; return ret; } -/** - * Deactivates the device, returning it to the locked “Activate with iTunes” - * screen. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_NO_RUNNING_SESSION if no session is open, - * LOCKDOWN_E_PLIST_ERROR if the received plist is broken - */ lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client) { if (!client) @@ -1502,7 +1453,7 @@ lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Deactivate")); + plist_dict_set_item(dict,"Request", plist_new_string("Deactivate")); ret = lockdownd_send(client, dict); plist_free(dict); @@ -1514,11 +1465,11 @@ lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client) return LOCKDOWN_E_PLIST_ERROR; } - ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "Deactivate") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "Deactivate"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; @@ -1537,19 +1488,6 @@ static void str_remove_spaces(char *source) *dest = 0; } -/** - * Calculates and returns the data classes the device supports from lockdownd. - * - * @param client An initialized lockdownd client. - * @param classes A pointer to store an array of class names. The caller is responsible - * for freeing the memory which can be done using mobilesync_data_classes_free(). - * @param count The number of items in the classes array. - * - * @return LOCKDOWN_E_SUCCESS on success, - * LOCKDOWN_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_NO_RUNNING_SESSION if no session is open, - * LOCKDOWN_E_PLIST_ERROR if the received plist is broken - */ lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, char ***classes, int *count) { if (!client) @@ -1583,14 +1521,16 @@ lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, cha } while((value = plist_array_get_item(dict, *count)) != NULL) { - plist_get_string_val(value, &val); - newlist = realloc(*classes, sizeof(char*) * (*count+1)); - str_remove_spaces(val); - asprintf(&newlist[*count], "com.apple.%s", val); - free(val); - val = NULL; - *classes = newlist; - *count = *count+1; + plist_get_string_val(value, &val); + newlist = realloc(*classes, sizeof(char*) * (*count+1)); + str_remove_spaces(val); + if (asprintf(&newlist[*count], "com.apple.%s", val) < 0) { + debug_info("ERROR: asprintf failed"); + } + free(val); + val = NULL; + *classes = newlist; + *count = *count+1; } newlist = realloc(*classes, sizeof(char*) * (*count+1)); @@ -1603,14 +1543,6 @@ lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, cha return LOCKDOWN_E_SUCCESS; } - -/** - * Frees memory of an allocated array of data classes as returned by lockdownd_get_sync_data_classes() - * - * @param classes An array of class names to free. - * - * @return LOCKDOWN_E_SUCCESS on success - */ lockdownd_error_t lockdownd_data_classes_free(char **classes) { if (classes) { @@ -1622,3 +1554,51 @@ lockdownd_error_t lockdownd_data_classes_free(char **classes) } return LOCKDOWN_E_SUCCESS; } + +lockdownd_error_t lockdownd_service_descriptor_free(lockdownd_service_descriptor_t service) +{ + if (service) { + free(service->identifier); + free(service); + } + + return LOCKDOWN_E_SUCCESS; +} + +const char* lockdownd_strerror(lockdownd_error_t err) +{ + switch (err) { + case LOCKDOWN_E_SUCCESS: + return "Success"; + case LOCKDOWN_E_INVALID_ARG: + return "Invalid argument"; + case LOCKDOWN_E_INVALID_CONF: + return "Invalid configuration"; + case LOCKDOWN_E_PLIST_ERROR: + return "PropertyList error"; + case LOCKDOWN_E_PAIRING_FAILED: + return "Pairing failed"; + case LOCKDOWN_E_SSL_ERROR: + return "SSL error"; + case LOCKDOWN_E_DICT_ERROR: + return "Invalid dictionary"; + case LOCKDOWN_E_RECEIVE_TIMEOUT: + return "Receive timeout"; + case LOCKDOWN_E_MUX_ERROR: + return "Mux error"; + case LOCKDOWN_E_NO_RUNNING_SESSION: + return "No running session"; + case LOCKDOWN_E_UNKNOWN_ERROR: + return "Unknown Error"; + default: { + int i = 0; + while (lockdownd_error_str_map[i].lockdown_errstr) { + if (lockdownd_error_str_map[i].errcode == err) { + return lockdownd_error_str_map[i].errstr; + } + i++; + } + } break; + } + return "Unknown Error"; +} diff --git a/src/lockdown.h b/src/lockdown.h index a25e59d..ba291ec 100644 --- a/src/lockdown.h +++ b/src/lockdown.h @@ -1,7 +1,8 @@ /* - * lockdownd.h + * lockdown.h * Defines lockdown stuff, like the client struct. * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -19,24 +20,25 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef LOCKDOWND_H -#define LOCKDOWND_H - -#include <gnutls/gnutls.h> +#ifndef __LOCKDOWND_H +#define __LOCKDOWND_H +#include "idevice.h" #include "libimobiledevice/lockdown.h" #include "property_list_service.h" +#define LOCKDOWN_PROTOCOL_VERSION "2" + struct lockdownd_client_private { property_list_service_client_t parent; int ssl_enabled; char *session_id; - char *uuid; char *label; + idevice_t device; + unsigned char* cu_key; + unsigned int cu_key_len; }; -lockdownd_error_t lockdownd_get_device_public_key(lockdownd_client_t client, gnutls_datum_t * public_key); -lockdownd_error_t lockdownd_gen_pair_cert(gnutls_datum_t public_key, gnutls_datum_t * device_cert, - gnutls_datum_t * host_cert, gnutls_datum_t * root_cert); +lockdownd_error_t lockdown_check_result(plist_t dict, const char *query_match); #endif diff --git a/src/misagent.c b/src/misagent.c new file mode 100644 index 0000000..e3da997 --- /dev/null +++ b/src/misagent.c @@ -0,0 +1,290 @@ +/* + * misagent.c + * com.apple.misagent service implementation. + * + * Copyright (c) 2012 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <plist/plist.h> +#include <stdio.h> + +#include "misagent.h" +#include "property_list_service.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a misagent_error_t + * value. Used internally to get correct error codes. + * + * @param err A property_list_service_error_t error code + * + * @return A matching misagent_error_t error code, + * MISAGENT_E_UNKNOWN_ERROR otherwise. + */ +static misagent_error_t misagent_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return MISAGENT_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return MISAGENT_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return MISAGENT_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return MISAGENT_E_CONN_FAILED; + default: + break; + } + return MISAGENT_E_UNKNOWN_ERROR; +} + +/** + * Checks the response from misagent to determine if the operation + * was successful or an error occurred. Internally used only. + * + * @param response a PLIST_DICT received from device's misagent + * @param status_code pointer to an int that will be set to the status code + * contained in the response + */ +static misagent_error_t misagent_check_result(plist_t response, int* status_code) +{ + if (plist_get_node_type(response) != PLIST_DICT) { + return MISAGENT_E_PLIST_ERROR; + } + + plist_t node = plist_dict_get_item(response, "Status"); + if (!node || (plist_get_node_type(node) != PLIST_UINT)) { + return MISAGENT_E_PLIST_ERROR; + } + + uint64_t val = -1LL; + plist_get_uint_val(node, &val); + if ((int64_t)val == -1LL) { + return MISAGENT_E_PLIST_ERROR; + } + *status_code = (int)(val & 0xFFFFFFFF); + if (*status_code == 0) { + return MISAGENT_E_SUCCESS; + } + return MISAGENT_E_REQUEST_FAILED; +} + +misagent_error_t misagent_client_new(idevice_t device, lockdownd_service_descriptor_t service, misagent_client_t *client) +{ + property_list_service_client_t plistclient = NULL; + misagent_error_t err = misagent_error(property_list_service_client_new(device, service, &plistclient)); + if (err != MISAGENT_E_SUCCESS) { + return err; + } + + misagent_client_t client_loc = (misagent_client_t) malloc(sizeof(struct misagent_client_private)); + client_loc->parent = plistclient; + client_loc->last_error = 0; + + *client = client_loc; + return MISAGENT_E_SUCCESS; +} + +misagent_error_t misagent_client_start_service(idevice_t device, misagent_client_t * client, const char* label) +{ + misagent_error_t err = MISAGENT_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MISAGENT_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(misagent_client_new), &err); + return err; +} + +misagent_error_t misagent_client_free(misagent_client_t client) +{ + if (!client) + return MISAGENT_E_INVALID_ARG; + + misagent_error_t err = MISAGENT_E_SUCCESS; + if (client->parent && client->parent->parent) { + misagent_error(property_list_service_client_free(client->parent)); + } + client->parent = NULL; + free(client); + + return err; +} + +misagent_error_t misagent_install(misagent_client_t client, plist_t profile) +{ + if (!client || !client->parent || !profile || (plist_get_node_type(profile) != PLIST_DATA)) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Install")); + plist_dict_set_item(dict, "Profile", plist_copy(profile)); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + plist_free(dict); + + return res; +} + +misagent_error_t misagent_copy(misagent_client_t client, plist_t* profiles) +{ + if (!client || !client->parent || !profiles) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Copy")); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + if (res == MISAGENT_E_SUCCESS) { + *profiles = plist_copy(plist_dict_get_item(dict, "Payload")); + } + plist_free(dict); + + return res; + +} + +misagent_error_t misagent_copy_all(misagent_client_t client, plist_t* profiles) +{ + if (!client || !client->parent || !profiles) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("CopyAll")); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + if (res == MISAGENT_E_SUCCESS) { + *profiles = plist_copy(plist_dict_get_item(dict, "Payload")); + } + plist_free(dict); + + return res; + +} + +misagent_error_t misagent_remove(misagent_client_t client, const char* profileID) +{ + if (!client || !client->parent || !profileID) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Remove")); + plist_dict_set_item(dict, "ProfileID", plist_new_string(profileID)); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + plist_free(dict); + + return res; +} + +int misagent_get_status_code(misagent_client_t client) +{ + if (!client) { + return -1; + } + return client->last_error; +} diff --git a/src/misagent.h b/src/misagent.h new file mode 100644 index 0000000..e394087 --- /dev/null +++ b/src/misagent.h @@ -0,0 +1,34 @@ +/* + * misagent.h + * com.apple.misagent service header file. + * + * Copyright (c) 2012 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __MISAGENT_H +#define __MISAGENT_H + +#include "idevice.h" +#include "libimobiledevice/misagent.h" +#include "property_list_service.h" + +struct misagent_client_private { + property_list_service_client_t parent; + int last_error; +}; + +#endif diff --git a/src/mobile_image_mounter.c b/src/mobile_image_mounter.c index 367bee0..6df50c4 100644 --- a/src/mobile_image_mounter.c +++ b/src/mobile_image_mounter.c @@ -2,23 +2,26 @@ * mobile_image_mounter.c * com.apple.mobile.mobile_image_mounter service implementation. * - * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> #include <unistd.h> @@ -26,7 +29,7 @@ #include "mobile_image_mounter.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" /** * Locks a mobile_image_mounter client, used for thread safety. @@ -35,17 +38,17 @@ */ static void mobile_image_mounter_lock(mobile_image_mounter_client_t client) { - g_mutex_lock(client->mutex); + mutex_lock(&client->mutex); } /** * Unlocks a mobile_image_mounter client, used for thread safety. - * + * * @param client mobile_image_mounter client to unlock */ static void mobile_image_mounter_unlock(mobile_image_mounter_client_t client) { - g_mutex_unlock(client->mutex); + mutex_unlock(&client->mutex); } /** @@ -75,51 +78,30 @@ static mobile_image_mounter_error_t mobile_image_mounter_error(property_list_ser return MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; } -/** - * Connects to the mobile_image_mounter service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * mobile_image_mounter_client_t upon successful return. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if device is NULL, - * or MOBILE_IMAGE_MOUNTER_E_CONN_FAILED if the connection to the - * device could not be established. - */ -mobile_image_mounter_error_t mobile_image_mounter_new(idevice_t device, uint16_t port, mobile_image_mounter_client_t *client) +mobile_image_mounter_error_t mobile_image_mounter_new(idevice_t device, lockdownd_service_descriptor_t service, mobile_image_mounter_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return MOBILE_IMAGE_MOUNTER_E_CONN_FAILED; + mobile_image_mounter_error_t err = mobile_image_mounter_error(property_list_service_client_new(device, service, &plistclient)); + if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + return err; } mobile_image_mounter_client_t client_loc = (mobile_image_mounter_client_t) malloc(sizeof(struct mobile_image_mounter_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); + mutex_init(&client_loc->mutex); *client = client_loc; return MOBILE_IMAGE_MOUNTER_E_SUCCESS; } -/** - * Disconnects a mobile_image_mounter client from the device and frees up the - * mobile_image_mounter client data. - * - * @param client The mobile_image_mounter client to disconnect and free. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * or MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if client is NULL. - */ +mobile_image_mounter_error_t mobile_image_mounter_start_service(idevice_t device, mobile_image_mounter_client_t * client, const char* label) +{ + mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILE_IMAGE_MOUNTER_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobile_image_mounter_new), &err); + return err; +} + mobile_image_mounter_error_t mobile_image_mounter_free(mobile_image_mounter_client_t client) { if (!client) @@ -127,27 +109,12 @@ mobile_image_mounter_error_t mobile_image_mounter_free(mobile_image_mounter_clie property_list_service_client_free(client->parent); client->parent = NULL; - if (client->mutex) { - g_mutex_free(client->mutex); - } + mutex_destroy(&client->mutex); free(client); return MOBILE_IMAGE_MOUNTER_E_SUCCESS; } -/** - * Tells if the image of ImageType is already mounted. - * - * @param client The client use - * @param image_type The type of the image to look up - * @param result Pointer to a plist that will receive the result of the - * operation. - * - * @note This function may return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the - * operation has failed. Check the resulting plist for further information. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, or an error code on error - */ mobile_image_mounter_error_t mobile_image_mounter_lookup_image(mobile_image_mounter_client_t client, const char *image_type, plist_t *result) { if (!client || !image_type || !result) { @@ -156,8 +123,8 @@ mobile_image_mounter_error_t mobile_image_mounter_lookup_image(mobile_image_moun mobile_image_mounter_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("LookupImage")); - plist_dict_insert_item(dict,"ImageType", plist_new_string(image_type)); + plist_dict_set_item(dict,"Command", plist_new_string("LookupImage")); + plist_dict_set_item(dict,"ImageType", plist_new_string(image_type)); mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); @@ -177,39 +144,139 @@ leave_unlock: return res; } -/** - * Mounts an image on the device. - * - * @param client The connected mobile_image_mounter client. - * @param image_path The absolute path of the image to mount. The image must - * be present before calling this function. - * @param image_signature Pointer to a buffer holding the images' signature - * @param signature_length Length of the signature image_signature points to - * @param image_type Type of image to mount - * @param result Pointer to a plist that will receive the result of the - * operation. - * - * @note This function may return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the - * operation has failed. Check the resulting plist for further information. - * Note that there is no unmounting function. The mount persists until the - * device is rebooted. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if on ore more parameters are - * invalid, or another error code otherwise. - */ -mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const char *image_signature, uint16_t signature_length, const char *image_type, plist_t *result) +static mobile_image_mounter_error_t process_result(plist_t result, const char *expected_status) +{ + mobile_image_mounter_error_t res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + char* strval = NULL; + plist_t node; + + node = plist_dict_get_item(result, "Error"); + if (node && plist_get_node_type(node) == PLIST_STRING) { + plist_get_string_val(node, &strval); + } + if (strval) { + if (!strcmp(strval, "DeviceLocked")) { + debug_info("Device is locked, can't mount"); + res = MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED; + } else { + debug_info("Unhandled error '%s' received", strval); + } + free(strval); + return res; + } + + node = plist_dict_get_item(result, "Status"); + if (node && plist_get_node_type(node) == PLIST_STRING) { + plist_get_string_val(node, &strval); + } + if (!strval) { + debug_info("Error: Unexpected response received!"); + } else if (strcmp(strval, expected_status) == 0) { + res = MOBILE_IMAGE_MOUNTER_E_SUCCESS; + } else { + debug_info("Error: didn't get %s but %s", expected_status, strval); + } + free(strval); + + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_upload_image(mobile_image_mounter_client_t client, const char *image_type, size_t image_size, const unsigned char *signature, unsigned int signature_size, mobile_image_mounter_upload_cb_t upload_cb, void* userdata) +{ + if (!client || !image_type || (image_size == 0) || !upload_cb) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + plist_t result = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("ReceiveBytes")); + if (signature && signature_size != 0) + plist_dict_set_item(dict, "ImageSignature", plist_new_data((char*)signature, signature_size)); + plist_dict_set_item(dict, "ImageSize", plist_new_uint(image_size)); + plist_dict_set_item(dict, "ImageType", plist_new_string(image_type)); + + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("Error sending XML plist to device!"); + goto leave_unlock; + } + + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("Error receiving response from device!"); + goto leave_unlock; + } + res = process_result(result, "ReceiveBytesAck"); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + goto leave_unlock; + } + + size_t tx = 0; + size_t bufsize = 65536; + unsigned char *buf = (unsigned char*)malloc(bufsize); + if (!buf) { + debug_info("Out of memory"); + res = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; + goto leave_unlock; + } + debug_info("uploading image (%d bytes)", (int)image_size); + while (tx < image_size) { + size_t remaining = image_size - tx; + size_t amount = (remaining < bufsize) ? remaining : bufsize; + ssize_t r = upload_cb(buf, amount, userdata); + if (r < 0) { + debug_info("upload_cb returned %d", (int)r); + break; + } + uint32_t sent = 0; + if (service_send(client->parent->parent, (const char*)buf, (uint32_t)r, &sent) != SERVICE_E_SUCCESS) { + debug_info("service_send failed"); + break; + } + tx += r; + } + free(buf); + if (tx < image_size) { + debug_info("Error: failed to upload image"); + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + goto leave_unlock; + } + debug_info("image uploaded"); + + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("Error receiving response from device!"); + goto leave_unlock; + } + res = process_result(result, "Complete"); + +leave_unlock: + mobile_image_mounter_unlock(client); + if (result) + plist_free(result); + return res; + +} + +mobile_image_mounter_error_t mobile_image_mounter_mount_image_with_options(mobile_image_mounter_client_t client, const char *image_path, const unsigned char *signature, unsigned int signature_size, const char *image_type, plist_t options, plist_t *result) { - if (!client || !image_path || !image_signature || (signature_length == 0) || !image_type || !result) { + if (!client || !image_path || !image_type || !result) { return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; } mobile_image_mounter_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Command", plist_new_string("MountImage")); - plist_dict_insert_item(dict, "ImagePath", plist_new_string(image_path)); - plist_dict_insert_item(dict, "ImageSignature", plist_new_data(image_signature, signature_length)); - plist_dict_insert_item(dict, "ImageType", plist_new_string(image_type)); + plist_dict_set_item(dict, "Command", plist_new_string("MountImage")); + plist_dict_set_item(dict, "ImagePath", plist_new_string(image_path)); + if (signature && signature_size != 0) + plist_dict_set_item(dict, "ImageSignature", plist_new_data((char*)signature, signature_size)); + plist_dict_set_item(dict, "ImageType", plist_new_string(image_type)); + if (PLIST_IS_DICT(options)) { + plist_dict_merge(&dict, options); + } mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); @@ -229,17 +296,56 @@ leave_unlock: return res; } -/** - * Hangs up the connection to the mobile_image_mounter service. - * This functions has to be called before freeing up a mobile_image_mounter - * instance. If not, errors appear in the device's syslog. - * - * @param client The client to hang up - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if client is invalid, - * or another error code otherwise. - */ +mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const unsigned char *signature, unsigned int signature_size, const char *image_type, plist_t *result) +{ + return mobile_image_mounter_mount_image_with_options(client, image_path, signature, signature_size, image_type, NULL, result); +} + +mobile_image_mounter_error_t mobile_image_mounter_unmount_image(mobile_image_mounter_client_t client, const char *mount_path) +{ + if (!client || !mount_path) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("UnmountImage")); + plist_dict_set_item(dict, "MountPath", plist_new_string(mount_path)); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } else { + plist_t p_error = plist_dict_get_item(result, "Error"); + if (p_error) { + plist_t p_detailed = plist_dict_get_item(result, "DetailedError"); + const char* detailederr = (p_detailed) ? plist_get_string_ptr(p_detailed, NULL) : ""; + const char* errstr = plist_get_string_ptr(p_error, NULL); + if (errstr && !strcmp(errstr, "UnknownCommand")) { + res = MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED; + } else if (errstr && !strcmp(errstr, "DeviceLocked")) { + res = MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED; + } else if (strstr(detailederr, "no matching entry")) { + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } else { + res = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; + } + } + } + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + mobile_image_mounter_error_t mobile_image_mounter_hangup(mobile_image_mounter_client_t client) { if (!client) { @@ -248,7 +354,7 @@ mobile_image_mounter_error_t mobile_image_mounter_hangup(mobile_image_mounter_cl mobile_image_mounter_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Command", plist_new_string("Hangup")); + plist_dict_set_item(dict, "Command", plist_new_string("Hangup")); mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); @@ -272,3 +378,215 @@ leave_unlock: mobile_image_mounter_unlock(client); return res; } + +mobile_image_mounter_error_t mobile_image_mounter_query_developer_mode_status(mobile_image_mounter_client_t client, plist_t *result) +{ + if (!client || !result) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryDeveloperModeStatus")); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_query_nonce(mobile_image_mounter_client_t client, const char* image_type, unsigned char** nonce, unsigned int* nonce_size) +{ + if (!client || !nonce || !nonce_size) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryNonce")); + if (image_type) { + plist_dict_set_item(dict, "PersonalizedImageType", plist_new_string(image_type)); + } + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } else { + plist_t p_nonce = plist_dict_get_item(result, "PersonalizationNonce"); + if (!p_nonce) { + res = MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED; + } else { + uint64_t nonce_size_ = 0; + plist_get_data_val(p_nonce, (char**)nonce, &nonce_size_); + if (*nonce) { + *nonce_size = (unsigned int)nonce_size_; + } else { + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } + } + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_query_personalization_identifiers(mobile_image_mounter_client_t client, const char* image_type, plist_t *result) +{ + if (!client || !result) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryPersonalizationIdentifiers")); + if (image_type) { + plist_dict_set_item(dict, "PersonalizedImageType", plist_new_string(image_type)); + } + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t _result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &_result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + *result = plist_copy(plist_dict_get_item(_result, "PersonalizationIdentifiers")); + if (!*result) { + debug_info("%s: Response did not contain PersonalizationIdentifiers!", __func__); + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_query_personalization_manifest(mobile_image_mounter_client_t client, const char* image_type, const unsigned char* signature, unsigned int signature_size, unsigned char** manifest, unsigned int* manifest_size) +{ + if (!client || !image_type || !signature || !signature_size || !manifest || !manifest_size) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryPersonalizationManifest")); + plist_dict_set_item(dict, "PersonalizedImageType", plist_new_string(image_type)); + plist_dict_set_item(dict, "ImageType", plist_new_string(image_type)); + plist_dict_set_item(dict, "ImageSignature", plist_new_data((char*)signature, signature_size)); + + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } else { + plist_t p_manifest = plist_dict_get_item(result, "ImageSignature"); + if (!p_manifest) { + res = MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED; + } else { + uint64_t manifest_size_ = 0; + plist_get_data_val(p_manifest, (char**)manifest, &manifest_size_); + if (*manifest) { + *manifest_size = (unsigned int)manifest_size_; + } else { + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } + } + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_roll_personalization_nonce(mobile_image_mounter_client_t client) +{ + if (!client) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("RollPersonalizationNonce")); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_roll_cryptex_nonce(mobile_image_mounter_client_t client) +{ + if (!client) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("RollCryptexNonce")); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} diff --git a/src/mobile_image_mounter.h b/src/mobile_image_mounter.h index 2615dbc..9a8fcdd 100644 --- a/src/mobile_image_mounter.h +++ b/src/mobile_image_mounter.h @@ -8,27 +8,28 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IMOBILE_IMAGE_MOUNTER_H -#define IMOBILE_IMAGE_MOUNTER_H -#include <glib.h> +#ifndef __MOBILE_IMAGE_MOUNTER_H +#define __MOBILE_IMAGE_MOUNTER_H +#include "idevice.h" #include "libimobiledevice/mobile_image_mounter.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct mobile_image_mounter_client_private { property_list_service_client_t parent; - GMutex *mutex; + mutex_t mutex; }; #endif diff --git a/src/mobileactivation.c b/src/mobileactivation.c new file mode 100644 index 0000000..fce5f16 --- /dev/null +++ b/src/mobileactivation.c @@ -0,0 +1,314 @@ +/* + * mobileactivation.c + * com.apple.mobileactivationd service implementation. + * + * Copyright (c) 2016-2017 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include "mobileactivation.h" +#include "property_list_service.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a mobileactivation_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching mobileactivation_error_t error code, + * MOBILEACTIVATION_E_UNKNOWN_ERROR otherwise. + */ +static mobileactivation_error_t mobileactivation_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return MOBILEACTIVATION_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return MOBILEACTIVATION_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return MOBILEACTIVATION_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return MOBILEACTIVATION_E_MUX_ERROR; + default: + break; + } + return MOBILEACTIVATION_E_UNKNOWN_ERROR; +} + +mobileactivation_error_t mobileactivation_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobileactivation_client_t *client) +{ + if (!device || !service || service->port == 0 || !client || *client) { + return MOBILEACTIVATION_E_INVALID_ARG; + } + + property_list_service_client_t plistclient = NULL; + if (property_list_service_client_new(device, service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return MOBILEACTIVATION_E_MUX_ERROR; + } + + /* create client object */ + mobileactivation_client_t client_loc = (mobileactivation_client_t) malloc(sizeof(struct mobileactivation_client_private)); + client_loc->parent = plistclient; + + /* all done, return success */ + *client = client_loc; + return MOBILEACTIVATION_E_SUCCESS; +} + +mobileactivation_error_t mobileactivation_client_start_service(idevice_t device, mobileactivation_client_t * client, const char* label) +{ + mobileactivation_error_t err = MOBILEACTIVATION_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILEACTIVATION_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobileactivation_client_new), &err); + return err; +} + +mobileactivation_error_t mobileactivation_client_free(mobileactivation_client_t client) +{ + if (!client) + return MOBILEACTIVATION_E_INVALID_ARG; + + if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return MOBILEACTIVATION_E_UNKNOWN_ERROR; + } + free(client); + return MOBILEACTIVATION_E_SUCCESS; +} + +static plist_t plist_data_from_plist(plist_t plist) +{ + if (plist && plist_get_node_type(plist) == PLIST_DATA) { + return plist_copy(plist); + } + plist_t result = NULL; + char *xml = NULL; + uint32_t xml_len = 0; + plist_to_xml(plist, &xml, &xml_len); + result = plist_new_data(xml, xml_len); + free(xml); + return result; +} + +static mobileactivation_error_t mobileactivation_check_result(plist_t dict, const char *command) +{ + if (!dict || plist_get_node_type(dict) != PLIST_DICT) { + return MOBILEACTIVATION_E_PLIST_ERROR; + } + + plist_t err_node = plist_dict_get_item(dict, "Error"); + if (!err_node) { + return MOBILEACTIVATION_E_SUCCESS; + } + + char *errmsg = NULL; + plist_get_string_val(err_node, &errmsg); + debug_info("ERROR: %s: %s", command, errmsg); + free(errmsg); + return MOBILEACTIVATION_E_REQUEST_FAILED; +} + +static mobileactivation_error_t mobileactivation_send_command_plist(mobileactivation_client_t client, plist_t command, plist_t *result) +{ + if (!client || !command) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t cmd = plist_dict_get_item(command, "Command"); + char* command_str = NULL; + if (cmd) { + plist_get_string_val(cmd, &command_str); + } + if (!command_str) + return MOBILEACTIVATION_E_INVALID_ARG; + + mobileactivation_error_t ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + *result = NULL; + + ret = mobileactivation_error(property_list_service_send_binary_plist(client->parent, command)); + + plist_t dict = NULL; + ret = mobileactivation_error(property_list_service_receive_plist(client->parent, &dict)); + if (!dict) { + debug_info("ERROR: Did not get reply for %s command", command_str); + free(command_str); + return MOBILEACTIVATION_E_PLIST_ERROR; + } + + *result = dict; + ret = mobileactivation_check_result(dict, command_str); + free(command_str); + return ret; +} + +static mobileactivation_error_t mobileactivation_send_command(mobileactivation_client_t client, const char* command, plist_t value, plist_t *result) +{ + if (!client || !command || !result) + return MOBILEACTIVATION_E_INVALID_ARG; + + mobileactivation_error_t ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + *result = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string(command)); + if (value) { + plist_dict_set_item(dict, "Value", plist_copy(value)); + } + + ret = mobileactivation_send_command_plist(client, dict, result); + plist_free(dict); + return ret; +} + +mobileactivation_error_t mobileactivation_get_activation_state(mobileactivation_client_t client, plist_t *state) +{ + if (!client || !state) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "GetActivationStateRequest", NULL, &result); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: GetActivationStateRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *state = plist_copy(node); + } + } + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_create_activation_session_info(mobileactivation_client_t client, plist_t *blob) +{ + if (!client || !blob) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "CreateTunnel1SessionInfoRequest", NULL, &result); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: CreateTunnel1SessionInfoRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *blob = plist_copy(node); + } + } + + return ret; +} + +mobileactivation_error_t mobileactivation_create_activation_info(mobileactivation_client_t client, plist_t *info) +{ + if (!client || !info) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "CreateActivationInfoRequest", NULL, &result); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: CreateActivationInfoRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *info = plist_copy(node); + } + } + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_create_activation_info_with_session(mobileactivation_client_t client, plist_t handshake_response, plist_t *info) +{ + if (!client || !info) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + plist_t data = plist_data_from_plist(handshake_response); + mobileactivation_error_t ret = mobileactivation_send_command(client, "CreateTunnel1ActivationInfoRequest", data, &result); + plist_free(data); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: CreateTunnel1ActivationInfoRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *info = plist_copy(node); + } + } + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_activate(mobileactivation_client_t client, plist_t activation_record) +{ + if (!client || !activation_record) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "HandleActivationInfoRequest", activation_record, &result); + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_activate_with_session(mobileactivation_client_t client, plist_t activation_record, plist_t headers) +{ + if (!client || !activation_record) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("HandleActivationInfoWithSessionRequest")); + plist_dict_set_item(dict, "Value", plist_data_from_plist(activation_record)); + if (headers) { + plist_dict_set_item(dict, "ActivationResponseHeaders", plist_copy(headers)); + } + + mobileactivation_error_t ret = mobileactivation_send_command_plist(client, dict, &result); + plist_free(dict); + plist_free(result); + result = NULL; + + return ret; +} + + +mobileactivation_error_t mobileactivation_deactivate(mobileactivation_client_t client) +{ + if (!client) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "DeactivateRequest", NULL, &result); + plist_free(result); + result = NULL; + + return ret; +} diff --git a/src/mobileactivation.h b/src/mobileactivation.h new file mode 100644 index 0000000..a8dff5d --- /dev/null +++ b/src/mobileactivation.h @@ -0,0 +1,33 @@ +/* + * mobileactivation.h + * com.apple.mobileactivationd service header file. + * + * Copyright (c) 2016 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __MOBILEACTIVATION_H +#define __MOBILEACTIVATION_H + +#include "idevice.h" +#include "libimobiledevice/mobileactivation.h" +#include "property_list_service.h" + +struct mobileactivation_client_private { + property_list_service_client_t parent; +}; + +#endif diff --git a/src/mobilebackup.c b/src/mobilebackup.c index fcff60d..36986a4 100644 --- a/src/mobilebackup.c +++ b/src/mobilebackup.c @@ -1,36 +1,41 @@ /* - * mobilebackup.c + * mobilebackup.c * Contains functions for the built-in MobileBackup client. - * + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <plist/plist.h> #include <string.h> #include <stdlib.h> +#include <stdio.h> #include "mobilebackup.h" #include "device_link_service.h" -#include "debug.h" +#include "common/debug.h" #define MBACKUP_VERSION_INT1 100 #define MBACKUP_VERSION_INT2 0 -#define IS_FLAG_SET(x, y) ((x & y) == y) +#define IS_FLAG_SET(x, y) (((x) & (y)) == (y)) /** * Convert an device_link_service_error_t value to an mobilebackup_error_t value. @@ -52,6 +57,10 @@ static mobilebackup_error_t mobilebackup_error(device_link_service_error_t err) return MOBILEBACKUP_E_PLIST_ERROR; case DEVICE_LINK_SERVICE_E_MUX_ERROR: return MOBILEBACKUP_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return MOBILEBACKUP_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return MOBILEBACKUP_E_RECEIVE_TIMEOUT; case DEVICE_LINK_SERVICE_E_BAD_VERSION: return MOBILEBACKUP_E_BAD_VERSION; default: @@ -60,26 +69,13 @@ static mobilebackup_error_t mobilebackup_error(device_link_service_error_t err) return MOBILEBACKUP_E_UNKNOWN_ERROR; } -/** - * Connects to the mobilebackup service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * mobilebackup_client_t upon successful return. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID ARG if one - * or more parameters are invalid, or DEVICE_LINK_SERVICE_E_BAD_VERSION if - * the mobilebackup version on the device is newer. - */ -mobilebackup_error_t mobilebackup_client_new(idevice_t device, uint16_t port, - mobilebackup_client_t * client) +mobilebackup_error_t mobilebackup_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilebackup_client_t * client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return MOBILEBACKUP_E_INVALID_ARG; device_link_service_client_t dlclient = NULL; - mobilebackup_error_t ret = mobilebackup_error(device_link_service_client_new(device, port, &dlclient)); + mobilebackup_error_t ret = mobilebackup_error(device_link_service_client_new(device, service, &dlclient)); if (ret != MOBILEBACKUP_E_SUCCESS) { return ret; } @@ -100,36 +96,26 @@ mobilebackup_error_t mobilebackup_client_new(idevice_t device, uint16_t port, return ret; } -/** - * Disconnects a mobilebackup client from the device and frees up the - * mobilebackup client data. - * - * @param client The mobilebackup client to disconnect and free. - * - * @return MOBILEBACKUP_E_SUCCESS on success, or MOBILEBACKUP_E_INVALID_ARG - * if client is NULL. - */ +mobilebackup_error_t mobilebackup_client_start_service(idevice_t device, mobilebackup_client_t * client, const char* label) +{ + mobilebackup_error_t err = MOBILEBACKUP_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILEBACKUP_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobilebackup_client_new), &err); + return err; +} + mobilebackup_error_t mobilebackup_client_free(mobilebackup_client_t client) { if (!client) return MOBILEBACKUP_E_INVALID_ARG; mobilebackup_error_t err = MOBILEBACKUP_E_SUCCESS; if (client->parent) { - device_link_service_disconnect(client->parent); + device_link_service_disconnect(client->parent, NULL); err = mobilebackup_error(device_link_service_client_free(client->parent)); } free(client); return err; } -/** - * Polls the device for mobilebackup data. - * - * @param client The mobilebackup client - * @param plist A pointer to the location where the plist should be stored - * - * @return an error code - */ mobilebackup_error_t mobilebackup_receive(mobilebackup_client_t client, plist_t * plist) { if (!client) @@ -138,17 +124,6 @@ mobilebackup_error_t mobilebackup_receive(mobilebackup_client_t client, plist_t return ret; } -/** - * Sends mobilebackup data to the device - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The mobilebackup client - * @param plist The location of the plist to send - * - * @return an error code - */ mobilebackup_error_t mobilebackup_send(mobilebackup_client_t client, plist_t plist) { if (!client || !plist) @@ -186,7 +161,7 @@ static mobilebackup_error_t mobilebackup_send_message(mobilebackup_client_t clie } else { dict = plist_new_dict(); } - plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string(message)); + plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string(message)); /* send it as DLMessageProcessMessage */ err = mobilebackup_error(device_link_service_send_process_message(client->parent, dict)); @@ -266,23 +241,6 @@ leave: return err; } -/** - * Request a backup from the connected device. - * - * @param client The connected MobileBackup client to use. - * @param backup_manifest The backup manifest, a plist_t of type PLIST_DICT - * containing the backup state of the last backup. For a first-time backup - * set this parameter to NULL. - * @param base_path The base path on the device to use for the backup - * operation, usually "/". - * @param proto_version A string denoting the version of the backup protocol - * to use. Latest known version is "1.6" - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * one of the parameters is invalid, MOBILEBACKUP_E_PLIST_ERROR if - * backup_manifest is not of type PLIST_DICT, MOBILEBACKUP_E_MUX_ERROR - * if a communication error occurs, MOBILEBACKUP_E_REPLY_NOT_OK - */ mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, plist_t backup_manifest, const char *base_path, const char *proto_version) { if (!client || !client->parent || !base_path || !proto_version) @@ -296,10 +254,10 @@ mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, p /* construct request plist */ plist_t dict = plist_new_dict(); if (backup_manifest) - plist_dict_insert_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); - plist_dict_insert_item(dict, "BackupComputerBasePathKey", plist_new_string(base_path)); - plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageBackupRequest")); - plist_dict_insert_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); + plist_dict_set_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); + plist_dict_set_item(dict, "BackupComputerBasePathKey", plist_new_string(base_path)); + plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageBackupRequest")); + plist_dict_set_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); /* send request */ err = mobilebackup_send_message(client, NULL, dict); @@ -322,7 +280,15 @@ mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, p char *str = NULL; plist_get_string_val(node, &str); if (str) { - if (strcmp(str, proto_version) != 0) { + int maj = 0; + int min = 0; + sscanf(str, "%u.%u", &maj, &min); + uint32_t this_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + maj = 0; + min = 0; + sscanf(proto_version, "%u.%u", &maj, &min); + uint32_t proto_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + if (this_ver > proto_ver) { err = MOBILEBACKUP_E_BAD_VERSION; } free(str); @@ -343,41 +309,11 @@ leave: return err; } -/** - * Sends a confirmation to the device that a backup file has been received. - * - * @param client The connected MobileBackup client to use. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, or MOBILEBACKUP_E_MUX_ERROR if a communication error - * occurs. - */ mobilebackup_error_t mobilebackup_send_backup_file_received(mobilebackup_client_t client) { return mobilebackup_send_message(client, "kBackupMessageBackupFileReceived", NULL); } -/** - * Request that a backup should be restored to the connected device. - * - * @param client The connected MobileBackup client to use. - * @param backup_manifest The backup manifest, a plist_t of type PLIST_DICT - * containing the backup state to be restored. - * @param flags Flags to send with the request. Currently this is a combination - * of the following mobilebackup_flags_t: - * MB_RESTORE_NOTIFY_SPRINGBOARD - let SpringBoard show a 'Restore' screen - * MB_RESTORE_PRESERVE_SETTINGS - do not overwrite any settings - * MB_RESTORE_PRESERVE_CAMERA_ROLL - preserve the photos of the camera roll - * @param proto_version A string denoting the version of the backup protocol - * to use. Latest known version is "1.6". Ideally this value should be - * extracted from the given manifest plist. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * one of the parameters is invalid, MOBILEBACKUP_E_PLIST_ERROR if - * backup_manifest is not of type PLIST_DICT, MOBILEBACKUP_E_MUX_ERROR - * if a communication error occurs, or MOBILEBACKUP_E_REPLY_NOT_OK - * if the device did not accept the request. - */ mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, plist_t backup_manifest, mobilebackup_flags_t flags, const char *proto_version) { if (!client || !client->parent || !backup_manifest || !proto_version) @@ -390,13 +326,13 @@ mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, /* construct request plist */ plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); - plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string("kBackupMessageRestoreRequest")); - plist_dict_insert_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); + plist_dict_set_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); + plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string("kBackupMessageRestoreRequest")); + plist_dict_set_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); /* add flags */ - plist_dict_insert_item(dict, "BackupNotifySpringBoard", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_NOTIFY_SPRINGBOARD))); - plist_dict_insert_item(dict, "BackupPreserveSettings", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_SETTINGS))); - plist_dict_insert_item(dict, "BackupPreserveCameraRoll", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_CAMERA_ROLL))); + plist_dict_set_item(dict, "BackupNotifySpringBoard", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_NOTIFY_SPRINGBOARD))); + plist_dict_set_item(dict, "BackupPreserveSettings", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_SETTINGS))); + plist_dict_set_item(dict, "BackupPreserveCameraRoll", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_CAMERA_ROLL))); /* send request */ err = mobilebackup_send_message(client, NULL, dict); @@ -419,7 +355,15 @@ mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, char *str = NULL; plist_get_string_val(node, &str); if (str) { - if (strcmp(str, proto_version) != 0) { + int maj = 0; + int min = 0; + sscanf(str, "%u.%u", &maj, &min); + uint32_t this_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + maj = 0; + min = 0; + sscanf(proto_version, "%u.%u", &maj, &min); + uint32_t proto_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + if (this_ver > proto_ver) { err = MOBILEBACKUP_E_BAD_VERSION; } free(str); @@ -432,63 +376,16 @@ leave: return err; } -/** - * Receive a confirmation from the device that it successfully received - * a restore file. - * - * @param client The connected MobileBackup client to use. - * @param result Pointer to a plist_t that will be set to the received plist - * for further processing. The caller has to free it using plist_free(). - * Note that it will be set to NULL if the operation itself fails due to - * a communication or plist error. - * If this parameter is NULL, it will be ignored. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, MOBILEBACKUP_E_REPLY_NOT_OK if the expected - * 'BackupMessageRestoreFileReceived' message could not be received, - * MOBILEBACKUP_E_PLIST_ERROR if the received message is not a valid backup - * message plist, or MOBILEBACKUP_E_MUX_ERROR if a communication error - * occurs. - */ mobilebackup_error_t mobilebackup_receive_restore_file_received(mobilebackup_client_t client, plist_t *result) { return mobilebackup_receive_message(client, "BackupMessageRestoreFileReceived", result); } -/** - * Receive a confirmation from the device that it successfully received - * application data file. - * - * @param client The connected MobileBackup client to use. - * @param result Pointer to a plist_t that will be set to the received plist - * for further processing. The caller has to free it using plist_free(). - * Note that it will be set to NULL if the operation itself fails due to - * a communication or plist error. - * If this parameter is NULL, it will be ignored. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, MOBILEBACKUP_E_REPLY_NOT_OK if the expected - * 'BackupMessageRestoreApplicationReceived' message could not be received, - * MOBILEBACKUP_E_PLIST_ERROR if the received message is not a valid backup - * message plist, or MOBILEBACKUP_E_MUX_ERROR if a communication error - * occurs. - */ mobilebackup_error_t mobilebackup_receive_restore_application_received(mobilebackup_client_t client, plist_t *result) { return mobilebackup_receive_message(client, "BackupMessageRestoreApplicationReceived", result); } -/** - * Tells the device that the restore process is complete and waits for the - * device to close the connection. After that, the device should reboot. - * - * @param client The connected MobileBackup client to use. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, MOBILEBACKUP_E_PLIST_ERROR if the received disconnect - * message plist is invalid, or MOBILEBACKUP_E_MUX_ERROR if a communication - * error occurs. - */ mobilebackup_error_t mobilebackup_send_restore_complete(mobilebackup_client_t client) { mobilebackup_error_t err = mobilebackup_send_message(client, "BackupMessageRestoreComplete", NULL); @@ -534,16 +431,6 @@ mobilebackup_error_t mobilebackup_send_restore_complete(mobilebackup_client_t cl return err; } -/** - * Sends a backup error message to the device. - * - * @param client The connected MobileBackup client to use. - * @param reason A string describing the reason for the error message. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * one of the parameters is invalid, or MOBILEBACKUP_E_MUX_ERROR if a - * communication error occurs. - */ mobilebackup_error_t mobilebackup_send_error(mobilebackup_client_t client, const char *reason) { if (!client || !client->parent || !reason) @@ -553,7 +440,7 @@ mobilebackup_error_t mobilebackup_send_error(mobilebackup_client_t client, const /* construct error plist */ plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "BackupErrorReasonKey", plist_new_string(reason)); + plist_dict_set_item(dict, "BackupErrorReasonKey", plist_new_string(reason)); err = mobilebackup_send_message(client, "BackupMessageError", dict); plist_free(dict); diff --git a/src/mobilebackup.h b/src/mobilebackup.h index 2c5be62..04ec479 100644 --- a/src/mobilebackup.h +++ b/src/mobilebackup.h @@ -1,26 +1,29 @@ - /* +/* * mobilebackup.h * Definitions for the mobilebackup service - * + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef MOBILEBACKUP_H -#define MOBILEBACKUP_H +#ifndef __MOBILEBACKUP_H +#define __MOBILEBACKUP_H + +#include "idevice.h" #include "libimobiledevice/mobilebackup.h" #include "device_link_service.h" diff --git a/src/mobilebackup2.c b/src/mobilebackup2.c new file mode 100644 index 0000000..a8d673f --- /dev/null +++ b/src/mobilebackup2.c @@ -0,0 +1,386 @@ +/* + * mobilebackup2.c + * Contains functions for the built-in MobileBackup2 client (iOS4+ only) + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <plist/plist.h> +#include <string.h> +#include <stdlib.h> + +#include "mobilebackup2.h" +#include "device_link_service.h" +#include "common/debug.h" + +#define MBACKUP2_VERSION_INT1 400 +#define MBACKUP2_VERSION_INT2 0 + +#define IS_FLAG_SET(x, y) (((x) & (y)) == (y)) + +/** + * Convert an device_link_service_error_t value to an mobilebackup2_error_t value. + * Used internally to get correct error codes from the underlying + * device_link_service. + * + * @param err An device_link_service_error_t error code + * + * @return A matching mobilebackup2_error_t error code, + * MOBILEBACKUP2_E_UNKNOWN_ERROR otherwise. + */ +static mobilebackup2_error_t mobilebackup2_error(device_link_service_error_t err) +{ + switch (err) { + case DEVICE_LINK_SERVICE_E_SUCCESS: + return MOBILEBACKUP2_E_SUCCESS; + case DEVICE_LINK_SERVICE_E_INVALID_ARG: + return MOBILEBACKUP2_E_INVALID_ARG; + case DEVICE_LINK_SERVICE_E_PLIST_ERROR: + return MOBILEBACKUP2_E_PLIST_ERROR; + case DEVICE_LINK_SERVICE_E_MUX_ERROR: + return MOBILEBACKUP2_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return MOBILEBACKUP2_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return MOBILEBACKUP2_E_RECEIVE_TIMEOUT; + case DEVICE_LINK_SERVICE_E_BAD_VERSION: + return MOBILEBACKUP2_E_BAD_VERSION; + default: + break; + } + return MOBILEBACKUP2_E_UNKNOWN_ERROR; +} + +mobilebackup2_error_t mobilebackup2_client_new(idevice_t device, lockdownd_service_descriptor_t service, + mobilebackup2_client_t * client) +{ + if (!device || !service || service->port == 0 || !client || *client) + return MOBILEBACKUP2_E_INVALID_ARG; + + device_link_service_client_t dlclient = NULL; + mobilebackup2_error_t ret = mobilebackup2_error(device_link_service_client_new(device, service, &dlclient)); + if (ret != MOBILEBACKUP2_E_SUCCESS) { + return ret; + } + + mobilebackup2_client_t client_loc = (mobilebackup2_client_t) malloc(sizeof(struct mobilebackup2_client_private)); + client_loc->parent = dlclient; + + /* perform handshake */ + ret = mobilebackup2_error(device_link_service_version_exchange(dlclient, MBACKUP2_VERSION_INT1, MBACKUP2_VERSION_INT2)); + if (ret != MOBILEBACKUP2_E_SUCCESS) { + debug_info("version exchange failed, error %d", ret); + mobilebackup2_client_free(client_loc); + return ret; + } + + *client = client_loc; + + return ret; +} + +mobilebackup2_error_t mobilebackup2_client_start_service(idevice_t device, mobilebackup2_client_t * client, const char* label) +{ + mobilebackup2_error_t err = MOBILEBACKUP2_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILEBACKUP2_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobilebackup2_client_new), &err); + return err; +} + +mobilebackup2_error_t mobilebackup2_client_free(mobilebackup2_client_t client) +{ + if (!client) + return MOBILEBACKUP2_E_INVALID_ARG; + mobilebackup2_error_t err = MOBILEBACKUP2_E_SUCCESS; + if (client->parent) { + device_link_service_disconnect(client->parent, NULL); + err = mobilebackup2_error(device_link_service_client_free(client->parent)); + } + free(client); + return err; +} + +mobilebackup2_error_t mobilebackup2_send_message(mobilebackup2_client_t client, const char *message, plist_t options) +{ + if (!client || !client->parent || (!message && !options)) + return MOBILEBACKUP2_E_INVALID_ARG; + + if (options && (plist_get_node_type(options) != PLIST_DICT)) { + return MOBILEBACKUP2_E_INVALID_ARG; + } + + mobilebackup2_error_t err; + + if (message) { + plist_t dict = NULL; + if (options) { + dict = plist_copy(options); + } else { + dict = plist_new_dict(); + } + plist_dict_set_item(dict, "MessageName", plist_new_string(message)); + + /* send it as DLMessageProcessMessage */ + err = mobilebackup2_error(device_link_service_send_process_message(client->parent, dict)); + plist_free(dict); + } else { + err = mobilebackup2_error(device_link_service_send_process_message(client->parent, options)); + } + if (err != MOBILEBACKUP2_E_SUCCESS) { + debug_info("ERROR: Could not send message '%s' (%d)!", message, err); + } + return err; +} + +/** + * Receives a plist from the device and checks if the value for the + * MessageName key matches the value passed in the message parameter. + * + * @param client The connected MobileBackup client to use. + * @param message The expected message to check. + * @param result Pointer to a plist_t that will be set to the received plist + * for further processing. The caller has to free it using plist_free(). + * Note that it will be set to NULL if the operation itself fails due to + * a communication or plist error. + * If this parameter is NULL, it will be ignored. + * + * @return MOBILEBACKUP2_E_SUCCESS on success, MOBILEBACKUP2_E_INVALID_ARG if + * client or message is invalid, MOBILEBACKUP2_E_REPLY_NOT_OK if the + * expected message could not be received, MOBILEBACKUP2_E_PLIST_ERROR if + * the received message is not a valid backup message plist (i.e. the + * MessageName key is not present), or MOBILEBACKUP2_E_MUX_ERROR + * if a communication error occurs. + */ +static mobilebackup2_error_t internal_mobilebackup2_receive_message(mobilebackup2_client_t client, const char *message, plist_t *result) +{ + if (!client || !client->parent || !message) + return MOBILEBACKUP2_E_INVALID_ARG; + + if (result) + *result = NULL; + mobilebackup2_error_t err; + + plist_t dict = NULL; + + /* receive DLMessageProcessMessage */ + err = mobilebackup2_error(device_link_service_receive_process_message(client->parent, &dict)); + if (err != MOBILEBACKUP2_E_SUCCESS) { + goto leave; + } + + plist_t node = plist_dict_get_item(dict, "MessageName"); + if (!node) { + debug_info("ERROR: MessageName key not found in plist!"); + err = MOBILEBACKUP2_E_PLIST_ERROR; + goto leave; + } + + char *str = NULL; + plist_get_string_val(node, &str); + if (str && (strcmp(str, message) == 0)) { + err = MOBILEBACKUP2_E_SUCCESS; + } else { + debug_info("ERROR: MessageName value does not match '%s'!", message); + err = MOBILEBACKUP2_E_REPLY_NOT_OK; + } + if (str) + free(str); + + if (result) { + *result = dict; + dict = NULL; + } +leave: + if (dict) { + plist_free(dict); + } + + return err; +} + +mobilebackup2_error_t mobilebackup2_receive_message(mobilebackup2_client_t client, plist_t *msg_plist, char **dlmessage) +{ + return mobilebackup2_error(device_link_service_receive_message(client->parent, msg_plist, dlmessage)); +} + +mobilebackup2_error_t mobilebackup2_send_raw(mobilebackup2_client_t client, const char *data, uint32_t length, uint32_t *bytes) +{ + if (!client || !client->parent || !data || (length == 0) || !bytes) + return MOBILEBACKUP2_E_INVALID_ARG; + + *bytes = 0; + + service_client_t raw = client->parent->parent->parent; + + int bytes_loc = 0; + uint32_t sent = 0; + do { + bytes_loc = 0; + service_send(raw, data+sent, length-sent, (uint32_t*)&bytes_loc); + if (bytes_loc <= 0) + break; + sent += bytes_loc; + } while (sent < length); + if (sent > 0) { + *bytes = sent; + return MOBILEBACKUP2_E_SUCCESS; + } + return MOBILEBACKUP2_E_MUX_ERROR; +} + +mobilebackup2_error_t mobilebackup2_receive_raw(mobilebackup2_client_t client, char *data, uint32_t length, uint32_t *bytes) +{ + if (!client || !client->parent || !data || (length == 0) || !bytes) + return MOBILEBACKUP2_E_INVALID_ARG; + + service_client_t raw = client->parent->parent->parent; + + *bytes = 0; + + int bytes_loc = 0; + uint32_t received = 0; + do { + bytes_loc = 0; + service_receive(raw, data+received, length-received, (uint32_t*)&bytes_loc); + if (bytes_loc <= 0) break; + received += bytes_loc; + } while (received < length); + if (received > 0) { + *bytes = received; + return MOBILEBACKUP2_E_SUCCESS; + } + if (received == 0) { + return MOBILEBACKUP2_E_SUCCESS; + } + return MOBILEBACKUP2_E_MUX_ERROR; +} + +mobilebackup2_error_t mobilebackup2_version_exchange(mobilebackup2_client_t client, double local_versions[], char count, double *remote_version) +{ + int i; + + if (!client || !client->parent) + return MOBILEBACKUP2_E_INVALID_ARG; + + plist_t dict = plist_new_dict(); + plist_t array = plist_new_array(); + for (i = 0; i < count; i++) { + plist_array_append_item(array, plist_new_real(local_versions[i])); + } + plist_dict_set_item(dict, "SupportedProtocolVersions", array); + + mobilebackup2_error_t err = mobilebackup2_send_message(client, "Hello", dict); + plist_free(dict); + + if (err != MOBILEBACKUP2_E_SUCCESS) + goto leave; + + dict = NULL; + err = internal_mobilebackup2_receive_message(client, "Response", &dict); + if (err != MOBILEBACKUP2_E_SUCCESS) + goto leave; + + /* check if we received an error */ + plist_t node = plist_dict_get_item(dict, "ErrorCode"); + if (!node || (plist_get_node_type(node) != PLIST_UINT)) { + err = MOBILEBACKUP2_E_PLIST_ERROR; + goto leave; + } + + uint64_t val = 0; + plist_get_uint_val(node, &val); + if (val != 0) { + if (val == 1) { + err = MOBILEBACKUP2_E_NO_COMMON_VERSION; + } else { + err = MOBILEBACKUP2_E_REPLY_NOT_OK; + } + goto leave; + } + + /* retrieve the protocol version of the device */ + node = plist_dict_get_item(dict, "ProtocolVersion"); + if (!node || (plist_get_node_type(node) != PLIST_REAL)) { + err = MOBILEBACKUP2_E_PLIST_ERROR; + goto leave; + } + + *remote_version = 0.0; + plist_get_real_val(node, remote_version); +leave: + if (dict) + plist_free(dict); + return err; +} + +mobilebackup2_error_t mobilebackup2_send_request(mobilebackup2_client_t client, const char *request, const char *target_identifier, const char *source_identifier, plist_t options) +{ + if (!client || !client->parent || !request || !target_identifier) + return MOBILEBACKUP2_E_INVALID_ARG; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "TargetIdentifier", plist_new_string(target_identifier)); + if (source_identifier) { + plist_dict_set_item(dict, "SourceIdentifier", plist_new_string(source_identifier)); + } + if (options) { + plist_dict_set_item(dict, "Options", plist_copy(options)); + } + if (!strcmp(request, "Unback") && options) { + plist_t node = plist_dict_get_item(options, "Password"); + if (node) { + plist_dict_set_item(dict, "Password", plist_copy(node)); + } + } + if (!strcmp(request, "EnableCloudBackup") && options) { + plist_t node = plist_dict_get_item(options, "CloudBackupState"); + if (node) { + plist_dict_set_item(dict, "CloudBackupState", plist_copy(node)); + } + } + mobilebackup2_error_t err = mobilebackup2_send_message(client, request, dict); + plist_free(dict); + + return err; +} + +mobilebackup2_error_t mobilebackup2_send_status_response(mobilebackup2_client_t client, int status_code, const char *status1, plist_t status2) +{ + if (!client || !client->parent) + return MOBILEBACKUP2_E_INVALID_ARG; + + plist_t array = plist_new_array(); + plist_array_append_item(array, plist_new_string("DLMessageStatusResponse")); + plist_array_append_item(array, plist_new_uint(status_code)); + if (status1) { + plist_array_append_item(array, plist_new_string(status1)); + } else { + plist_array_append_item(array, plist_new_string("___EmptyParameterString___")); + } + if (status2) { + plist_array_append_item(array, plist_copy(status2)); + } else { + plist_array_append_item(array, plist_new_string("___EmptyParameterString___")); + } + + mobilebackup2_error_t err = mobilebackup2_error(device_link_service_send(client->parent, array)); + plist_free(array); + + return err; +} diff --git a/src/mobilebackup2.h b/src/mobilebackup2.h new file mode 100644 index 0000000..e232b97 --- /dev/null +++ b/src/mobilebackup2.h @@ -0,0 +1,33 @@ +/* + * mobilebackup2.h + * Definitions for the mobilebackup2 service (iOS4+) + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __MOBILEBACKUP2_H +#define __MOBILEBACKUP2_H + +#include "idevice.h" +#include "libimobiledevice/mobilebackup2.h" +#include "device_link_service.h" + +struct mobilebackup2_client_private { + device_link_service_client_t parent; +}; + +#endif diff --git a/src/mobilesync.c b/src/mobilesync.c index ee9af5f..9b81a49 100644 --- a/src/mobilesync.c +++ b/src/mobilesync.c @@ -1,7 +1,7 @@ /* - * mobilesync.c + * mobilesync.c * Contains functions for the built-in MobileSync client. - * + * * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * @@ -9,31 +9,32 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #define _GNU_SOURCE 1 #define __USE_GNU 1 - #include <plist/plist.h> #include <string.h> #include <stdlib.h> #include <stdio.h> -#include <glib.h> #include "mobilesync.h" #include "device_link_service.h" -#include "debug.h" +#include "common/debug.h" -#define MSYNC_VERSION_INT1 100 +#define MSYNC_VERSION_INT1 400 #define MSYNC_VERSION_INT2 100 #define EMPTY_PARAMETER_STRING "___EmptyParameterString___" @@ -58,6 +59,10 @@ static mobilesync_error_t mobilesync_error(device_link_service_error_t err) return MOBILESYNC_E_PLIST_ERROR; case DEVICE_LINK_SERVICE_E_MUX_ERROR: return MOBILESYNC_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return MOBILESYNC_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return MOBILESYNC_E_RECEIVE_TIMEOUT; case DEVICE_LINK_SERVICE_E_BAD_VERSION: return MOBILESYNC_E_BAD_VERSION; default: @@ -66,27 +71,14 @@ static mobilesync_error_t mobilesync_error(device_link_service_error_t err) return MOBILESYNC_E_UNKNOWN_ERROR; } -/** - * Connects to the mobilesync service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service()). - * @param client Pointer that will be set to a newly allocated - * #mobilesync_client_t upon successful return. - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one or more parameters are invalid - * @retval DEVICE_LINK_SERVICE_E_BAD_VERSION if the mobilesync version on - * the device is newer. - */ -mobilesync_error_t mobilesync_client_new(idevice_t device, uint16_t port, +mobilesync_error_t mobilesync_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilesync_client_t * client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return MOBILESYNC_E_INVALID_ARG; device_link_service_client_t dlclient = NULL; - mobilesync_error_t ret = mobilesync_error(device_link_service_client_new(device, port, &dlclient)); + mobilesync_error_t ret = mobilesync_error(device_link_service_client_new(device, service, &dlclient)); if (ret != MOBILESYNC_E_SUCCESS) { return ret; } @@ -109,33 +101,23 @@ mobilesync_error_t mobilesync_client_new(idevice_t device, uint16_t port, return ret; } -/** - * Disconnects a mobilesync client from the device and frees up the - * mobilesync client data. - * - * @param client The mobilesync client to disconnect and free. - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if \a client is NULL. - */ +mobilesync_error_t mobilesync_client_start_service(idevice_t device, mobilesync_client_t * client, const char* label) +{ + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILESYNC_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobilesync_client_new), &err); + return err; +} + mobilesync_error_t mobilesync_client_free(mobilesync_client_t client) { if (!client) return MOBILESYNC_E_INVALID_ARG; - device_link_service_disconnect(client->parent); + device_link_service_disconnect(client->parent, "All done, thanks for the memories"); mobilesync_error_t err = mobilesync_error(device_link_service_client_free(client->parent)); free(client); return err; } -/** - * Polls the device for mobilesync data. - * - * @param client The mobilesync client - * @param plist A pointer to the location where the plist should be stored - * - * @return an error code - */ mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist_t * plist) { if (!client) @@ -144,17 +126,6 @@ mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist_t * plis return ret; } -/** - * Sends mobilesync data to the device - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The mobilesync client - * @param plist The location of the plist to send - * - * @return an error code - */ mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist) { if (!client || !plist) @@ -162,26 +133,7 @@ mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist) return mobilesync_error(device_link_service_send(client->parent, plist)); } -/** - * Requests starting synchronization of a data class with the device - * - * @param client The mobilesync client - * @param data_class The data class identifier to sync - * @param anchors The anchors required to exchange with the device. The anchors - * allow the device to tell if the synchronization information on the computer - * and device are consistent to determine what sync type is required. - * @param computer_data_class_version The version of the data class storage on the computer - * @param sync_type A pointer to store the sync type reported by the device_anchor - * @param device_data_class_version The version of the data class storage on the device - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form - * @retval MOBILESYNC_E_SYNC_REFUSED if the device refused to sync - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * sync request - */ -mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, uint64_t computer_data_class_version, mobilesync_sync_type_t *sync_type, uint64_t *device_data_class_version) +mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, uint64_t computer_data_class_version, mobilesync_sync_type_t *sync_type, uint64_t *device_data_class_version, char** error_description) { if (!client || client->data_class || !data_class || !anchors || !anchors->computer_anchor) { @@ -194,6 +146,8 @@ mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data plist_t msg = NULL; plist_t response_type_node = NULL; + *error_description = NULL; + msg = plist_new_array(); plist_array_append_item(msg, plist_new_string("SDMessageSyncDataClassWithDevice")); plist_array_append_item(msg, plist_new_string(data_class)); @@ -233,23 +187,19 @@ mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data goto out; } + // did the device refuse to sync with the computer? if (!strcmp(response_type, "SDMessageRefuseToSyncDataClassWithComputer")) { - char *reason = NULL; err = MOBILESYNC_E_SYNC_REFUSED; - plist_get_string_val(plist_array_get_item(msg, 2), &reason); - debug_info("Device refused sync: %s", reason); - free(reason); - reason = NULL; + plist_get_string_val(plist_array_get_item(msg, 2), error_description); + debug_info("Device refused sync: %s", error_description); goto out; } + // did the device cancel the session? if (!strcmp(response_type, "SDMessageCancelSession")) { - char *reason = NULL; err = MOBILESYNC_E_CANCELLED; - plist_get_string_val(plist_array_get_item(msg, 2), &reason); - debug_info("Device cancelled: %s", reason); - free(reason); - reason = NULL; + plist_get_string_val(plist_array_get_item(msg, 2), error_description); + debug_info("Device cancelled: %s", error_description); goto out; } @@ -309,17 +259,6 @@ mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data return err; } -/** - * Finish a synchronization session of a data class on the device. - * A session must have previously been started using mobilesync_start(). - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid - * form - */ mobilesync_error_t mobilesync_finish(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -395,7 +334,7 @@ static mobilesync_error_t mobilesync_get_records(mobilesync_client_t client, con msg = plist_new_array(); plist_array_append_item(msg, plist_new_string(operation)); plist_array_append_item(msg, plist_new_string(client->data_class)); - + err = mobilesync_send(client, msg); if (msg) { @@ -405,49 +344,16 @@ static mobilesync_error_t mobilesync_get_records(mobilesync_client_t client, con return err; } -/** - * Requests to receive all records of the currently set data class from the device. - * The actually changes are retrieved using mobilesync_receive_changes() after this - * request has been successful. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_get_all_records_from_device(mobilesync_client_t client) { return mobilesync_get_records(client, "SDMessageGetAllRecordsFromDevice"); } -/** - * Requests to receive only changed records of the currently set data class from the device. - * The actually changes are retrieved using mobilesync_receive_changes() after this - * request has been successful. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_get_changes_from_device(mobilesync_client_t client) { return mobilesync_get_records(client, "SDMessageGetChangesFromDevice"); } -/** - * Receives changed entitites of the currently set data class from the device - * - * @param client The mobilesync client - * @param entities A pointer to store the changed entity records as a PLIST_DICT - * @param is_last_record A pointer to store a flag indicating if this submission is the last one - * @param actions A pointer to additional flags the device is sending or NULL to ignore - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * session - */ mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_t *entities, uint8_t *is_last_record, plist_t *actions) { if (!client || !client->data_class) { @@ -497,7 +403,7 @@ mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_ if (actions != NULL) { actions_node = plist_array_get_item(msg, 4); - if (plist_get_node_type(actions) == PLIST_DICT) + if (plist_get_node_type(actions_node) == PLIST_DICT) *actions = plist_copy(actions_node); else *actions = NULL; @@ -515,17 +421,6 @@ mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_ return err; } -/** - * Requests the device to delete all records of the current data class - * - * @note The operation must be called after starting synchronization. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form - */ mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -578,7 +473,7 @@ mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t cl goto out; } - if (strcmp(response_type, "SDMessageDeviceWillClearAllRecords")) { + if (strcmp(response_type, "SDMessageDeviceWillClearAllRecords") != 0) { err = MOBILESYNC_E_PLIST_ERROR; } @@ -595,14 +490,6 @@ mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t cl return err; } -/** - * Acknowledges to the device that the changes have been merged on the computer - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_acknowledge_changes_from_device(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -637,23 +524,6 @@ static plist_t create_process_changes_message(const char *data_class, plist_t en return msg; } -/** - * Verifies if the device is ready to receive changes from the computer. - * This call changes the synchronization direction so that mobilesync_send_changes() - * can be used to send changes to the device. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form - * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does - * not permit this call - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * session - * @retval MOBILESYNC_E_NOT_READY if the device is not ready to start - * receiving any changes - */ mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -721,20 +591,6 @@ mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_cli return err; } -/** - * Sends changed entities of the currently set data class to the device - * - * @param client The mobilesync client - * @param entities The changed entity records as a PLIST_DICT - * @param is_last_record A flag indicating if this submission is the last one - * @param actions Additional actions for the device created with mobilesync_actions_new() - * or NULL if no actions should be passed - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid, - * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does - * not permit this call - */ mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t entities, uint8_t is_last_record, plist_t actions) { if (!client || !client->data_class || !entities) { @@ -763,21 +619,6 @@ mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t e return err; } -/** - * Receives any remapped identifiers reported after the device merged submitted changes. - * - * @param client The mobilesync client - * @param mapping A pointer to an array plist containing a dict of identifier remappings - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid - * form - * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does - * not permit this call - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * session - */ mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plist_t *mapping) { if (!client || !client->data_class) { @@ -847,15 +688,6 @@ mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plis return err; } -/** - * Cancels a running synchronization session with a device at any time. - * - * @param client The mobilesync client - * @param reason The reason to supply to the device for cancelling - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* reason) { if (!client || !client->data_class || !reason) { @@ -882,18 +714,9 @@ mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* rea return err; } -/** - * Allocates memory for a new anchors struct initialized with the passed anchors. - * - * @param device_anchor An anchor the device reported the last time or NULL - * if none is known yet which for instance is true on first synchronization. - * @param computer_anchor An arbitrary string to use as anchor for the computer. - * - * @return A new #mobilesync_anchors_t struct. Must be freed using mobilesync_anchors_free(). - */ mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const char *computer_anchor) { - mobilesync_anchors_t anchors = (mobilesync_anchors_t) malloc(sizeof(mobilesync_anchors)); + mobilesync_anchors_t anchors = (mobilesync_anchors_t) malloc(sizeof(mobilesync_anchors)); if (device_anchor != NULL) { anchors->device_anchor = strdup(device_anchor); } else { @@ -908,11 +731,6 @@ mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const cha return anchors; } -/** - * Free memory used by anchors. - * - * @param anchors The anchors to free. - */ void mobilesync_anchors_free(mobilesync_anchors_t anchors) { if (anchors->device_anchor != NULL) { @@ -927,28 +745,11 @@ void mobilesync_anchors_free(mobilesync_anchors_t anchors) anchors = NULL; } -/** - * Create a new actions plist to use in mobilesync_send_changes(). - * - * @return A new plist_t of type PLIST_DICT. - */ -plist_t mobilesync_actions_new() +plist_t mobilesync_actions_new(void) { return plist_new_dict(); } -/** - * Add one or more new key:value pairs to the given actions plist. - * - * @param actions The actions to modify. - * @param ... KEY, VALUE, [KEY, VALUE], NULL - * - * @note The known keys so far are "SyncDeviceLinkEntityNamesKey" which expects - * an array of entity names, followed by a count paramter as well as - * "SyncDeviceLinkAllRecordsOfPulledEntityTypeSentKey" which expects an - * integer to use as a boolean value indicating that the device should - * link submitted changes and report remapped identifiers. - */ void mobilesync_actions_add(plist_t actions, ...) { if (!actions) @@ -969,10 +770,10 @@ void mobilesync_actions_add(plist_t actions, ...) plist_array_append_item(array, plist_new_string(entity_names[i])); } - plist_dict_insert_item(actions, key, array); + plist_dict_set_item(actions, key, array); } else if (!strcmp(key, "SyncDeviceLinkAllRecordsOfPulledEntityTypeSentKey")) { int link_records = va_arg(args, int); - plist_dict_insert_item(actions, key, plist_new_bool(link_records)); + plist_dict_set_item(actions, key, plist_new_bool(link_records)); } free(key); key = NULL; @@ -981,11 +782,6 @@ void mobilesync_actions_add(plist_t actions, ...) va_end(args); } -/** - * Free actions plist. - * - * @param actions The actions plist to free. Does nothing if NULL is passed. - */ void mobilesync_actions_free(plist_t actions) { if (actions) { diff --git a/src/mobilesync.h b/src/mobilesync.h index 24e61af..3b5ece9 100644 --- a/src/mobilesync.h +++ b/src/mobilesync.h @@ -1,7 +1,7 @@ -/* +/* * mobilesync.h * Definitions for the built-in MobileSync client - * + * * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * @@ -9,19 +9,21 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef MOBILESYNC_H -#define MOBILESYNC_H +#ifndef __MOBILESYNC_H +#define __MOBILESYNC_H + +#include "idevice.h" #include "libimobiledevice/mobilesync.h" #include "device_link_service.h" diff --git a/src/notification_proxy.c b/src/notification_proxy.c index 80a82c4..60b2e03 100644 --- a/src/notification_proxy.c +++ b/src/notification_proxy.c @@ -8,17 +8,20 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> #include <unistd.h> @@ -26,7 +29,11 @@ #include "notification_proxy.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" + +#ifdef WIN32 +#define sleep(x) Sleep(x*1000) +#endif struct np_thread { np_client_t client; @@ -41,19 +48,19 @@ struct np_thread { */ static void np_lock(np_client_t client) { - debug_info("NP: Locked"); - g_mutex_lock(client->mutex); + debug_info("Locked"); + mutex_lock(&client->mutex); } /** * Unlocks a notification_proxy client, used for thread safety. - * + * * @param client notification_proxy client to unlock */ static void np_unlock(np_client_t client) { - debug_info("NP: Unlocked"); - g_mutex_unlock(client->mutex); + debug_info("Unlocked"); + mutex_unlock(&client->mutex); } /** @@ -82,78 +89,85 @@ static np_error_t np_error(property_list_service_error_t err) return NP_E_UNKNOWN_ERROR; } -/** - * Connects to the notification_proxy on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated np_client_t - * upon successful return. - * - * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when device is NULL, - * or NP_E_CONN_FAILED when the connection to the device could not be - * established. - */ -np_error_t np_client_new(idevice_t device, uint16_t port, np_client_t *client) +np_error_t np_client_new(idevice_t device, lockdownd_service_descriptor_t service, np_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return NP_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return NP_E_CONN_FAILED; + np_error_t err = np_error(property_list_service_client_new(device, service, &plistclient)); + if (err != NP_E_SUCCESS) { + return err; } np_client_t client_loc = (np_client_t) malloc(sizeof(struct np_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); - - client_loc->notifier = NULL; + mutex_init(&client_loc->mutex); + client_loc->notifier = THREAD_T_NULL; *client = client_loc; return NP_E_SUCCESS; } -/** - * Disconnects a notification_proxy client from the device and frees up the - * notification_proxy client data. - * - * @param client The notification_proxy client to disconnect and free. - * - * @return NP_E_SUCCESS on success, or NP_E_INVALID_ARG when client is NULL. - */ +np_error_t np_client_start_service(idevice_t device, np_client_t* client, const char* label) +{ + np_error_t err = NP_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, NP_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(np_client_new), &err); + return err; +} + np_error_t np_client_free(np_client_t client) { + plist_t dict; + property_list_service_client_t parent; + if (!client) return NP_E_INVALID_ARG; - property_list_service_client_free(client->parent); + dict = plist_new_dict(); + plist_dict_set_item(dict,"Command", plist_new_string("Shutdown")); + property_list_service_send_xml_plist(client->parent, dict); + plist_free(dict); + + parent = client->parent; + /* notifies the client->notifier thread that it should terminate */ client->parent = NULL; + if (client->notifier) { debug_info("joining np callback"); - g_thread_join(client->notifier); - } - if (client->mutex) { - g_mutex_free(client->mutex); + thread_join(client->notifier); + thread_free(client->notifier); + client->notifier = THREAD_T_NULL; + } else { + dict = NULL; + property_list_service_receive_plist(parent, &dict); + if (dict) { +#ifndef STRIP_DEBUG_CODE + char *cmd_value = NULL; + plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); + if (plist_get_node_type(cmd_value_node) == PLIST_STRING) { + plist_get_string_val(cmd_value_node, &cmd_value); + } + if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { + // this is the expected answer + } else { + debug_info("Did not get ProxyDeath but:"); + debug_plist(dict); + } + if (cmd_value) { + free(cmd_value); + } +#endif + plist_free(dict); + } } + + property_list_service_client_free(parent); + + mutex_destroy(&client->mutex); free(client); return NP_E_SUCCESS; } -/** - * Sends a notification to the device's notification_proxy. - * - * @param client The client to send to - * @param notification The notification message to send - * - * @return NP_E_SUCCESS on success, or an error returned by np_plist_send - */ np_error_t np_post_notification(np_client_t client, const char *notification) { if (!client || !notification) { @@ -162,66 +176,24 @@ np_error_t np_post_notification(np_client_t client, const char *notification) np_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("PostNotification")); - plist_dict_insert_item(dict,"Name", plist_new_string(notification)); + plist_dict_set_item(dict,"Command", plist_new_string("PostNotification")); + plist_dict_set_item(dict,"Name", plist_new_string(notification)); np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); - dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("Shutdown")); - - res = np_error(property_list_service_send_xml_plist(client->parent, dict)); - plist_free(dict); - if (res != NP_E_SUCCESS) { debug_info("Error sending XML plist to device!"); } - - // try to read an answer, we just ignore errors here - dict = NULL; - property_list_service_receive_plist(client->parent, &dict); - if (dict) { -#ifndef STRIP_DEBUG_CODE - char *cmd_value = NULL; - plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); - if (plist_get_node_type(cmd_value_node) == PLIST_STRING) { - plist_get_string_val(cmd_value_node, &cmd_value); - } - - if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { - // this is the expected answer - } else { - debug_plist(dict); - } - g_free(cmd_value); -#endif - plist_free(dict); - } - np_unlock(client); return res; } -/** - * Tells the device to send a notification on the specified event. - * - * @param client The client to send to - * @param notification The notifications that should be observed. - * - * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * notification are NULL, or an error returned by np_plist_send. - */ -np_error_t np_observe_notification( np_client_t client, const char *notification ) +static np_error_t internal_np_observe_notification(np_client_t client, const char *notification) { - if (!client || !notification) { - return NP_E_INVALID_ARG; - } - np_lock(client); - plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("ObserveNotification")); - plist_dict_insert_item(dict,"Name", plist_new_string(notification)); + plist_dict_set_item(dict,"Command", plist_new_string("ObserveNotification")); + plist_dict_set_item(dict,"Name", plist_new_string(notification)); np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); if (res != NP_E_SUCCESS) { @@ -229,21 +201,20 @@ np_error_t np_observe_notification( np_client_t client, const char *notification } plist_free(dict); + return res; +} + +np_error_t np_observe_notification( np_client_t client, const char *notification ) +{ + if (!client || !notification) { + return NP_E_INVALID_ARG; + } + np_lock(client); + np_error_t res = internal_np_observe_notification(client, notification); np_unlock(client); return res; } -/** - * Tells the device to send a notification on specified events. - * - * @param client The client to send to - * @param notification_spec Specification of the notifications that should be - * observed. This is expected to be an array of const char* that MUST have a - * terminating NULL entry. - * - * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client is null, - * or an error returned by np_observe_notification. - */ np_error_t np_observe_notifications(np_client_t client, const char **notification_spec) { int i = 0; @@ -258,13 +229,15 @@ np_error_t np_observe_notifications(np_client_t client, const char **notificatio return NP_E_INVALID_ARG; } + np_lock(client); while (notifications[i]) { - res = np_observe_notification(client, notifications[i]); + res = internal_np_observe_notification(client, notifications[i]); if (res != NP_E_SUCCESS) { break; } i++; } + np_unlock(client); return res; } @@ -277,7 +250,7 @@ np_error_t np_observe_notifications(np_client_t client, const char **notificatio * with the notification that has been received. * * @return 0 if a notification has been received or nothing has been received, - * or a negative value if an error occured. + * or a negative value if an error occurred. * * @note You probably want to check out np_set_notify_callback * @see np_set_notify_callback @@ -292,11 +265,15 @@ static int np_get_notification(np_client_t client, char **notification) np_lock(client); - property_list_service_receive_plist_with_timeout(client->parent, &dict, 500); - if (!dict) { + property_list_service_error_t perr = property_list_service_receive_plist_with_timeout(client->parent, &dict, 500); + if (perr == PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT) { debug_info("NotificationProxy: no notification received!"); res = 0; - } else { + } else if (perr != PROPERTY_LIST_SERVICE_E_SUCCESS) { + debug_info("NotificationProxy: error %d occurred!", perr); + res = perr; + } + if (dict) { char *cmd_value = NULL; plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); @@ -315,11 +292,11 @@ static int np_get_notification(np_client_t client, char **notification) res = -2; if (name_value_node && name_value) { *notification = name_value; - debug_info("got notification %s\n", __func__, name_value); + debug_info("got notification %s", __func__, name_value); res = 0; } } else if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { - debug_info("ERROR: NotificationProxy died!"); + debug_info("NotificationProxy died!"); res = -1; } else if (cmd_value) { debug_info("unknown NotificationProxy command '%s' received!", cmd_value); @@ -342,7 +319,7 @@ static int np_get_notification(np_client_t client, char **notification) /** * Internally used thread function. */ -gpointer np_notifier( gpointer arg ) +void* np_notifier( void* arg ) { char *notification = NULL; struct np_thread *npt = (struct np_thread*)arg; @@ -351,7 +328,10 @@ gpointer np_notifier( gpointer arg ) debug_info("starting callback."); while (npt->client->parent) { - np_get_notification(npt->client, ¬ification); + if (np_get_notification(npt->client, ¬ification) < 0) { + npt->cbfunc("", npt->user_data); + break; + } if (notification) { npt->cbfunc(notification, npt->user_data); free(notification); @@ -366,25 +346,6 @@ gpointer np_notifier( gpointer arg ) return NULL; } -/** - * This function allows an application to define a callback function that will - * be called when a notification has been received. - * It will start a thread that polls for notifications and calls the callback - * function if a notification has been received. - * - * @param client the NP client - * @param notify_cb pointer to a callback function or NULL to de-register a - * previously set callback function. - * @param user_data Pointer that will be passed to the callback function as - * user data. If notify_cb is NULL, this parameter is ignored. - * - * @note Only one callback function can be registered at the same time; - * any previously set callback function will be removed automatically. - * - * @return NP_E_SUCCESS when the callback was successfully registered, - * NP_E_INVALID_ARG when client is NULL, or NP_E_UNKNOWN_ERROR when - * the callback thread could no be created. - */ np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb, void *user_data ) { if (!client) @@ -394,11 +355,12 @@ np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb, np_lock(client); if (client->notifier) { - debug_info("callback already set, removing\n"); + debug_info("callback already set, removing"); property_list_service_client_t parent = client->parent; client->parent = NULL; - g_thread_join(client->notifier); - client->notifier = NULL; + thread_join(client->notifier); + thread_free(client->notifier); + client->notifier = THREAD_T_NULL; client->parent = parent; } @@ -409,8 +371,7 @@ np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb, npt->cbfunc = notify_cb; npt->user_data = user_data; - client->notifier = g_thread_create(np_notifier, npt, TRUE, NULL); - if (client->notifier) { + if (thread_new(&client->notifier, np_notifier, npt) == 0) { res = NP_E_SUCCESS; } } diff --git a/src/notification_proxy.h b/src/notification_proxy.h index 8d5cd24..595cb01 100644 --- a/src/notification_proxy.h +++ b/src/notification_proxy.h @@ -8,30 +8,31 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef INOTIFICATION_PROXY_H -#define INOTIFICATION_PROXY_H -#include <glib.h> +#ifndef __NOTIFICATION_PROXY_H +#define __NOTIFICATION_PROXY_H +#include "idevice.h" #include "libimobiledevice/notification_proxy.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct np_client_private { property_list_service_client_t parent; - GMutex *mutex; - GThread *notifier; + mutex_t mutex; + THREAD_T notifier; }; -gpointer np_notifier(gpointer arg); +void* np_notifier(void* arg); #endif diff --git a/src/preboard.c b/src/preboard.c new file mode 100644 index 0000000..c3eff02 --- /dev/null +++ b/src/preboard.c @@ -0,0 +1,256 @@ +/* + * preboard.c + * com.apple.preboardservice_v2 service implementation. + * + * Copyright (c) 2019 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <plist/plist.h> + +#include "preboard.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a preboard_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching preboard_error_t error code, + * PREBOARD_E_UNKNOWN_ERROR otherwise. + */ +static preboard_error_t preboard_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return PREBOARD_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return PREBOARD_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return PREBOARD_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return PREBOARD_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return PREBOARD_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return PREBOARD_E_NOT_ENOUGH_DATA; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return PREBOARD_E_TIMEOUT; + default: + break; + } + return PREBOARD_E_UNKNOWN_ERROR; +} + +preboard_error_t preboard_client_new(idevice_t device, lockdownd_service_descriptor_t service, preboard_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to preboard_client_new."); + return PREBOARD_E_INVALID_ARG; + } + + debug_info("Creating preboard_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + preboard_error_t ret = preboard_error(property_list_service_client_new(device, service, &plclient)); + if (ret != PREBOARD_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + preboard_client_t client_loc = (preboard_client_t) malloc(sizeof(struct preboard_client_private)); + client_loc->parent = plclient; + client_loc->receive_status_thread = THREAD_T_NULL; + + *client = client_loc; + + debug_info("preboard_client successfully created."); + return 0; +} + +preboard_error_t preboard_client_start_service(idevice_t device, preboard_client_t * client, const char* label) +{ + preboard_error_t err = PREBOARD_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, PREBOARD_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(preboard_client_new), &err); + return err; +} + +preboard_error_t preboard_client_free(preboard_client_t client) +{ + if (!client) + return PREBOARD_E_INVALID_ARG; + + property_list_service_client_t parent = client->parent; + client->parent = NULL; + if (client->receive_status_thread) { + debug_info("joining receive_status_thread"); + thread_join(client->receive_status_thread); + thread_free(client->receive_status_thread); + client->receive_status_thread = THREAD_T_NULL; + } + preboard_error_t err = preboard_error(property_list_service_client_free(parent)); + free(client); + + return err; +} + +preboard_error_t preboard_send(preboard_client_t client, plist_t plist) +{ + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + res = preboard_error(property_list_service_send_binary_plist(client->parent, plist)); + if (res != PREBOARD_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + return res; +} + +preboard_error_t preboard_receive_with_timeout(preboard_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + plist_t outplist = NULL; + res = preboard_error(property_list_service_receive_plist_with_timeout(client->parent, &outplist, timeout_ms)); + if (res != PREBOARD_E_SUCCESS && res != PREBOARD_E_TIMEOUT) { + debug_info("Could not receive plist, error %d", res); + plist_free(outplist); + } else if (res == PREBOARD_E_SUCCESS) { + *plist = outplist; + } + return res; +} + +preboard_error_t preboard_receive(preboard_client_t client, plist_t * plist) +{ + return preboard_receive_with_timeout(client, plist, 5000); +} + +struct preboard_status_data { + preboard_client_t client; + preboard_status_cb_t cbfunc; + void *user_data; +}; + +static void* preboard_receive_status_loop_thread(void* arg) +{ + struct preboard_status_data *data = (struct preboard_status_data*)arg; + + /* run until the service disconnects or an error occurs */ + while (data->client && data->client->parent) { + plist_t pl = NULL; + preboard_error_t perr = preboard_receive_with_timeout(data->client, &pl, 1000); + if (perr == PREBOARD_E_TIMEOUT) { + continue; + } + if (perr == PREBOARD_E_SUCCESS) { + data->cbfunc(pl, data->user_data); + } + plist_free(pl); + if (perr != PREBOARD_E_SUCCESS) { + data->cbfunc(NULL, data->user_data); + break; + } + } + + /* cleanup */ + debug_info("done, cleaning up."); + + if (data->client->receive_status_thread) { + thread_free(data->client->receive_status_thread); + data->client->receive_status_thread = THREAD_T_NULL; + } + free(data); + + return NULL; +} + +static preboard_error_t preboard_receive_status_loop_with_callback(preboard_client_t client, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client || !client->parent) { + return PREBOARD_E_INVALID_ARG; + } + + if (client->receive_status_thread) { + return PREBOARD_E_OP_IN_PROGRESS; + } + + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + struct preboard_status_data *data = (struct preboard_status_data*)malloc(sizeof(struct preboard_status_data)); + if (data) { + data->client = client; + data->cbfunc = status_cb; + data->user_data = user_data; + if (thread_new(&client->receive_status_thread, preboard_receive_status_loop_thread, data) == 0) { + res = PREBOARD_E_SUCCESS; + } + } + + return res; +} + +preboard_error_t preboard_create_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client) { + return PREBOARD_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("CreateStashbag")); + if (manifest) { + plist_dict_set_item(dict, "Manifest", plist_copy(manifest)); + } + preboard_error_t perr = preboard_send(client, dict); + plist_free(dict); + if (perr != PREBOARD_E_SUCCESS) { + return perr; + } + if (!status_cb) { + return PREBOARD_E_SUCCESS; + } + + return preboard_receive_status_loop_with_callback(client, status_cb, user_data); +} + +preboard_error_t preboard_commit_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client) { + return PREBOARD_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("CommitStashbag")); + if (manifest) { + plist_dict_set_item(dict, "Manifest", plist_copy(manifest)); + } + preboard_error_t perr = preboard_send(client, dict); + plist_free(dict); + if (perr != PREBOARD_E_SUCCESS) { + return perr; + } + if (!status_cb) { + return PREBOARD_E_SUCCESS; + } + + return preboard_receive_status_loop_with_callback(client, status_cb, user_data); +} diff --git a/src/preboard.h b/src/preboard.h new file mode 100644 index 0000000..f8164eb --- /dev/null +++ b/src/preboard.h @@ -0,0 +1,35 @@ +/* + * preboard.h + * com.apple.preboard_v2 service header file. + * + * Copyright (c) 2019 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PREBOARD_H +#define __PREBOARD_H + +#include "idevice.h" +#include "libimobiledevice/preboard.h" +#include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> + +struct preboard_client_private { + property_list_service_client_t parent; + THREAD_T receive_status_thread; +}; + +#endif diff --git a/src/property_list_service.c b/src/property_list_service.c index 8af958e..2fca4e7 100644 --- a/src/property_list_service.c +++ b/src/property_list_service.c @@ -1,4 +1,4 @@ -/* +/* * property_list_service.c * PropertyList service implementation. * @@ -8,97 +8,86 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <stdlib.h> #include <string.h> -#include <glib.h> #include "property_list_service.h" -#include "idevice.h" -#include "debug.h" +#include "common/debug.h" +#include "endianness.h" /** - * Convert an idevice_error_t value to an property_list_service_error_t value. + * Convert a service_error_t value to a property_list_service_error_t value. * Used internally to get correct error codes. * - * @param err An idevice_error_t error code + * @param err A service_error_t error code * * @return A matching property_list_service_error_t error code, * PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. */ -static property_list_service_error_t idevice_to_property_list_service_error(idevice_error_t err) +static property_list_service_error_t service_to_property_list_service_error(service_error_t err) { switch (err) { - case IDEVICE_E_SUCCESS: + case SERVICE_E_SUCCESS: return PROPERTY_LIST_SERVICE_E_SUCCESS; - case IDEVICE_E_INVALID_ARG: + case SERVICE_E_INVALID_ARG: return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - case IDEVICE_E_SSL_ERROR: + case SERVICE_E_MUX_ERROR: + return PROPERTY_LIST_SERVICE_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: return PROPERTY_LIST_SERVICE_E_SSL_ERROR; + case SERVICE_E_NOT_ENOUGH_DATA: + return PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA; + case SERVICE_E_TIMEOUT: + return PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT; default: break; } return PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR; } -/** - * Creates a new property list service for the specified port. - * - * @param device The device to connect to. - * @param port The port on the device to connect to, usually opened by a call to - * lockdownd_start_service. - * @param client Pointer that will be set to a newly allocated - * property_list_service_client_t upon successful return. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when one of the arguments is invalid, - * or PROPERTY_LIST_SERVICE_E_MUX_ERROR when connecting to the device failed. - */ -property_list_service_error_t property_list_service_client_new(idevice_t device, uint16_t port, property_list_service_client_t *client) +property_list_service_error_t property_list_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, property_list_service_client_t *client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - /* Attempt connection */ - idevice_connection_t connection = NULL; - if (idevice_connect(device, port, &connection) != IDEVICE_E_SUCCESS) { - return PROPERTY_LIST_SERVICE_E_MUX_ERROR; + service_client_t parent = NULL; + service_error_t rerr = service_client_new(device, service, &parent); + if (rerr != SERVICE_E_SUCCESS) { + return service_to_property_list_service_error(rerr); } /* create client object */ property_list_service_client_t client_loc = (property_list_service_client_t)malloc(sizeof(struct property_list_service_client_private)); - client_loc->connection = connection; + client_loc->parent = parent; + /* all done, return success */ *client = client_loc; - return PROPERTY_LIST_SERVICE_E_SUCCESS; } -/** - * Frees a PropertyList service. - * - * @param client The property list service to free. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client is invalid, or a - * PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when another error occured. - */ property_list_service_error_t property_list_service_client_free(property_list_service_client_t client) { if (!client) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - property_list_service_error_t err = idevice_to_property_list_service_error(idevice_disconnect(client->connection)); + property_list_service_error_t err = service_to_property_list_service_error(service_client_free(client->parent)); + free(client); + client = NULL; + return err; } @@ -113,7 +102,8 @@ property_list_service_error_t property_list_service_client_free(property_list_se * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, * PROPERTY_LIST_SERVICE_E_INVALID_ARG when one or more parameters are * invalid, PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid - * plist, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified + * plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a communication error + * occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified * error occurs. */ static property_list_service_error_t internal_plist_send(property_list_service_client_t client, plist_t plist, int binary) @@ -122,9 +112,9 @@ static property_list_service_error_t internal_plist_send(property_list_service_c char *content = NULL; uint32_t length = 0; uint32_t nlen = 0; - int bytes = 0; + uint32_t bytes = 0; - if (!client || (client && !client->connection) || !plist) { + if (!client || (client && !client->parent) || !plist) { return PROPERTY_LIST_SERVICE_E_INVALID_ARG; } @@ -138,15 +128,15 @@ static property_list_service_error_t internal_plist_send(property_list_service_c return PROPERTY_LIST_SERVICE_E_PLIST_ERROR; } - nlen = GUINT32_TO_BE(length); + nlen = htobe32(length); debug_info("sending %d bytes", length); - idevice_connection_send(client->connection, (const char*)&nlen, sizeof(nlen), (uint32_t*)&bytes); + service_send(client->parent, (const char*)&nlen, sizeof(nlen), &bytes); if (bytes == sizeof(nlen)) { - idevice_connection_send(client->connection, content, length, (uint32_t*)&bytes); + service_send(client->parent, content, length, &bytes); if (bytes > 0) { debug_info("sent %d bytes", bytes); debug_plist(plist); - if ((uint32_t)bytes == length) { + if (bytes == length) { res = PROPERTY_LIST_SERVICE_E_SUCCESS; } else { debug_info("ERROR: Could not send all data (%d of %d)!", bytes, length); @@ -155,40 +145,18 @@ static property_list_service_error_t internal_plist_send(property_list_service_c } if (bytes <= 0) { debug_info("ERROR: sending to device failed."); + res = PROPERTY_LIST_SERVICE_E_MUX_ERROR; } free(content); - return res; } -/** - * Sends an XML plist. - * - * @param client The property list service client to use for sending. - * @param plist plist to send - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid plist, - * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified error occurs. - */ property_list_service_error_t property_list_service_send_xml_plist(property_list_service_client_t client, plist_t plist) { return internal_plist_send(client, plist, 0); } -/** - * Sends a binary plist. - * - * @param client The property list service client to use for sending. - * @param plist plist to send - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid plist, - * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified error occurs. - */ property_list_service_error_t property_list_service_send_binary_plist(property_list_service_client_t client, plist_t plist) { return internal_plist_send(client, plist, 1); @@ -205,6 +173,8 @@ property_list_service_error_t property_list_service_send_binary_plist(property_l * * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or *plist is NULL, + * PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA when not enough data + * received, PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT when the connection times out, * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR @@ -216,135 +186,110 @@ static property_list_service_error_t internal_plist_receive_timeout(property_lis uint32_t pktlen = 0; uint32_t bytes = 0; - if (!client || (client && !client->connection) || !plist) { + if (!client || (client && !client->parent) || !plist) { return PROPERTY_LIST_SERVICE_E_INVALID_ARG; } - idevice_connection_receive_timeout(client->connection, (char*)&pktlen, sizeof(pktlen), &bytes, timeout); - debug_info("initial read=%i", bytes); - if (bytes < 4) { + *plist = NULL; + service_error_t serr = service_receive_with_timeout(client->parent, (char*)&pktlen, sizeof(pktlen), &bytes, timeout); + if (serr != SERVICE_E_SUCCESS) { debug_info("initial read failed!"); - return PROPERTY_LIST_SERVICE_E_MUX_ERROR; - } else { - pktlen = GUINT32_FROM_BE(pktlen); - if (pktlen < (1 << 24)) { /* prevent huge buffers */ - uint32_t curlen = 0; - char *content = NULL; - debug_info("%d bytes following", pktlen); - content = (char*)malloc(pktlen); - - while (curlen < pktlen) { - idevice_connection_receive(client->connection, content+curlen, pktlen-curlen, &bytes); - if (bytes <= 0) { - res = PROPERTY_LIST_SERVICE_E_MUX_ERROR; - break; - } - debug_info("received %d bytes", bytes); - curlen += bytes; - } - if (!memcmp(content, "bplist00", 8)) { - plist_from_bin(content, pktlen, plist); - } else { - /* iOS 4.3 hack: plist data might contain invalid null characters, thus we convert those to spaces */ - for (bytes = 0; bytes < pktlen-1; bytes++) { - if (content[bytes] == 0x0) - content[bytes] = 0x20; - } - plist_from_xml(content, pktlen, plist); - } - if (*plist) { - debug_plist(*plist); - res = PROPERTY_LIST_SERVICE_E_SUCCESS; - } else { - res = PROPERTY_LIST_SERVICE_E_PLIST_ERROR; - } - free(content); - content = NULL; - } else { - res = PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR; + return service_to_property_list_service_error(serr); + } + + if (bytes == 0) { + /* success but 0 bytes length, assume timeout */ + return PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT; + } + + debug_info("initial read=%i", bytes); + + uint32_t curlen = 0; + char *content = NULL; + + pktlen = be32toh(pktlen); + debug_info("%d bytes following", pktlen); + content = (char*)malloc(pktlen); + if (!content) { + debug_info("out of memory when allocating %d bytes", pktlen); + return PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR; + } + + while (curlen < pktlen) { + serr = service_receive(client->parent, content+curlen, pktlen-curlen, &bytes); + if (serr != SERVICE_E_SUCCESS) { + res = service_to_property_list_service_error(serr); + break; } + debug_info("received %d bytes", bytes); + curlen += bytes; } + + if (curlen < pktlen) { + debug_info("received incomplete packet (%d of %d bytes)", curlen, pktlen); + if (curlen > 0) { + debug_info("incomplete packet following:"); + debug_buffer(content, curlen); + } + free(content); + return res; + } + + if ((pktlen > 8) && !memcmp(content, "bplist00", 8)) { + plist_from_bin(content, pktlen, plist); + } else if ((pktlen > 5) && !memcmp(content, "<?xml", 5)) { + /* iOS 4.3+ hack: plist data might contain invalid characters, thus we convert those to spaces */ + for (bytes = 0; bytes < pktlen-1; bytes++) { + if ((content[bytes] >= 0) && (content[bytes] < 0x20) && (content[bytes] != 0x09) && (content[bytes] != 0x0a) && (content[bytes] != 0x0d)) + content[bytes] = 0x20; + } + plist_from_xml(content, pktlen, plist); + } else { + debug_info("WARNING: received unexpected non-plist content"); + debug_buffer(content, pktlen); + } + + if (*plist) { + debug_plist(*plist); + res = PROPERTY_LIST_SERVICE_E_SUCCESS; + } else { + res = PROPERTY_LIST_SERVICE_E_PLIST_ERROR; + } + + free(content); + content = NULL; + return res; } -/** - * Receives a plist using the given property list service client with specified - * timeout. - * Binary or XML plists are automatically handled. - * - * @param client The property list service client to use for receiving - * @param plist pointer to a plist_t that will point to the received plist - * upon successful return - * @param timeout Maximum time in milliseconds to wait for data. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when connection or *plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be - * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a - * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when - * an unspecified error occurs. - */ property_list_service_error_t property_list_service_receive_plist_with_timeout(property_list_service_client_t client, plist_t *plist, unsigned int timeout) { return internal_plist_receive_timeout(client, plist, timeout); } -/** - * Receives a plist using the given property list service client. - * Binary or XML plists are automatically handled. - * - * This function is like property_list_service_receive_plist_with_timeout - * using a timeout of 10 seconds. - * @see property_list_service_receive_plist_with_timeout - * - * @param client The property list service client to use for receiving - * @param plist pointer to a plist_t that will point to the received plist - * upon successful return - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or *plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be - * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a - * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when - * an unspecified error occurs. - */ property_list_service_error_t property_list_service_receive_plist(property_list_service_client_t client, plist_t *plist) { - return internal_plist_receive_timeout(client, plist, 10000); + return internal_plist_receive_timeout(client, plist, 30000); } -/** - * Enable SSL for the given property list service client. - * - * @param client The connected property list service client for which SSL - * should be enabled. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG if client or client->connection is - * NULL, PROPERTY_LIST_SERVICE_E_SSL_ERROR when SSL could not be enabled, - * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. - */ property_list_service_error_t property_list_service_enable_ssl(property_list_service_client_t client) { - if (!client || !client->connection) + if (!client || !client->parent) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - return idevice_to_property_list_service_error(idevice_connection_enable_ssl(client->connection)); + return service_to_property_list_service_error(service_enable_ssl(client->parent)); } -/** - * Disable SSL for the given property list service client. - * - * @param client The connected property list service client for which SSL - * should be disabled. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG if client or client->connection is - * NULL, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. - */ property_list_service_error_t property_list_service_disable_ssl(property_list_service_client_t client) { - if (!client || !client->connection) + if (!client || !client->parent) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - return idevice_to_property_list_service_error(idevice_connection_disable_ssl(client->connection)); + return service_to_property_list_service_error(service_disable_ssl(client->parent)); } +property_list_service_error_t property_list_service_get_service_client(property_list_service_client_t client, service_client_t *service_client) +{ + if (!client || !client->parent || !service_client) + return PROPERTY_LIST_SERVICE_E_INVALID_ARG; + *service_client = client->parent; + return PROPERTY_LIST_SERVICE_E_SUCCESS; +} diff --git a/src/property_list_service.h b/src/property_list_service.h index 037f9aa..0e9e948 100644 --- a/src/property_list_service.h +++ b/src/property_list_service.h @@ -1,59 +1,33 @@ - /* +/* * property_list_service.h * Definitions for the PropertyList service - * + * * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PROPERTY_LIST_SERVICE_H -#define PROPERTY_LIST_SERVICE_H -#include "idevice.h" - -/* Error Codes */ -#define PROPERTY_LIST_SERVICE_E_SUCCESS 0 -#define PROPERTY_LIST_SERVICE_E_INVALID_ARG -1 -#define PROPERTY_LIST_SERVICE_E_PLIST_ERROR -2 -#define PROPERTY_LIST_SERVICE_E_MUX_ERROR -3 -#define PROPERTY_LIST_SERVICE_E_SSL_ERROR -4 +#ifndef __PROPERTY_LIST_SERVICE_H +#define __PROPERTY_LIST_SERVICE_H -#define PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR -256 +#include "idevice.h" +#include "libimobiledevice/property_list_service.h" +#include "service.h" struct property_list_service_client_private { - idevice_connection_t connection; + service_client_t parent; }; -typedef struct property_list_service_client_private *property_list_service_client_t; - -typedef int16_t property_list_service_error_t; - -/* creation and destruction */ -property_list_service_error_t property_list_service_client_new(idevice_t device, uint16_t port, property_list_service_client_t *client); -property_list_service_error_t property_list_service_client_free(property_list_service_client_t client); - -/* sending */ -property_list_service_error_t property_list_service_send_xml_plist(property_list_service_client_t client, plist_t plist); -property_list_service_error_t property_list_service_send_binary_plist(property_list_service_client_t client, plist_t plist); - -/* receiving */ -property_list_service_error_t property_list_service_receive_plist_with_timeout(property_list_service_client_t client, plist_t *plist, unsigned int timeout); -property_list_service_error_t property_list_service_receive_plist(property_list_service_client_t client, plist_t *plist); - -/* misc */ -property_list_service_error_t property_list_service_enable_ssl(property_list_service_client_t client); -property_list_service_error_t property_list_service_disable_ssl(property_list_service_client_t client); - #endif diff --git a/src/restore.c b/src/restore.c index fd23d85..d13a28a 100644 --- a/src/restore.c +++ b/src/restore.c @@ -19,17 +19,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <arpa/inet.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <errno.h> #include <string.h> #include <stdlib.h> -#include <glib.h> #include <plist/plist.h> #include "property_list_service.h" #include "restore.h" #include "idevice.h" -#include "debug.h" +#include "common/debug.h" #define RESULT_SUCCESS 0 #define RESULT_FAILURE 1 @@ -43,7 +44,7 @@ * * @return RESULT_SUCCESS when the result is 'Success', * RESULT_FAILURE when the result is 'Failure', - * or a negative value if an error occured during evaluation. + * or a negative value if an error occurred during evaluation. */ static int restored_check_result(plist_t dict) { @@ -87,40 +88,49 @@ static void plist_dict_add_label(plist_t plist, const char *label) { if (plist && label) { if (plist_get_node_type(plist) == PLIST_DICT) - plist_dict_insert_item(plist, "Label", plist_new_string(label)); + plist_dict_set_item(plist, "Label", plist_new_string(label)); } } -/** - * Closes the restored client session if one is running and frees up the - * restored_client struct. - * - * @param client The restore client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ +static restored_error_t restored_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return RESTORE_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return RESTORE_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return RESTORE_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return RESTORE_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return RESTORE_E_RECEIVE_TIMEOUT; + default: + break; + } + return RESTORE_E_UNKNOWN_ERROR; +} + restored_error_t restored_client_free(restored_client_t client) { if (!client) return RESTORE_E_INVALID_ARG; - + restored_error_t ret = RESTORE_E_UNKNOWN_ERROR; if (client->parent) { restored_goodbye(client); - if (property_list_service_client_free(client->parent) == PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = RESTORE_E_SUCCESS; - } + ret = restored_error(property_list_service_client_free(client->parent)); } - if (client->uuid) { - free(client->uuid); + if (client->udid) { + free(client->udid); } if (client->label) { free(client->label); } - + if (client->info) { plist_free(client->info); } @@ -129,13 +139,6 @@ restored_error_t restored_client_free(restored_client_t client) return ret; } -/** - * Sets the label to send for requests to restored. - * - * @param client The restore client - * @param label The label to set or NULL to disable sending a label - * - */ void restored_client_set_label(restored_client_t client, const char *label) { if (client) { @@ -146,71 +149,22 @@ void restored_client_set_label(restored_client_t client, const char *label) } } -/** - * Receives a plist from restored. - * - * @param client The restored client - * @param plist The plist to store the received data - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ restored_error_t restored_receive(restored_client_t client, plist_t *plist) { if (!client || !plist || (plist && *plist)) return RESTORE_E_INVALID_ARG; - - restored_error_t ret = RESTORE_E_SUCCESS; - property_list_service_error_t err; - - err = property_list_service_receive_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = RESTORE_E_UNKNOWN_ERROR; - } - if (!*plist) - ret = RESTORE_E_PLIST_ERROR; - - return ret; + return restored_error(property_list_service_receive_plist(client->parent, plist)); } -/** - * Sends a plist to restored. - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The restored client - * @param plist The plist to send - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ restored_error_t restored_send(restored_client_t client, plist_t plist) { if (!client || !plist) return RESTORE_E_INVALID_ARG; - restored_error_t ret = RESTORE_E_SUCCESS; - idevice_error_t err; - - err = property_list_service_send_xml_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = RESTORE_E_UNKNOWN_ERROR; - } - return ret; + return restored_error(property_list_service_send_xml_plist(client->parent, plist)); } -/** - * Query the type of the service daemon. Depending on whether the device is - * queried in normal mode or restore mode, different types will be returned. - * - * @param client The restored client - * @param type The type returned by the service daemon. Pass NULL to ignore. - * @param version The restore protocol version. Pass NULL to ignore. - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ restored_error_t restored_query_type(restored_client_t client, char **type, uint64_t *version) { if (!client) @@ -220,7 +174,7 @@ restored_error_t restored_query_type(restored_client_t client, char **type, uint plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("QueryType")); + plist_dict_set_item(dict,"Request", plist_new_string("QueryType")); debug_info("called"); ret = restored_send(client, dict); @@ -229,25 +183,25 @@ restored_error_t restored_query_type(restored_client_t client, char **type, uint dict = NULL; ret = restored_receive(client, &dict); - + if (RESTORE_E_SUCCESS != ret) return ret; ret = RESTORE_E_UNKNOWN_ERROR; - if (restored_check_result(dict) == RESULT_SUCCESS) { + plist_t type_node = plist_dict_get_item(dict, "Type"); + if (type_node && (plist_get_node_type(type_node) == PLIST_STRING)) { + char* typestr = NULL; + /* save our device information info */ client->info = dict; - + + plist_get_string_val(type_node, &typestr); + debug_info("success with type %s", typestr); /* return the type if requested */ if (type) { - plist_t type_node = plist_dict_get_item(dict, "Type"); - if (type_node && PLIST_STRING == plist_get_node_type(type_node)) { - plist_get_string_val(type_node, type); - debug_info("success with type %s", *type); - ret = RESTORE_E_SUCCESS; - } else { - return RESTORE_E_UNKNOWN_ERROR; - } + *type = typestr; + } else { + free(typestr); } /* fetch the restore protocol version */ @@ -256,87 +210,121 @@ restored_error_t restored_query_type(restored_client_t client, char **type, uint if (version_node && PLIST_UINT == plist_get_node_type(version_node)) { plist_get_uint_val(version_node, version); debug_info("restored protocol version %llu", *version); - ret = RESTORE_E_SUCCESS; } else { return RESTORE_E_UNKNOWN_ERROR; } } ret = RESTORE_E_SUCCESS; + } else { + debug_info("hmm. QueryType response does not contain a type?!"); + debug_plist(dict); + plist_free(dict); } return ret; } -/** - * Retrieves a value from information plist specified by a key. - * - * @param client An initialized restored client. - * @param key The key name to request or NULL to query for all keys - * @param value A plist node representing the result value node - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, RESTORE_E_PLIST_ERROR if value for key can't be found - */ -restored_error_t restored_get_value(restored_client_t client, const char *key, plist_t *value) +restored_error_t restored_query_value(restored_client_t client, const char *key, plist_t *value) { + if (!client || !key) + return RESTORE_E_INVALID_ARG; + + plist_t dict = NULL; + restored_error_t ret = RESTORE_E_UNKNOWN_ERROR; + + /* setup request plist */ + dict = plist_new_dict(); + plist_dict_add_label(dict, client->label); + if (key) { + plist_dict_set_item(dict,"QueryKey", plist_new_string(key)); + } + plist_dict_set_item(dict,"Request", plist_new_string("QueryValue")); + + /* send to device */ + ret = restored_send(client, dict); + + plist_free(dict); + dict = NULL; + + if (ret != RESTORE_E_SUCCESS) + return ret; + + /* Now get device's answer */ + ret = restored_receive(client, &dict); + if (ret != RESTORE_E_SUCCESS) + return ret; + + plist_t value_node = plist_dict_get_item(dict, key); + if (value_node) { + debug_info("has a value"); + *value = plist_copy(value_node); + } else { + ret = RESTORE_E_PLIST_ERROR; + } + + plist_free(dict); + return ret; +} + +restored_error_t restored_get_value(restored_client_t client, const char *key, plist_t *value) +{ + plist_t item; + if (!client || !value || (value && *value)) return RESTORE_E_INVALID_ARG; - + if (!client->info) return RESTORE_E_NOT_ENOUGH_DATA; - - restored_error_t ret = RESTORE_E_SUCCESS; - plist_t item = NULL; - + if (!key) { *value = plist_copy(client->info); return RESTORE_E_SUCCESS; - } else { - item = plist_dict_get_item(client->info, key); } - - if (item) { - *value = plist_copy(item); - } else { - ret = RESTORE_E_PLIST_ERROR; + + item = plist_dict_get_item(client->info, key); + if (!item) { + return RESTORE_E_PLIST_ERROR; } - - return ret; + + *value = plist_copy(item); + + return RESTORE_E_SUCCESS; } -/** - * Creates a new restored client for the device. - * - * @param device The device to create a restored client for - * @param client The pointer to the location of the new restored_client - * @param label The label to use for communication. Usually the program name. - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ restored_error_t restored_client_new(idevice_t device, restored_client_t *client, const char *label) { if (!client) return RESTORE_E_INVALID_ARG; restored_error_t ret = RESTORE_E_SUCCESS; + idevice_error_t idev_ret; + + static struct lockdownd_service_descriptor service = { + .port = 0xf27e, + .ssl_enabled = 0 + }; property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, 0xf27e, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - debug_info("could not connect to restored (device %s)", device->uuid); - return RESTORE_E_MUX_ERROR; + ret = restored_error(property_list_service_client_new(device, (lockdownd_service_descriptor_t)&service, &plistclient)); + if (ret != RESTORE_E_SUCCESS) { + debug_info("could not connect to restored (device %s)", device->udid); + return ret; } restored_client_t client_loc = (restored_client_t) malloc(sizeof(struct restored_client_private)); client_loc->parent = plistclient; - client_loc->uuid = NULL; + client_loc->udid = NULL; client_loc->label = NULL; + client_loc->info = NULL; if (label != NULL) client_loc->label = strdup(label); - ret = idevice_get_uuid(device, &client_loc->uuid); - if (RESTORE_E_SUCCESS != ret) { - debug_info("failed to get device uuid."); + idev_ret = idevice_get_udid(device, &client_loc->udid); + if (IDEVICE_E_SUCCESS != idev_ret) { + debug_info("failed to get device udid."); + ret = RESTORE_E_UNKNOWN_ERROR; } - debug_info("device uuid: %s", client_loc->uuid); + debug_info("device udid: %s", client_loc->udid); if (RESTORE_E_SUCCESS == ret) { *client = client_loc; @@ -347,14 +335,6 @@ restored_error_t restored_client_new(idevice_t device, restored_client_t *client return ret; } -/** - * Sends the Goodbye request to restored signaling the end of communication. - * - * @param client The restore client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * RESTORE_E_PLIST_ERROR if the device did not acknowledge the request - */ restored_error_t restored_goodbye(restored_client_t client) { if (!client) @@ -364,7 +344,7 @@ restored_error_t restored_goodbye(restored_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Goodbye")); + plist_dict_set_item(dict,"Request", plist_new_string("Goodbye")); debug_info("called"); @@ -387,15 +367,7 @@ restored_error_t restored_goodbye(restored_client_t client) return ret; } -/** - * Requests to start a restore and retrieve it's port on success. - * - * @param client The restored client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG if a parameter - * is NULL, RESTORE_E_START_RESTORE_FAILED if the request fails - */ -restored_error_t restored_start_restore(restored_client_t client) +restored_error_t restored_start_restore(restored_client_t client, plist_t options, uint64_t version) { if (!client) return RESTORE_E_INVALID_ARG; @@ -405,8 +377,11 @@ restored_error_t restored_start_restore(restored_client_t client) dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("StartRestore")); - plist_dict_insert_item(dict,"RestoreProtocolVersion", plist_new_uint(2)); + plist_dict_set_item(dict,"Request", plist_new_string("StartRestore")); + if (options) { + plist_dict_set_item(dict, "RestoreOptions", plist_copy(options)); + } + plist_dict_set_item(dict,"RestoreProtocolVersion", plist_new_uint(version)); /* send to device */ ret = restored_send(client, dict); @@ -416,14 +391,6 @@ restored_error_t restored_start_restore(restored_client_t client) return ret; } -/** - * Requests device to reboot. - * - * @param client The restored client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG if a parameter - * is NULL - */ restored_error_t restored_reboot(restored_client_t client) { if (!client) @@ -434,7 +401,7 @@ restored_error_t restored_reboot(restored_client_t client) dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Reboot")); + plist_dict_set_item(dict,"Request", plist_new_string("Reboot")); /* send to device */ ret = restored_send(client, dict); @@ -455,4 +422,3 @@ restored_error_t restored_reboot(restored_client_t client) dict = NULL; return ret; } - diff --git a/src/restore.h b/src/restore.h index d790d01..ec6fa04 100644 --- a/src/restore.h +++ b/src/restore.h @@ -19,17 +19,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef RESTORED_H -#define RESTORED_H +#ifndef __RESTORED_H +#define __RESTORED_H #include <string.h> +#include "idevice.h" #include "libimobiledevice/restore.h" #include "property_list_service.h" struct restored_client_private { property_list_service_client_t parent; - char *uuid; + char *udid; char *label; plist_t info; }; diff --git a/src/reverse_proxy.c b/src/reverse_proxy.c new file mode 100644 index 0000000..2fcfdd1 --- /dev/null +++ b/src/reverse_proxy.c @@ -0,0 +1,810 @@ +/* + * reverse_proxy.c + * com.apple.PurpleReverseProxy service implementation. + * + * Copyright (c) 2021 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2014 BALATON Zoltan. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include <stdio.h> +#include <errno.h> + +#include <plist/plist.h> +#include <libimobiledevice-glue/thread.h> +#include <libimobiledevice-glue/socket.h> + +#include "reverse_proxy.h" +#include "lockdown.h" +#include "common/debug.h" +#include "endianness.h" +#include "asprintf.h" + +#ifndef ECONNRESET +#define ECONNRESET 108 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 138 +#endif + +#define CTRL_PORT 1082 +#define CTRLCMD "BeginCtrl" +#define HELLOCTRLCMD "HelloCtrl" +#define HELLOCMD "HelloConn" + +#define RP_SYNC_MSG 0x1 +#define RP_PROXY_MSG 0x105 +#define RP_PLIST_MSG 0xbbaa + +/** + * Convert a service_error_t value to a reverse_proxy_error_t value. + * Used internally to get correct error codes. + * + * @param err A service_error_t error code + * + * @return A matching reverse_proxy_error_t error code, + * REVERSE_PROXY_E_UNKNOWN_ERROR otherwise. + */ +static reverse_proxy_error_t reverse_proxy_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return REVERSE_PROXY_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return REVERSE_PROXY_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return REVERSE_PROXY_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return REVERSE_PROXY_E_SSL_ERROR; + case SERVICE_E_NOT_ENOUGH_DATA: + return REVERSE_PROXY_E_NOT_ENOUGH_DATA; + case SERVICE_E_TIMEOUT: + return REVERSE_PROXY_E_TIMEOUT; + default: + break; + } + return REVERSE_PROXY_E_UNKNOWN_ERROR; +} + +static void _reverse_proxy_log(reverse_proxy_client_t client, const char* format, ...) +{ + if (!client || !client->log_cb) { + return; + } + va_list args; + va_start(args, format); + char* buffer = NULL; + if(vasprintf(&buffer, format, args)<0){} + va_end(args); + client->log_cb(client, buffer, client->log_cb_user_data); + free(buffer); +} + +static void _reverse_proxy_data(reverse_proxy_client_t client, int direction, char* buffer, uint32_t length) +{ + if (!client || !client->data_cb) { + return; + } + client->data_cb(client, direction, buffer, length, client->data_cb_user_data); +} + +static void _reverse_proxy_status(reverse_proxy_client_t client, int status, const char* format, ...) +{ + if (!client || !client->status_cb) { + return; + } + va_list args; + va_start(args, format); + char* buffer = NULL; + if(vasprintf(&buffer, format, args)<0){} + va_end(args); + client->status_cb(client, status, buffer, client->status_cb_user_data); + free(buffer); +} + +static int _reverse_proxy_handle_proxy_cmd(reverse_proxy_client_t client) +{ + reverse_proxy_error_t err = REVERSE_PROXY_E_SUCCESS; + char *buf = NULL; + size_t bufsize = 1048576; + uint32_t sent = 0, bytes = 0; + uint32_t sent_total = 0; + uint32_t recv_total = 0; + char *host = NULL; + uint16_t port = 0; + + buf = malloc(bufsize); + if (!buf) { + _reverse_proxy_log(client, "ERROR: Failed to allocate buffer"); + return -1; + } + + err = reverse_proxy_receive(client, buf, bufsize, &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + free(buf); + _reverse_proxy_log(client, "ERROR: Unable to read data for proxy command"); + return -1; + } + _reverse_proxy_log(client, "Handling proxy command"); + + /* Just return success here unconditionally because we don't know + * anything else and we will eventually abort on failure anyway */ + uint16_t ack = 5; + err = reverse_proxy_send(client, (char *)&ack, sizeof(ack), &sent); + if (err != REVERSE_PROXY_E_SUCCESS || sent != sizeof(ack)) { + free(buf); + _reverse_proxy_log(client, "ERROR: Unable to send ack. Sent %u of %u bytes.", sent, (uint32_t)sizeof(ack)); + return -1; + } + + if (bytes < 3) { + free(buf); + _reverse_proxy_log(client, "Proxy command data too short, retrying"); + return 0; + } + + /* ack command data too */ + err = reverse_proxy_send(client, buf, bytes, &sent); + if (err != REVERSE_PROXY_E_SUCCESS || sent != bytes) { + free(buf); + _reverse_proxy_log(client, "ERROR: Unable to send data. Sent %u of %u bytes.", sent, bytes); + return -1; + } + + /* Now try to handle actual messages */ + /* Connect: 0 3 hostlen <host> <port> */ + if (buf[0] == 0 && buf[1] == 3) { + uint16_t *p = (uint16_t *)&buf[bytes - 2]; + port = be16toh(*p); + buf[bytes - 2] = '\0'; + host = strdup(&buf[3]); + _reverse_proxy_log(client, "Connect request to %s:%u", host, port); + } + + if (!host || !buf[2]) { + /* missing or zero length host name */ + free(buf); + return 0; + } + + /* else wait for messages and forward them */ + int sockfd = socket_connect(host, port); + if (sockfd < 0) { + free(buf); + _reverse_proxy_log(client, "ERROR: Connection to %s:%u failed: %s", host, port, strerror(errno)); + free(host); + return -1; + } + + _reverse_proxy_status(client, RP_STATUS_CONNECTED, "Connected to %s:%u", host, port); + + int res = 0, bytes_ret; + while (1) { + bytes = 0; + err = reverse_proxy_receive_with_timeout(client, buf, bufsize, &bytes, 100); + if (err == REVERSE_PROXY_E_TIMEOUT || (err == REVERSE_PROXY_E_SUCCESS && !bytes)) { + /* just a timeout condition */ + } + else if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "Connection closed"); + res = -1; + break; + } + if (bytes) { + _reverse_proxy_log(client, "Proxying %u bytes of data", bytes); + _reverse_proxy_data(client, RP_DATA_DIRECTION_OUT, buf, bytes); + sent = 0; + while (sent < bytes) { + int s = socket_send(sockfd, buf + sent, bytes - sent); + if (s < 0) { + break; + } + sent += s; + } + sent_total += sent; + if (sent != bytes) { + _reverse_proxy_log(client, "ERROR: Sending proxy payload failed: %s. Sent %u of %u bytes.", strerror(errno), sent, bytes); + socket_close(sockfd); + res = -1; + break; + } + } + bytes_ret = socket_receive_timeout(sockfd, buf, bufsize, 0, 100); + if (bytes_ret == -ETIMEDOUT) { + bytes_ret = 0; + } else if (bytes_ret == -ECONNRESET) { + res = 1; + break; + } else if (bytes_ret < 0) { + _reverse_proxy_log(client, "ERROR: Failed to receive from host: %s", strerror(-bytes_ret)); + break; + } + + bytes = bytes_ret; + if (bytes) { + _reverse_proxy_log(client, "Received %u bytes reply data, sending to device\n", bytes); + _reverse_proxy_data(client, RP_DATA_DIRECTION_IN, buf, bytes); + recv_total += bytes; + sent = 0; + while (sent < bytes) { + uint32_t s; + err = reverse_proxy_send(client, buf + sent, bytes - sent, &s); + if (err != REVERSE_PROXY_E_SUCCESS) { + break; + } + sent += s; + } + if (err != REVERSE_PROXY_E_SUCCESS || bytes != sent) { + _reverse_proxy_log(client, "ERROR: Unable to send data (%d). Sent %u of %u bytes.", err, sent, bytes); + res = -1; + break; + } + } + } + socket_close(sockfd); + free(host); + free(buf); + + _reverse_proxy_status(client, RP_STATUS_DISCONNECTED, "Disconnected (out: %u / in: %u)", sent_total, recv_total); + + return res; +} + +static int _reverse_proxy_handle_plist_cmd(reverse_proxy_client_t client) +{ + plist_t dict; + reverse_proxy_error_t err; + + err = reverse_proxy_receive_plist(client, &dict); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "ERROR: Unable to receive plist command, error", err); + return -1; + } + plist_t node = plist_dict_get_item(dict, "Command"); + if (!node || (plist_get_node_type(node) != PLIST_STRING)) { + _reverse_proxy_log(client, "ERROR: No 'Command' in reply", err); + plist_free(dict); + return -1; + } + char *command = NULL; + plist_get_string_val(node, &command); + plist_free(dict); + + if (!command) { + _reverse_proxy_log(client, "ERROR: Empty 'Command' string"); + return -1; + } + + if (!strcmp(command, "Ping")) { + _reverse_proxy_log(client, "Received Ping command, replying with Pong"); + dict = plist_new_dict(); + plist_dict_set_item(dict, "Pong", plist_new_bool(1)); + err = reverse_proxy_send_plist(client, dict); + plist_free(dict); + if (err) { + _reverse_proxy_log(client, "ERROR: Unable to send Ping command reply"); + free(command); + return -1; + } + } else { + _reverse_proxy_log(client, "WARNING: Received unhandled plist command '%s'", command); + free(command); + return -1; + } + + free(command); + /* reverse proxy connection will be terminated remotely. Next receive will get nothing, error and terminate this worker thread. */ + return 0; +} + +static reverse_proxy_error_t reverse_proxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, reverse_proxy_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + return REVERSE_PROXY_E_INVALID_ARG; + } + + debug_info("Creating reverse_proxy_client, port = %d.", service->port); + + service_client_t sclient = NULL; + reverse_proxy_error_t ret = reverse_proxy_error(service_client_new(device, service, &sclient)); + if (ret != REVERSE_PROXY_E_SUCCESS) { + debug_info("Creating service client failed. Error: %i", ret); + return ret; + } + + reverse_proxy_client_t client_loc = (reverse_proxy_client_t) calloc(1, sizeof(struct reverse_proxy_client_private)); + client_loc->parent = sclient; + client_loc->th_ctrl = THREAD_T_NULL; + *client = client_loc; + + return 0; +} + +static void* _reverse_proxy_connection_thread(void *cdata) +{ + reverse_proxy_client_t client = (reverse_proxy_client_t)cdata; + uint32_t bytes = 0; + reverse_proxy_client_t conn_client = NULL; + reverse_proxy_error_t err = REVERSE_PROXY_E_UNKNOWN_ERROR; + + if (client->conn_port == 0) { + service_client_factory_start_service(client->parent->connection->device, "com.apple.PurpleReverseProxy.Conn", (void**)&conn_client, client->label, SERVICE_CONSTRUCTOR(reverse_proxy_client_new), &err); + if (!conn_client) { + _reverse_proxy_log(client, "ERROR: Failed to start proxy connection service, error %d", err); + } + } else { + struct lockdownd_service_descriptor svc; + svc.port = client->conn_port; + svc.ssl_enabled = 0; + svc.identifier = NULL; + err = reverse_proxy_client_new(client->parent->connection->device, &svc, &conn_client); + if (!conn_client) { + _reverse_proxy_log(client, "ERROR: Failed to connect to proxy connection port %u, error %d", client->conn_port, err); + } + } + if (!conn_client) { + goto leave; + } + conn_client->type = RP_TYPE_CONN; + conn_client->protoversion = client->protoversion; + conn_client->log_cb = client->log_cb; + conn_client->log_cb_user_data = client->log_cb_user_data; + conn_client->status_cb = client->status_cb; + conn_client->status_cb_user_data = client->status_cb_user_data; + + err = reverse_proxy_send(conn_client, HELLOCMD, sizeof(HELLOCMD), &bytes); + if (err != REVERSE_PROXY_E_SUCCESS || bytes != sizeof(HELLOCMD)) { + _reverse_proxy_log(conn_client, "ERROR: Unable to send " HELLOCMD " (sent %u/%u bytes)", bytes, sizeof(HELLOCMD)); + goto leave; + } + + if (conn_client->protoversion == 2) { + plist_t reply = NULL; + err = reverse_proxy_receive_plist(conn_client, &reply); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(conn_client, "ERROR: Did not receive " HELLOCMD " reply, error %d", err); + goto leave; + } + char* identifier = NULL; + char* cmd = NULL; + plist_t node = NULL; + node = plist_dict_get_item(reply, "Command"); + if (node) { + plist_get_string_val(node, &cmd); + } + node = plist_dict_get_item(reply, "Identifier"); + if (node) { + plist_get_string_val(node, &identifier); + } + plist_free(reply); + + if (!cmd || (strcmp(cmd, HELLOCMD) != 0)) { + free(cmd); + free(identifier); + _reverse_proxy_log(conn_client, "ERROR: Unexpected reply to " HELLOCMD " received"); + goto leave; + } + free(cmd); + + if (identifier) { + _reverse_proxy_log(conn_client, "Got device identifier %s", identifier); + free(identifier); + } + } else { + char buf[16]; + memset(buf, '\0', sizeof(buf)); + bytes = 0; + err = reverse_proxy_receive(conn_client, buf, sizeof(HELLOCMD), &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(conn_client, "ERROR: Did not receive " HELLOCMD " reply, error %d", err); + goto leave; + } + if (memcmp(buf, HELLOCMD, sizeof(HELLOCMD)) != 0) { + _reverse_proxy_log(conn_client, "ERROR: Did not receive " HELLOCMD " as reply, but %.*s", (int)bytes, buf); + goto leave; + } + } + + _reverse_proxy_status(conn_client, RP_STATUS_READY, "Ready"); + + int running = 1; + while (client->th_ctrl != THREAD_T_NULL && conn_client && running) { + uint16_t cmd = 0; + bytes = 0; + err = reverse_proxy_receive_with_timeout(conn_client, (char*)&cmd, sizeof(cmd), &bytes, 1000); + if (err == REVERSE_PROXY_E_TIMEOUT || (err == REVERSE_PROXY_E_SUCCESS && bytes != sizeof(cmd))) { + continue; + } else if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(conn_client, "Connection closed"); + break; + } + cmd = le16toh(cmd); + switch (cmd) { + case 0xBBAA: + /* plist command */ + if (_reverse_proxy_handle_plist_cmd(conn_client) < 0) { + running = 0; + } + break; + case 0x105: + /* proxy command */ + if (_reverse_proxy_handle_proxy_cmd(conn_client) < 0) { + running = 0; + } + break; + default: + /* unknown */ + debug_info("ERROR: Unknown request 0x%x", cmd); + _reverse_proxy_log(conn_client, "ERROR: Unknown request 0x%x", cmd); + running = 0; + break; + } + } + +leave: + _reverse_proxy_status(conn_client, RP_STATUS_TERMINATE, "Terminated"); + if (conn_client) { + reverse_proxy_client_free(conn_client); + } + + return NULL; +} + +static void* _reverse_proxy_control_thread(void *cdata) +{ + reverse_proxy_client_t client = (reverse_proxy_client_t)cdata; + THREAD_T th_conn = THREAD_T_NULL; + int running = 1; + _reverse_proxy_status(client, RP_STATUS_READY, "Ready"); + while (client && client->parent && running) { + uint32_t cmd = 0; + uint32_t bytes = 0; + reverse_proxy_error_t err = reverse_proxy_receive_with_timeout(client, (char*)&cmd, sizeof(cmd), &bytes, 1000); + if (err == REVERSE_PROXY_E_TIMEOUT || (err == REVERSE_PROXY_E_SUCCESS && bytes != sizeof(cmd))) { + continue; + } else if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "Connection closed"); + break; + } + cmd = le32toh(cmd); + switch (cmd) { + case 1: + /* connection request */ + debug_info("ReverseProxy<%p> got connect request", client); + _reverse_proxy_status(client, RP_STATUS_CONNECT_REQ, "Connect Request"); + if (thread_new(&th_conn, _reverse_proxy_connection_thread, client) != 0) { + debug_info("ERROR: Failed to start connection thread"); + th_conn = THREAD_T_NULL; + running = 0; + } + break; + case 2: + /* shutdown request */ + debug_info("ReverseProxy<%p> got shutdown request", client); + _reverse_proxy_status(client, RP_STATUS_SHUTDOWN_REQ, "Shutdown Request"); + running = 0; + break; + default: + /* unknown */ + debug_info("ERROR: Unknown request 0x%x", cmd); + _reverse_proxy_log(client, "ERROR: Unknown request 0x%x", cmd); + running = 0; + break; + } + } + _reverse_proxy_log(client, "Terminating"); + + client->th_ctrl = THREAD_T_NULL; + if (th_conn) { + debug_info("joining connection thread"); + thread_join(th_conn); + thread_free(th_conn); + } + + _reverse_proxy_status(client, RP_STATUS_TERMINATE, "Terminated"); + + return NULL; +} + +reverse_proxy_error_t reverse_proxy_client_start_proxy(reverse_proxy_client_t client, int control_protocol_version) +{ + char buf[16] = {0, }; + uint32_t bytes = 0; + reverse_proxy_error_t err = REVERSE_PROXY_E_UNKNOWN_ERROR; + + if (!client) { + return REVERSE_PROXY_E_INVALID_ARG; + } + if (control_protocol_version < 1 || control_protocol_version > 2) { + debug_info("invalid protocol version %d, must be 1 or 2", control_protocol_version); + return REVERSE_PROXY_E_INVALID_ARG; + } + + if (control_protocol_version == 2) { + err = reverse_proxy_send(client, CTRLCMD, sizeof(CTRLCMD), &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "ERROR: Failed to send " CTRLCMD " to device, error %d", err); + return err; + } + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string(CTRLCMD)); + plist_dict_set_item(dict, "CtrlProtoVersion", plist_new_uint(client->protoversion)); + err = reverse_proxy_send_plist(client, dict); + plist_free(dict); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "ERROR: Could not send " CTRLCMD " plist command, error %d", err); + return err; + } + dict = NULL; + err = reverse_proxy_receive_plist(client, &dict); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "ERROR: Could not receive " CTRLCMD " plist reply, error %d", err); + return err; + } + plist_t node = plist_dict_get_item(dict, "ConnPort"); + if (node && plist_get_node_type(node) == PLIST_UINT) { + uint64_t u64val = 0; + plist_get_uint_val(node, &u64val); + client->conn_port = (uint16_t)u64val; + } else { + _reverse_proxy_log(client, "ERROR: Could not get ConnPort value"); + return REVERSE_PROXY_E_UNKNOWN_ERROR; + } + client->protoversion = 2; + } else { + err = reverse_proxy_send(client, HELLOCTRLCMD, sizeof(HELLOCTRLCMD), &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "ERROR: Failed to send " HELLOCTRLCMD " to device, error %d", err); + return err; + } + + bytes = 0; + err = reverse_proxy_receive(client, buf, sizeof(HELLOCTRLCMD)-1, &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "ERROR: Could not receive " HELLOCTRLCMD " reply, error %d", err); + return err; + } + + uint16_t cport = 0; + bytes = 0; + err = reverse_proxy_receive(client, (char*)&cport, 2, &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + _reverse_proxy_log(client, "ERROR: Failed to receive connection port, error %d", err); + return err; + } + client->conn_port = le16toh(cport); + client->protoversion = 1; + } + + if (thread_new(&(client->th_ctrl), _reverse_proxy_control_thread, client) != 0) { + _reverse_proxy_log(client, "ERROR: Failed to start control thread"); + client->th_ctrl = THREAD_T_NULL; /* undefined after failure */ + err = REVERSE_PROXY_E_UNKNOWN_ERROR; + } + + return err; +} + +reverse_proxy_error_t reverse_proxy_client_create_with_service(idevice_t device, reverse_proxy_client_t* client, const char* label) +{ + reverse_proxy_error_t err = REVERSE_PROXY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, "com.apple.PurpleReverseProxy.Ctrl", (void**)client, label, SERVICE_CONSTRUCTOR(reverse_proxy_client_new), &err); + if (!*client) { + return err; + } + (*client)->label = strdup(label); + (*client)->type = RP_TYPE_CTRL; + + return REVERSE_PROXY_E_SUCCESS; +} + +reverse_proxy_error_t reverse_proxy_client_create_with_port(idevice_t device, reverse_proxy_client_t* client, uint16_t device_port) +{ + reverse_proxy_client_t client_loc = NULL; + reverse_proxy_error_t err; + + struct lockdownd_service_descriptor svc; + svc.port = device_port; + svc.ssl_enabled = 0; + svc.identifier = NULL; + + err = reverse_proxy_client_new(device, &svc, &client_loc); + if (err != REVERSE_PROXY_E_SUCCESS) { + return err; + } + + client_loc->type = RP_TYPE_CTRL; + *client = client_loc; + + return REVERSE_PROXY_E_SUCCESS; +} + +reverse_proxy_error_t reverse_proxy_client_free(reverse_proxy_client_t client) +{ + if (!client) + return REVERSE_PROXY_E_INVALID_ARG; + service_client_t parent = client->parent; + client->parent = NULL; + if (client->th_ctrl) { + debug_info("joining control thread"); + thread_join(client->th_ctrl); + thread_free(client->th_ctrl); + client->th_ctrl = THREAD_T_NULL; + } + reverse_proxy_error_t err = reverse_proxy_error(service_client_free(parent)); + free(client->label); + free(client); + + return err; +} + +reverse_proxy_client_type_t reverse_proxy_get_type(reverse_proxy_client_t client) +{ + if (!client) + return 0; + return client->type; +} + +void reverse_proxy_client_set_status_callback(reverse_proxy_client_t client, reverse_proxy_status_cb_t status_callback, void* user_data) +{ + if (!client) { + return; + } + client->status_cb = status_callback; + client->status_cb_user_data = user_data; +} + +void reverse_proxy_client_set_log_callback(reverse_proxy_client_t client, reverse_proxy_log_cb_t log_callback, void* user_data) +{ + if (!client) { + return; + } + client->log_cb = log_callback; + client->log_cb_user_data = user_data; +} + +void reverse_proxy_client_set_data_callback(reverse_proxy_client_t client, reverse_proxy_data_cb_t data_callback, void* user_data) +{ + if (!client) { + return; + } + client->data_cb = data_callback; + client->data_cb_user_data = user_data; +} + +reverse_proxy_error_t reverse_proxy_send(reverse_proxy_client_t client, const char* data, uint32_t len, uint32_t* sent) +{ + reverse_proxy_error_t err = reverse_proxy_error(service_send(client->parent, data, len, sent)); + return err; +} + +reverse_proxy_error_t reverse_proxy_receive_with_timeout(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received, unsigned int timeout) +{ + if (!client) + return REVERSE_PROXY_E_INVALID_ARG; + return reverse_proxy_error(service_receive_with_timeout(client->parent, buffer, len, received, timeout)); +} + +reverse_proxy_error_t reverse_proxy_receive(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received) +{ + return reverse_proxy_receive_with_timeout(client, buffer, len, received, 20000); +} + +reverse_proxy_error_t reverse_proxy_send_plist(reverse_proxy_client_t client, plist_t plist) +{ + reverse_proxy_error_t err; + uint32_t len = 0; + char* buf = NULL; + uint32_t bytes = 0; + + plist_to_bin(plist, &buf, &len); + + if (!buf) { + return REVERSE_PROXY_E_INVALID_ARG; + } + + debug_info("Sending %u bytes", len); + + uint32_t slen = htole32(len); + err = reverse_proxy_send(client, (char*)&slen, sizeof(slen), &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + free(buf); + debug_info("ERROR: Unable to send data length, error %d. Sent %u/%u bytes.", err, bytes, (uint32_t)sizeof(slen)); + return err; + } + uint32_t done = 0; + do { + bytes = 0; + err = reverse_proxy_send(client, buf+done, len-done, &bytes); + if (err != REVERSE_PROXY_E_SUCCESS) { + break; + } + done += bytes; + } while (done < len); + free(buf); + if (err != REVERSE_PROXY_E_SUCCESS || done != len) { + debug_info("ERROR: Unable to send data, error %d. Sent %u/%u bytes.", err, done, len); + return err; + } + + debug_info("Sent %u bytes", len); + + return REVERSE_PROXY_E_SUCCESS; +} + +reverse_proxy_error_t reverse_proxy_receive_plist(reverse_proxy_client_t client, plist_t* plist) +{ + return reverse_proxy_receive_plist_with_timeout(client, plist, 20000); +} + +reverse_proxy_error_t reverse_proxy_receive_plist_with_timeout(reverse_proxy_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + uint32_t len; + uint32_t bytes; + reverse_proxy_error_t err; + + err = reverse_proxy_receive_with_timeout(client, (char*)&len, sizeof(len), &bytes, timeout_ms); + if (err != REVERSE_PROXY_E_SUCCESS) { + if (err != REVERSE_PROXY_E_TIMEOUT) { + debug_info("ERROR: Unable to receive packet length, error %d\n", err); + } + return err; + } + + len = le32toh(len); + char* buf = calloc(1, len); + if (!buf) { + debug_info("ERROR: Out of memory"); + return REVERSE_PROXY_E_UNKNOWN_ERROR; + } + + uint32_t done = 0; + do { + bytes = 0; + err = reverse_proxy_receive_with_timeout(client, buf+done, len-done, &bytes, timeout_ms); + if (err != REVERSE_PROXY_E_SUCCESS) { + break; + } + done += bytes; + } while (done < len); + + if (err != REVERSE_PROXY_E_SUCCESS || done != len) { + free(buf); + debug_info("ERROR: Unable to receive data, error %d. Received %u/%u bytes.", err, done, len); + return err; + } + + debug_info("Received %u bytes", len); + + plist_from_bin(buf, len, plist); + free(buf); + + if (!(*plist)) { + debug_info("ERROR: Failed to convert buffer to plist"); + return REVERSE_PROXY_E_PLIST_ERROR; + } + + return REVERSE_PROXY_E_SUCCESS; +} diff --git a/src/reverse_proxy.h b/src/reverse_proxy.h new file mode 100644 index 0000000..7f441bd --- /dev/null +++ b/src/reverse_proxy.h @@ -0,0 +1,51 @@ +/* + * reverse_proxy.h + * com.apple.PurpleReverseProxy service header file. + * + * Copyright (c) 2021 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __REVERSE_PROXY_H +#define __REVERSE_PROXY_H + +#include "idevice.h" +#include "libimobiledevice/reverse_proxy.h" +#include "service.h" + +struct reverse_proxy_client_private { + service_client_t parent; + char* label; + int type; + int protoversion; + THREAD_T th_ctrl; + uint16_t conn_port; + reverse_proxy_log_cb_t log_cb; + void* log_cb_user_data; + reverse_proxy_data_cb_t data_cb; + void* data_cb_user_data; + reverse_proxy_status_cb_t status_cb; + void* status_cb_user_data; +}; + +reverse_proxy_error_t reverse_proxy_send(reverse_proxy_client_t client, const char* data, uint32_t len, uint32_t* sent); +reverse_proxy_error_t reverse_proxy_receive(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received); +reverse_proxy_error_t reverse_proxy_receive_with_timeout(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received, unsigned int timeout); +reverse_proxy_error_t reverse_proxy_send_plist(reverse_proxy_client_t client, plist_t plist); +reverse_proxy_error_t reverse_proxy_receive_plist(reverse_proxy_client_t client, plist_t* plist); +reverse_proxy_error_t reverse_proxy_receive_plist_with_timeout(reverse_proxy_client_t client, plist_t * plist, uint32_t timeout_ms); + +#endif diff --git a/src/sbservices.c b/src/sbservices.c index 3596cbd..365e130 100644 --- a/src/sbservices.c +++ b/src/sbservices.c @@ -8,17 +8,20 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> #include <unistd.h> @@ -26,28 +29,28 @@ #include "sbservices.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" /** * Locks an sbservices client, used for thread safety. * * @param client sbservices client to lock. */ -static void sbs_lock(sbservices_client_t client) +static void sbservices_lock(sbservices_client_t client) { - debug_info("SBServices: Locked"); - g_mutex_lock(client->mutex); + debug_info("Locked"); + mutex_lock(&client->mutex); } /** * Unlocks an sbservices client, used for thread safety. - * + * * @param client sbservices client to unlock */ -static void sbs_unlock(sbservices_client_t client) +static void sbservices_unlock(sbservices_client_t client) { - debug_info("SBServices: Unlocked"); - g_mutex_unlock(client->mutex); + debug_info("Unlocked"); + mutex_unlock(&client->mutex); } /** @@ -61,64 +64,44 @@ static void sbs_unlock(sbservices_client_t client) */ static sbservices_error_t sbservices_error(property_list_service_error_t err) { - switch (err) { - case PROPERTY_LIST_SERVICE_E_SUCCESS: - return SBSERVICES_E_SUCCESS; - case PROPERTY_LIST_SERVICE_E_INVALID_ARG: - return SBSERVICES_E_INVALID_ARG; - case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: - return SBSERVICES_E_PLIST_ERROR; - case PROPERTY_LIST_SERVICE_E_MUX_ERROR: - return SBSERVICES_E_CONN_FAILED; - default: - break; - } - return SBSERVICES_E_UNKNOWN_ERROR; + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return SBSERVICES_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return SBSERVICES_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return SBSERVICES_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return SBSERVICES_E_CONN_FAILED; + default: + break; + } + return SBSERVICES_E_UNKNOWN_ERROR; } -/** - * Connects to the springboardservices service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will point to a newly allocated - * sbservices_client_t upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client is NULL, or an SBSERVICES_E_* error code otherwise. - */ -sbservices_error_t sbservices_client_new(idevice_t device, uint16_t port, sbservices_client_t *client) +sbservices_error_t sbservices_client_new(idevice_t device, lockdownd_service_descriptor_t service, sbservices_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return SBSERVICES_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - sbservices_error_t err = sbservices_error(property_list_service_client_new(device, port, &plistclient)); + sbservices_error_t err = sbservices_error(property_list_service_client_new(device, service, &plistclient)); if (err != SBSERVICES_E_SUCCESS) { return err; } sbservices_client_t client_loc = (sbservices_client_t) malloc(sizeof(struct sbservices_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); + mutex_init(&client_loc->mutex); *client = client_loc; return SBSERVICES_E_SUCCESS; } -/** - * Disconnects an sbservices client from the device and frees up the - * sbservices client data. - * - * @param client The sbservices client to disconnect and free. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client is NULL, or an SBSERVICES_E_* error code otherwise. - */ +sbservices_error_t sbservices_client_start_service(idevice_t device, sbservices_client_t * client, const char* label) +{ + sbservices_error_t err = SBSERVICES_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, SBSERVICES_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(sbservices_client_new), &err); + return err; +} + sbservices_error_t sbservices_client_free(sbservices_client_t client) { if (!client) @@ -126,28 +109,12 @@ sbservices_error_t sbservices_client_free(sbservices_client_t client) sbservices_error_t err = sbservices_error(property_list_service_client_free(client->parent)); client->parent = NULL; - if (client->mutex) { - g_mutex_free(client->mutex); - } + mutex_destroy(&client->mutex); free(client); return err; } -/** - * Gets the icon state of the connected device. - * - * @param client The connected sbservices client to use. - * @param state Pointer that will point to a newly allocated plist containing - * the current icon state. It is up to the caller to free the memory. - * @param format_version A string to be passed as formatVersion along with - * the request, or NULL if no formatVersion should be passed. This is only - * supported since iOS 4.0 so for older firmware versions this must be set - * to NULL. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or state is invalid, or an SBSERVICES_E_* error code otherwise. - */ sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t *state, const char *format_version) { if (!client || !client->parent || !state) @@ -156,12 +123,12 @@ sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("getIconState")); + plist_dict_set_item(dict, "command", plist_new_string("getIconState")); if (format_version) { - plist_dict_insert_item(dict, "formatVersion", plist_new_string(format_version)); + plist_dict_set_item(dict, "formatVersion", plist_new_string(format_version)); } - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { @@ -184,19 +151,10 @@ leave_unlock: if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; } -/** - * Sets the icon state of the connected device. - * - * @param client The connected sbservices client to use. - * @param newstate A plist containing the new iconstate. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or newstate is NULL, or an SBSERVICES_E_* error code otherwise. - */ sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t newstate) { if (!client || !client->parent || !newstate) @@ -205,39 +163,27 @@ sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("setIconState")); - plist_dict_insert_item(dict, "iconState", plist_copy(newstate)); + plist_dict_set_item(dict, "command", plist_new_string("setIconState")); + plist_dict_set_item(dict, "iconState", plist_copy(newstate)); - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { debug_info("could not send plist, error %d", res); } - /* NO RESPONSE */ + + uint32_t bytes = 0; + service_receive_with_timeout(client->parent->parent, malloc(4), 4, &bytes, 2000); + debug_info("setIconState response: %u", bytes); if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; } -/** - * Get the icon of the specified app as PNG data. - * - * @param client The connected sbservices client to use. - * @param bundleId The bundle identifier of the app to retrieve the icon for. - * @param pngdata Pointer that will point to a newly allocated buffer - * containing the PNG data upon successful return. It is up to the caller - * to free the memory. - * @param pngsize Pointer to a uint64_t that will be set to the size of the - * buffer pngdata points to upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client, bundleId, or pngdata are invalid, or an SBSERVICES_E_* error - * code otherwise. - */ sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const char *bundleId, char **pngdata, uint64_t *pngsize) { if (!client || !client->parent || !bundleId || !pngdata) @@ -246,10 +192,10 @@ sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("getIconPNGData")); - plist_dict_insert_item(dict, "bundleId", plist_new_string(bundleId)); + plist_dict_set_item(dict, "command", plist_new_string("getIconPNGData")); + plist_dict_set_item(dict, "bundleId", plist_new_string(bundleId)); - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { @@ -271,25 +217,48 @@ leave_unlock: if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; +} + +sbservices_error_t sbservices_get_interface_orientation(sbservices_client_t client, sbservices_interface_orientation_t* interface_orientation) +{ + if (!client || !client->parent || !interface_orientation) + return SBSERVICES_E_INVALID_ARG; + + sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "command", plist_new_string("getInterfaceOrientation")); + + sbservices_lock(client); + + res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); + if (res != SBSERVICES_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + goto leave_unlock; + } + plist_free(dict); + dict = NULL; + + res = sbservices_error(property_list_service_receive_plist(client->parent, &dict)); + if (res == SBSERVICES_E_SUCCESS) { + plist_t node = plist_dict_get_item(dict, "interfaceOrientation"); + if (node) { + uint64_t value = SBSERVICES_INTERFACE_ORIENTATION_UNKNOWN; + plist_get_uint_val(node, &value); + *interface_orientation = (sbservices_interface_orientation_t)value; + } + } +leave_unlock: + if (dict) { + plist_free(dict); + } + sbservices_unlock(client); + return res; } -/** - * Get the home screen wallpaper as PNG data. - * - * @param client The connected sbservices client to use. - * @param pngdata Pointer that will point to a newly allocated buffer - * containing the PNG data upon successful return. It is up to the caller - * to free the memory. - * @param pngsize Pointer to a uint64_t that will be set to the size of the - * buffer pngdata points to upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or pngdata are invalid, or an SBSERVICES_E_* error - * code otherwise. - */ sbservices_error_t sbservices_get_home_screen_wallpaper_pngdata(sbservices_client_t client, char **pngdata, uint64_t *pngsize) { if (!client || !client->parent || !pngdata) @@ -298,9 +267,9 @@ sbservices_error_t sbservices_get_home_screen_wallpaper_pngdata(sbservices_clien sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("getHomeScreenWallpaperPNGData")); + plist_dict_set_item(dict, "command", plist_new_string("getHomeScreenWallpaperPNGData")); - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { @@ -322,6 +291,6 @@ leave_unlock: if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; } diff --git a/src/sbservices.h b/src/sbservices.h index 3a4120f..b67281e 100644 --- a/src/sbservices.h +++ b/src/sbservices.h @@ -8,27 +8,28 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef ISBSERVICES_H -#define ISBSERVICES_H -#include <glib.h> +#ifndef __SBSERVICES_H +#define __SBSERVICES_H +#include "idevice.h" #include "libimobiledevice/sbservices.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct sbservices_client_private { property_list_service_client_t parent; - GMutex *mutex; + mutex_t mutex; }; #endif diff --git a/src/screenshotr.c b/src/screenshotr.c index 0c4c9b2..c3cc9ba 100644 --- a/src/screenshotr.c +++ b/src/screenshotr.c @@ -1,33 +1,36 @@ /* - * screenshotr.c + * screenshotr.c * com.apple.mobile.screenshotr service implementation. - * - * Copyright (c) 2010 Nikias Bassen All Rights Reserved. + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <plist/plist.h> #include <string.h> #include <stdlib.h> #include "screenshotr.h" #include "device_link_service.h" -#include "debug.h" +#include "common/debug.h" -#define SCREENSHOTR_VERSION_INT1 100 +#define SCREENSHOTR_VERSION_INT1 400 #define SCREENSHOTR_VERSION_INT2 0 /** @@ -50,6 +53,10 @@ static screenshotr_error_t screenshotr_error(device_link_service_error_t err) return SCREENSHOTR_E_PLIST_ERROR; case DEVICE_LINK_SERVICE_E_MUX_ERROR: return SCREENSHOTR_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return SCREENSHOTR_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return SCREENSHOTR_E_RECEIVE_TIMEOUT; case DEVICE_LINK_SERVICE_E_BAD_VERSION: return SCREENSHOTR_E_BAD_VERSION; default: @@ -58,29 +65,14 @@ static screenshotr_error_t screenshotr_error(device_link_service_error_t err) return SCREENSHOTR_E_UNKNOWN_ERROR; } -/** - * Connects to the screenshotr service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * screenshotr_client_t upon successful return. - * - * @note This service is only available if a developer disk image has been - * mounted. - * - * @return SCREENSHOTR_E_SUCCESS on success, SCREENSHOTR_E_INVALID ARG if one - * or more parameters are invalid, or SCREENSHOTR_E_CONN_FAILED if the - * connection to the device could not be established. - */ -screenshotr_error_t screenshotr_client_new(idevice_t device, uint16_t port, +screenshotr_error_t screenshotr_client_new(idevice_t device, lockdownd_service_descriptor_t service, screenshotr_client_t * client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return SCREENSHOTR_E_INVALID_ARG; device_link_service_client_t dlclient = NULL; - screenshotr_error_t ret = screenshotr_error(device_link_service_client_new(device, port, &dlclient)); + screenshotr_error_t ret = screenshotr_error(device_link_service_client_new(device, service, &dlclient)); if (ret != SCREENSHOTR_E_SUCCESS) { return ret; } @@ -101,39 +93,23 @@ screenshotr_error_t screenshotr_client_new(idevice_t device, uint16_t port, return ret; } -/** - * Disconnects a screenshotr client from the device and frees up the - * screenshotr client data. - * - * @param client The screenshotr client to disconnect and free. - * - * @return SCREENSHOTR_E_SUCCESS on success, or SCREENSHOTR_E_INVALID_ARG - * if client is NULL. - */ +screenshotr_error_t screenshotr_client_start_service(idevice_t device, screenshotr_client_t * client, const char* label) +{ + screenshotr_error_t err = SCREENSHOTR_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, SCREENSHOTR_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(screenshotr_client_new), &err); + return err; +} + screenshotr_error_t screenshotr_client_free(screenshotr_client_t client) { if (!client) return SCREENSHOTR_E_INVALID_ARG; - device_link_service_disconnect(client->parent); + device_link_service_disconnect(client->parent, NULL); screenshotr_error_t err = screenshotr_error(device_link_service_client_free(client->parent)); free(client); return err; } -/** - * Get a screen shot from the connected device. - * - * @param client The connection screenshotr service client. - * @param imgdata Pointer that will point to a newly allocated buffer - * containing TIFF image data upon successful return. It is up to the - * caller to free the memory. - * @param imgsize Pointer to a uint64_t that will be set to the size of the - * buffer imgdata points to upon successful return. - * - * @return SCREENSHOTR_E_SUCCESS on success, SCREENSHOTR_E_INVALID_ARG if - * one or more parameters are invalid, or another error code if an - * error occured. - */ screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, char **imgdata, uint64_t *imgsize) { if (!client || !client->parent || !imgdata) @@ -142,7 +118,7 @@ screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, cha screenshotr_error_t res = SCREENSHOTR_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "MessageType", plist_new_string("ScreenShotRequest")); + plist_dict_set_item(dict, "MessageType", plist_new_string("ScreenShotRequest")); res = screenshotr_error(device_link_service_send_process_message(client->parent, dict)); plist_free(dict); @@ -166,7 +142,7 @@ screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, cha plist_t node = plist_dict_get_item(dict, "MessageType"); char *strval = NULL; plist_get_string_val(node, &strval); - if (!strval || strcmp(strval, "ScreenShotReply")) { + if (!strval || strcmp(strval, "ScreenShotReply") != 0) { debug_info("invalid screenshot data received!"); res = SCREENSHOTR_E_PLIST_ERROR; goto leave; diff --git a/src/screenshotr.h b/src/screenshotr.h index df6774a..1319ec0 100644 --- a/src/screenshotr.h +++ b/src/screenshotr.h @@ -1,26 +1,28 @@ /* * screenshotr.h * com.apple.mobile.screenshotr service header file. - * + * * Copyright (c) 2010 Nikias Bassen All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef SCREENSHOTR_H -#define SCREENSHOTR_H +#ifndef __SCREENSHOTR_H +#define __SCREENSHOTR_H + +#include "idevice.h" #include "libimobiledevice/screenshotr.h" #include "device_link_service.h" diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..9474021 --- /dev/null +++ b/src/service.c @@ -0,0 +1,207 @@ +/* + * service.c + * generic service implementation. + * + * Copyright (c) 2013 Nikias Bassen. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdlib.h> +#include <string.h> + +#include "service.h" +#include "idevice.h" +#include "common/debug.h" + +/** + * Convert an idevice_error_t value to an service_error_t value. + * Used internally to get correct error codes. + * + * @param err An idevice_error_t error code + * + * @return A matching service_error_t error code, + * SERVICE_E_UNKNOWN_ERROR otherwise. + */ +static service_error_t idevice_to_service_error(idevice_error_t err) +{ + switch (err) { + case IDEVICE_E_SUCCESS: + return SERVICE_E_SUCCESS; + case IDEVICE_E_INVALID_ARG: + return SERVICE_E_INVALID_ARG; + case IDEVICE_E_SSL_ERROR: + return SERVICE_E_SSL_ERROR; + case IDEVICE_E_NOT_ENOUGH_DATA: + return SERVICE_E_NOT_ENOUGH_DATA; + case IDEVICE_E_TIMEOUT: + return SERVICE_E_TIMEOUT; + default: + break; + } + return SERVICE_E_UNKNOWN_ERROR; +} + +service_error_t service_client_new(idevice_t device, lockdownd_service_descriptor_t service, service_client_t *client) +{ + if (!device || !service || service->port == 0 || !client || *client) + return SERVICE_E_INVALID_ARG; + + /* Attempt connection */ + idevice_connection_t connection = NULL; + if (idevice_connect(device, service->port, &connection) != IDEVICE_E_SUCCESS) { + return SERVICE_E_MUX_ERROR; + } + + /* create client object */ + service_client_t client_loc = (service_client_t)malloc(sizeof(struct service_client_private)); + client_loc->connection = connection; + + /* enable SSL if requested */ + if (service->ssl_enabled == 1) + service_enable_ssl(client_loc); + + /* all done, return success */ + *client = client_loc; + return SERVICE_E_SUCCESS; +} + +service_error_t service_client_factory_start_service(idevice_t device, const char* service_name, void **client, const char* label, int32_t (*constructor_func)(idevice_t, lockdownd_service_descriptor_t, void**), int32_t *error_code) +{ + *client = NULL; + + lockdownd_client_t lckd = NULL; + if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lckd, label)) { + debug_info("Could not create a lockdown client."); + return SERVICE_E_START_SERVICE_ERROR; + } + + lockdownd_service_descriptor_t service = NULL; + lockdownd_error_t lerr = lockdownd_start_service(lckd, service_name, &service); + lockdownd_client_free(lckd); + + if (lerr != LOCKDOWN_E_SUCCESS) { + debug_info("Could not start service %s: %s", service_name, lockdownd_strerror(lerr)); + return SERVICE_E_START_SERVICE_ERROR; + } + + int32_t ec; + if (constructor_func) { + ec = (int32_t)constructor_func(device, service, client); + } else { + ec = service_client_new(device, service, (service_client_t*)client); + } + if (error_code) { + *error_code = ec; + } + + if (ec != SERVICE_E_SUCCESS) { + debug_info("Could not connect to service %s! Port: %i, error: %i", service_name, service->port, ec); + } + + lockdownd_service_descriptor_free(service); + service = NULL; + + return (ec == SERVICE_E_SUCCESS) ? SERVICE_E_SUCCESS : SERVICE_E_START_SERVICE_ERROR; +} + +service_error_t service_client_free(service_client_t client) +{ + if (!client) + return SERVICE_E_INVALID_ARG; + + service_error_t err = idevice_to_service_error(idevice_disconnect(client->connection)); + + free(client); + client = NULL; + + return err; +} + +service_error_t service_send(service_client_t client, const char* data, uint32_t size, uint32_t *sent) +{ + service_error_t res = SERVICE_E_UNKNOWN_ERROR; + uint32_t bytes = 0; + + if (!client || (client && !client->connection) || !data || (size == 0)) { + return SERVICE_E_INVALID_ARG; + } + + debug_info("sending %d bytes", size); + res = idevice_to_service_error(idevice_connection_send(client->connection, data, size, &bytes)); + if (res != SERVICE_E_SUCCESS) { + debug_info("ERROR: sending to device failed."); + } + if (sent) { + *sent = bytes; + } + + return res; +} + +service_error_t service_receive_with_timeout(service_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + service_error_t res = SERVICE_E_UNKNOWN_ERROR; + uint32_t bytes = 0; + + if (!client || (client && !client->connection) || !data || (size == 0)) { + return SERVICE_E_INVALID_ARG; + } + + res = idevice_to_service_error(idevice_connection_receive_timeout(client->connection, data, size, &bytes, timeout)); + if (res != SERVICE_E_SUCCESS && res != SERVICE_E_TIMEOUT) { + debug_info("could not read data"); + return res; + } + if (received) { + *received = bytes; + } + + return res; +} + +service_error_t service_receive(service_client_t client, char* data, uint32_t size, uint32_t *received) +{ + return service_receive_with_timeout(client, data, size, received, 30000); +} + +service_error_t service_enable_ssl(service_client_t client) +{ + if (!client || !client->connection) + return SERVICE_E_INVALID_ARG; + return idevice_to_service_error(idevice_connection_enable_ssl(client->connection)); +} + +service_error_t service_disable_ssl(service_client_t client) +{ + return service_disable_bypass_ssl(client, 0); +} + +service_error_t service_disable_bypass_ssl(service_client_t client, uint8_t sslBypass) +{ + if (!client || !client->connection) + return SERVICE_E_INVALID_ARG; + return idevice_to_service_error(idevice_connection_disable_bypass_ssl(client->connection, sslBypass)); +} + +service_error_t service_get_connection(service_client_t client, idevice_connection_t *connection) +{ + if (!client || !client->connection || !connection) + return SERVICE_E_INVALID_ARG; + *connection = client->connection; + return SERVICE_E_SUCCESS; +} diff --git a/src/service.h b/src/service.h new file mode 100644 index 0000000..071fe3f --- /dev/null +++ b/src/service.h @@ -0,0 +1,32 @@ +/* + * service.h + * Definitions for the generic service implementation + * + * Copyright (c) 2013 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef SERVICE_H +#define SERVICE_H + +#include "idevice.h" +#include "libimobiledevice/service.h" +#include "libimobiledevice/lockdown.h" + +struct service_client_private { + idevice_connection_t connection; +}; + +#endif diff --git a/src/syslog_relay.c b/src/syslog_relay.c new file mode 100644 index 0000000..9f4296e --- /dev/null +++ b/src/syslog_relay.c @@ -0,0 +1,248 @@ +/* + * syslog_relay.c + * com.apple.syslog_relay service implementation. + * + * Copyright (c) 2019-2020 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2013-2015 Martin Szulecki, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> + +#include "syslog_relay.h" +#include "lockdown.h" +#include "common/debug.h" + +struct syslog_relay_worker_thread { + syslog_relay_client_t client; + syslog_relay_receive_cb_t cbfunc; + void *user_data; + int is_raw; +}; + +/** + * Convert a service_error_t value to a syslog_relay_error_t value. + * Used internally to get correct error codes. + * + * @param err An service_error_t error code + * + * @return A matching syslog_relay_error_t error code, + * SYSLOG_RELAY_E_UNKNOWN_ERROR otherwise. + */ +static syslog_relay_error_t syslog_relay_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return SYSLOG_RELAY_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return SYSLOG_RELAY_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return SYSLOG_RELAY_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return SYSLOG_RELAY_E_SSL_ERROR; + case SERVICE_E_NOT_ENOUGH_DATA: + return SYSLOG_RELAY_E_NOT_ENOUGH_DATA; + case SERVICE_E_TIMEOUT: + return SYSLOG_RELAY_E_TIMEOUT; + default: + break; + } + return SYSLOG_RELAY_E_UNKNOWN_ERROR; +} + +syslog_relay_error_t syslog_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, syslog_relay_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to syslog_relay_client_new."); + return SYSLOG_RELAY_E_INVALID_ARG; + } + + debug_info("Creating syslog_relay_client, port = %d.", service->port); + + service_client_t parent = NULL; + syslog_relay_error_t ret = syslog_relay_error(service_client_new(device, service, &parent)); + if (ret != SYSLOG_RELAY_E_SUCCESS) { + debug_info("Creating base service client failed. Error: %i", ret); + return ret; + } + + syslog_relay_client_t client_loc = (syslog_relay_client_t) malloc(sizeof(struct syslog_relay_client_private)); + client_loc->parent = parent; + client_loc->worker = THREAD_T_NULL; + + *client = client_loc; + + debug_info("syslog_relay_client successfully created."); + return 0; +} + +syslog_relay_error_t syslog_relay_client_start_service(idevice_t device, syslog_relay_client_t * client, const char* label) +{ + syslog_relay_error_t err = SYSLOG_RELAY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, SYSLOG_RELAY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(syslog_relay_client_new), &err); + return err; +} + +syslog_relay_error_t syslog_relay_client_free(syslog_relay_client_t client) +{ + if (!client) + return SYSLOG_RELAY_E_INVALID_ARG; + syslog_relay_stop_capture(client); + syslog_relay_error_t err = syslog_relay_error(service_client_free(client->parent)); + free(client); + + return err; +} + +syslog_relay_error_t syslog_relay_receive(syslog_relay_client_t client, char* data, uint32_t size, uint32_t *received) +{ + return syslog_relay_receive_with_timeout(client, data, size, received, 1000); +} + +syslog_relay_error_t syslog_relay_receive_with_timeout(syslog_relay_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return SYSLOG_RELAY_E_INVALID_ARG; + } + + res = syslog_relay_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); + if (res != SYSLOG_RELAY_E_SUCCESS && res != SYSLOG_RELAY_E_TIMEOUT && res != SYSLOG_RELAY_E_NOT_ENOUGH_DATA) { + debug_info("Could not read data, error %d", res); + } + if (received) { + *received = (uint32_t)bytes; + } + + return res; +} + +void *syslog_relay_worker(void *arg) +{ + syslog_relay_error_t ret = SYSLOG_RELAY_E_UNKNOWN_ERROR; + struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)arg; + + if (!srwt) + return NULL; + + debug_info("Running"); + + while (srwt->client->parent) { + char c; + uint32_t bytes = 0; + ret = syslog_relay_receive_with_timeout(srwt->client, &c, 1, &bytes, 100); + if (ret == SYSLOG_RELAY_E_TIMEOUT || ret == SYSLOG_RELAY_E_NOT_ENOUGH_DATA || ((bytes == 0) && (ret == SYSLOG_RELAY_E_SUCCESS))) { + continue; + } + if (ret < 0) { + debug_info("Connection to syslog relay interrupted"); + break; + } + if (srwt->is_raw) { + srwt->cbfunc(c, srwt->user_data); + } else if (c != 0) { + srwt->cbfunc(c, srwt->user_data); + } + } + + if (srwt) { + free(srwt); + } + + debug_info("Exiting"); + + return NULL; +} + +syslog_relay_error_t syslog_relay_start_capture(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data) +{ + if (!client || !callback) + return SYSLOG_RELAY_E_INVALID_ARG; + + syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; + + if (client->worker) { + debug_info("Another syslog capture thread appears to be running already."); + return res; + } + + /* start worker thread */ + struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)malloc(sizeof(struct syslog_relay_worker_thread)); + if (srwt) { + srwt->client = client; + srwt->cbfunc = callback; + srwt->user_data = user_data; + srwt->is_raw = 0; + + if (thread_new(&client->worker, syslog_relay_worker, srwt) == 0) { + res = SYSLOG_RELAY_E_SUCCESS; + } + } + + return res; +} + +syslog_relay_error_t syslog_relay_start_capture_raw(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data) +{ + if (!client || !callback) + return SYSLOG_RELAY_E_INVALID_ARG; + + syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; + + if (client->worker) { + debug_info("Another syslog capture thread appears to be running already."); + return res; + } + + /* start worker thread */ + struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)malloc(sizeof(struct syslog_relay_worker_thread)); + if (srwt) { + srwt->client = client; + srwt->cbfunc = callback; + srwt->user_data = user_data; + srwt->is_raw = 1; + + if (thread_new(&client->worker, syslog_relay_worker, srwt) == 0) { + res = SYSLOG_RELAY_E_SUCCESS; + } + } + + return res; +} + +syslog_relay_error_t syslog_relay_stop_capture(syslog_relay_client_t client) +{ + if (client->worker) { + /* notify thread to finish */ + service_client_t parent = client->parent; + client->parent = NULL; + /* join thread to make it exit */ + thread_join(client->worker); + thread_free(client->worker); + client->worker = THREAD_T_NULL; + client->parent = parent; + } + + return SYSLOG_RELAY_E_SUCCESS; +} diff --git a/src/syslog_relay.h b/src/syslog_relay.h new file mode 100644 index 0000000..d5263e2 --- /dev/null +++ b/src/syslog_relay.h @@ -0,0 +1,37 @@ +/* + * syslog_relay.h + * com.apple.syslog_relay service header file. + * + * Copyright (c) 2013 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SYSLOG_RELAY_H +#define _SYSLOG_RELAY_H + +#include "idevice.h" +#include "libimobiledevice/syslog_relay.h" +#include "service.h" +#include <libimobiledevice-glue/thread.h> + +struct syslog_relay_client_private { + service_client_t parent; + THREAD_T worker; +}; + +void *syslog_relay_worker(void *arg); + +#endif diff --git a/src/userpref.c b/src/userpref.c deleted file mode 100644 index 6e62000..0000000 --- a/src/userpref.c +++ /dev/null @@ -1,605 +0,0 @@ -/* - * userpref.c - * contains methods to access user specific certificates IDs and more. - * - * Copyright (c) 2008 Jonathan Beck All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include <glib.h> -#include <glib/gstdio.h> -#include <glib/gprintf.h> -#include <stdio.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> -#include <gcrypt.h> - -#include "userpref.h" -#include "debug.h" - -#define LIBIMOBILEDEVICE_CONF_DIR "libimobiledevice" -#define LIBIMOBILEDEVICE_CONF_FILE "libimobiledevicerc" - -#define LIBIMOBILEDEVICE_ROOT_PRIVKEY "RootPrivateKey.pem" -#define LIBIMOBILEDEVICE_HOST_PRIVKEY "HostPrivateKey.pem" -#define LIBIMOBILEDEVICE_ROOT_CERTIF "RootCertificate.pem" -#define LIBIMOBILEDEVICE_HOST_CERTIF "HostCertificate.pem" - - -/** - * Creates a freedesktop compatible configuration directory. - */ -static void userpref_create_config_dir(void) -{ - gchar *config_dir = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, NULL); - - if (!g_file_test(config_dir, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) - g_mkdir_with_parents(config_dir, 0755); - - g_free(config_dir); -} - -static int get_rand(int min, int max) -{ - int retval = (rand() % (max - min)) + min; - return retval; -} - -/** - * Generates a valid HostID (which is actually a UUID). - * - * @return A null terminated string containing a valid HostID. - */ -static char *userpref_generate_host_id() -{ - /* HostID's are just UUID's, and UUID's are 36 characters long */ - char *hostid = (char *) malloc(sizeof(char) * 37); - const char *chars = "ABCDEF0123456789"; - srand(time(NULL)); - int i = 0; - - for (i = 0; i < 36; i++) { - if (i == 8 || i == 13 || i == 18 || i == 23) { - hostid[i] = '-'; - continue; - } else { - hostid[i] = chars[get_rand(0, 16)]; - } - } - /* make it a real string */ - hostid[36] = '\0'; - return hostid; -} - -/** - * Store HostID in config file. - * - * @param host_id A null terminated string containing a valid HostID. - */ -static int userpref_set_host_id(const char *host_id) -{ - GKeyFile *key_file; - gsize length; - gchar *buf, *config_file; - GIOChannel *file; - - if (!host_id) - return 0; - - /* Make sure config directory exists */ - userpref_create_config_dir(); - - /* Now parse file to get the HostID */ - key_file = g_key_file_new(); - - /* Store in config file */ - debug_info("setting hostID to %s", host_id); - g_key_file_set_value(key_file, "Global", "HostID", host_id); - - /* Write config file on disk */ - buf = g_key_file_to_data(key_file, &length, NULL); - config_file = - g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_CONF_FILE, NULL); - file = g_io_channel_new_file(config_file, "w", NULL); - g_free(config_file); - g_io_channel_write_chars(file, buf, length, NULL, NULL); - g_io_channel_shutdown(file, TRUE, NULL); - g_io_channel_unref(file); - - g_key_file_free(key_file); - return 1; -} - -/** - * Reads the HostID from a previously generated configuration file. - * - * @note It is the responsibility of the calling function to free the returned host_id - * - * @return The string containing the HostID or NULL - */ -void userpref_get_host_id(char **host_id) -{ - gchar *config_file; - GKeyFile *key_file; - gchar *loc_host_id; - - config_file = - g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_CONF_FILE, NULL); - - /* now parse file to get the HostID */ - key_file = g_key_file_new(); - if (g_key_file_load_from_file(key_file, config_file, G_KEY_FILE_KEEP_COMMENTS, NULL)) { - loc_host_id = g_key_file_get_value(key_file, "Global", "HostID", NULL); - if (loc_host_id) - *host_id = strdup((char *) loc_host_id); - g_free(loc_host_id); - } - g_key_file_free(key_file); - g_free(config_file); - - if (!*host_id) { - /* no config, generate host_id */ - *host_id = userpref_generate_host_id(); - userpref_set_host_id(*host_id); - } - - debug_info("Using %s as HostID", *host_id); -} - -/** - * Determines whether this device has been connected to this system before. - * - * @param uid The device uid as given by the device. - * - * @return 1 if the device has been connected previously to this configuration - * or 0 otherwise. - */ -int userpref_has_device_public_key(const char *uuid) -{ - int ret = 0; - gchar *config_file; - - /* first get config file */ - gchar *device_file = g_strconcat(uuid, ".pem", NULL); - config_file = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, device_file, NULL); - if (g_file_test(config_file, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) - ret = 1; - g_free(config_file); - g_free(device_file); - return ret; -} - -/** - * Fills a list with UUIDs of devices that have been connected to this - * system before, i.e. for which a public key file exists. - * - * @param list A pointer to a char** initially pointing to NULL that will - * hold a newly allocated list of UUIDs upon successful return. - * The caller is responsible for freeing the memory. Note that if - * no public key file was found the list has to be freed too as it - * points to a terminating NULL element. - * @param count The number of UUIDs found. This parameter can be NULL if it - * is not required. - * - * @return USERPREF_E_SUCCESS on success, or USERPREF_E_INVALID_ARG if the - * list parameter is not pointing to NULL. - */ -userpref_error_t userpref_get_paired_uuids(char ***list, unsigned int *count) -{ - GDir *config_dir; - gchar *config_path; - const gchar *dir_file; - GList *uuids = NULL; - unsigned int i; - unsigned int found = 0; - - if (!list || (list && *list)) { - debug_info("ERROR: The list parameter needs to point to NULL!"); - return USERPREF_E_INVALID_ARG; - } - - if (count) { - *count = 0; - } - - config_path = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, NULL); - - config_dir = g_dir_open(config_path,0,NULL); - if (config_dir) { - while ((dir_file = g_dir_read_name(config_dir))) { - if (g_str_has_suffix(dir_file, ".pem") && (strlen(dir_file) == 44)) { - uuids = g_list_append(uuids, g_strndup(dir_file, strlen(dir_file)-4)); - found++; - } - } - g_dir_close(config_dir); - } - *list = (char**)malloc(sizeof(char*) * (found+1)); - for (i = 0; i < found; i++) { - (*list)[i] = g_list_nth_data(uuids, i); - } - (*list)[i] = NULL; - - if (count) { - *count = found; - } - g_list_free(uuids); - g_free(config_path); - - return USERPREF_E_SUCCESS; -} - -/** - * Mark the device (as represented by the key) as having connected to this - * configuration. - * - * @param public_key The public key given by the device - * - * @return 1 on success and 0 if no public key is given or if it has already - * been marked as connected previously. - */ -userpref_error_t userpref_set_device_public_key(const char *uuid, gnutls_datum_t public_key) -{ - if (NULL == public_key.data) - return USERPREF_E_INVALID_ARG; - - if (userpref_has_device_public_key(uuid)) - return USERPREF_E_SUCCESS; - - /* ensure config directory exists */ - userpref_create_config_dir(); - - /* build file path */ - gchar *device_file = g_strconcat(uuid, ".pem", NULL); - gchar *pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, device_file, NULL); - - /* store file */ - FILE *pFile = fopen(pem, "wb"); - fwrite(public_key.data, 1, public_key.size, pFile); - fclose(pFile); - g_free(pem); - g_free(device_file); - - return USERPREF_E_SUCCESS; -} - -/** - * Remove the public key stored for the device with uuid from this host. - * - * @param uuid The uuid of the device - * - * @return USERPREF_E_SUCCESS on success. - */ -userpref_error_t userpref_remove_device_public_key(const char *uuid) -{ - if (!userpref_has_device_public_key(uuid)) - return USERPREF_E_SUCCESS; - - /* build file path */ - gchar *device_file = g_strconcat(uuid, ".pem", NULL); - gchar *pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, device_file, NULL); - - /* remove file */ - g_remove(pem); - - g_free(pem); - g_free(device_file); - - return USERPREF_E_SUCCESS; -} - -/** - * Private function which reads the given file into a gnutls structure. - * - * @param file The filename of the file to read - * @param data The pointer at which to store the data. - * - * @return 1 if the file contents where read successfully and 0 otherwise. - */ -static int userpref_get_file_contents(const char *file, gnutls_datum_t * data) -{ - gboolean success; - gsize size; - char *content; - gchar *filepath; - - if (NULL == file || NULL == data) - return 0; - - /* Read file */ - filepath = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, file, NULL); - success = g_file_get_contents(filepath, &content, &size, NULL); - g_free(filepath); - - /* Add it to the gnutls_datnum_t structure */ - data->data = (uint8_t*) content; - data->size = size; - - return success; -} - -/** - * Private function which generate private keys and certificates. - * - * @return 1 if keys were successfully generated, 0 otherwise - */ -static userpref_error_t userpref_gen_keys_and_cert(void) -{ - userpref_error_t ret = USERPREF_E_SSL_ERROR; - - gnutls_x509_privkey_t root_privkey; - gnutls_x509_crt_t root_cert; - gnutls_x509_privkey_t host_privkey; - gnutls_x509_crt_t host_cert; - - gnutls_global_deinit(); - gnutls_global_init(); - - //use less secure random to speed up key generation - gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM); - - gnutls_x509_privkey_init(&root_privkey); - gnutls_x509_privkey_init(&host_privkey); - - gnutls_x509_crt_init(&root_cert); - gnutls_x509_crt_init(&host_cert); - - /* generate root key */ - gnutls_x509_privkey_generate(root_privkey, GNUTLS_PK_RSA, 2048, 0); - gnutls_x509_privkey_generate(host_privkey, GNUTLS_PK_RSA, 2048, 0); - - /* generate certificates */ - gnutls_x509_crt_set_key(root_cert, root_privkey); - gnutls_x509_crt_set_serial(root_cert, "\x00", 1); - gnutls_x509_crt_set_version(root_cert, 3); - gnutls_x509_crt_set_ca_status(root_cert, 1); - gnutls_x509_crt_set_activation_time(root_cert, time(NULL)); - gnutls_x509_crt_set_expiration_time(root_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); - gnutls_x509_crt_sign(root_cert, root_cert, root_privkey); - - gnutls_x509_crt_set_key(host_cert, host_privkey); - gnutls_x509_crt_set_serial(host_cert, "\x00", 1); - gnutls_x509_crt_set_version(host_cert, 3); - gnutls_x509_crt_set_ca_status(host_cert, 0); - gnutls_x509_crt_set_key_usage(host_cert, GNUTLS_KEY_KEY_ENCIPHERMENT | GNUTLS_KEY_DIGITAL_SIGNATURE); - gnutls_x509_crt_set_activation_time(host_cert, time(NULL)); - gnutls_x509_crt_set_expiration_time(host_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); - gnutls_x509_crt_sign(host_cert, root_cert, root_privkey); - - /* export to PEM format */ - size_t root_key_export_size = 0; - size_t host_key_export_size = 0; - gnutls_datum_t root_key_pem = { NULL, 0 }; - gnutls_datum_t host_key_pem = { NULL, 0 }; - - gnutls_x509_privkey_export(root_privkey, GNUTLS_X509_FMT_PEM, NULL, &root_key_export_size); - gnutls_x509_privkey_export(host_privkey, GNUTLS_X509_FMT_PEM, NULL, &host_key_export_size); - - root_key_pem.data = gnutls_malloc(root_key_export_size); - host_key_pem.data = gnutls_malloc(host_key_export_size); - - gnutls_x509_privkey_export(root_privkey, GNUTLS_X509_FMT_PEM, root_key_pem.data, &root_key_export_size); - root_key_pem.size = root_key_export_size; - gnutls_x509_privkey_export(host_privkey, GNUTLS_X509_FMT_PEM, host_key_pem.data, &host_key_export_size); - host_key_pem.size = host_key_export_size; - - size_t root_cert_export_size = 0; - size_t host_cert_export_size = 0; - gnutls_datum_t root_cert_pem = { NULL, 0 }; - gnutls_datum_t host_cert_pem = { NULL, 0 }; - - gnutls_x509_crt_export(root_cert, GNUTLS_X509_FMT_PEM, NULL, &root_cert_export_size); - gnutls_x509_crt_export(host_cert, GNUTLS_X509_FMT_PEM, NULL, &host_cert_export_size); - - root_cert_pem.data = gnutls_malloc(root_cert_export_size); - host_cert_pem.data = gnutls_malloc(host_cert_export_size); - - gnutls_x509_crt_export(root_cert, GNUTLS_X509_FMT_PEM, root_cert_pem.data, &root_cert_export_size); - root_cert_pem.size = root_cert_export_size; - gnutls_x509_crt_export(host_cert, GNUTLS_X509_FMT_PEM, host_cert_pem.data, &host_cert_export_size); - host_cert_pem.size = host_cert_export_size; - - if (NULL != root_cert_pem.data && 0 != root_cert_pem.size && - NULL != host_cert_pem.data && 0 != host_cert_pem.size) - ret = USERPREF_E_SUCCESS; - - /* store values in config file */ - userpref_set_keys_and_certs( &root_key_pem, &root_cert_pem, &host_key_pem, &host_cert_pem); - - gnutls_free(root_key_pem.data); - gnutls_free(root_cert_pem.data); - gnutls_free(host_key_pem.data); - gnutls_free(host_cert_pem.data); - - //restore gnutls env - gnutls_global_deinit(); - gnutls_global_init(); - - return ret; -} - -/** - * Private function which import the given key into a gnutls structure. - * - * @param key_name The filename of the private key to import. - * @param key the gnutls key structure. - * - * @return 1 if the key was successfully imported. - */ -static userpref_error_t userpref_import_key(const char* key_name, gnutls_x509_privkey_t key) -{ - userpref_error_t ret = USERPREF_E_INVALID_CONF; - gnutls_datum_t pem_key = { NULL, 0 }; - - if (userpref_get_file_contents(key_name, &pem_key)) { - if (GNUTLS_E_SUCCESS == gnutls_x509_privkey_import(key, &pem_key, GNUTLS_X509_FMT_PEM)) - ret = USERPREF_E_SUCCESS; - else - ret = USERPREF_E_SSL_ERROR; - } - gnutls_free(pem_key.data); - return ret; -} - -/** - * Private function which import the given certificate into a gnutls structure. - * - * @param crt_name The filename of the certificate to import. - * @param cert the gnutls certificate structure. - * - * @return IDEVICE_E_SUCCESS if the certificate was successfully imported. - */ -static userpref_error_t userpref_import_crt(const char* crt_name, gnutls_x509_crt_t cert) -{ - userpref_error_t ret = USERPREF_E_INVALID_CONF; - gnutls_datum_t pem_cert = { NULL, 0 }; - - if (userpref_get_file_contents(crt_name, &pem_cert)) { - if (GNUTLS_E_SUCCESS == gnutls_x509_crt_import(cert, &pem_cert, GNUTLS_X509_FMT_PEM)) - ret = USERPREF_E_SUCCESS; - else - ret = USERPREF_E_SSL_ERROR; - } - gnutls_free(pem_cert.data); - return ret; -} - -/** - * Function to retrieve host keys and certificates. - * This function trigger key generation if they do not exists yet or are invalid. - * - * @note This function can take few seconds to complete (typically 5 seconds) - * - * @param root_privkey The root private key. - * @param root_crt The root certificate. - * @param host_privkey The host private key. - * @param host_crt The host certificate. - * - * @return 1 if the keys and certificates were successfully retrieved, 0 otherwise - */ -userpref_error_t userpref_get_keys_and_certs(gnutls_x509_privkey_t root_privkey, gnutls_x509_crt_t root_crt, gnutls_x509_privkey_t host_privkey, gnutls_x509_crt_t host_crt) -{ - userpref_error_t ret = USERPREF_E_SUCCESS; - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_ROOT_PRIVKEY, root_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_HOST_PRIVKEY, host_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_ROOT_CERTIF, root_crt); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_HOST_CERTIF, host_crt); - - - if (USERPREF_E_SUCCESS != ret) { - //we had problem reading or importing root cert - //try with a new ones. - ret = userpref_gen_keys_and_cert(); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_ROOT_PRIVKEY, root_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_HOST_PRIVKEY, host_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_ROOT_CERTIF, root_crt); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_HOST_CERTIF, host_crt); - } - - return ret; -} - -/** - * Function to retrieve certificates encoded in PEM format. - * - * @param pem_root_cert The root certificate. - * @param pem_host_cert The host certificate. - * - * @return 1 if the certificates were successfully retrieved, 0 otherwise - */ -userpref_error_t userpref_get_certs_as_pem(gnutls_datum_t *pem_root_cert, gnutls_datum_t *pem_host_cert) -{ - if (!pem_root_cert || !pem_host_cert) - return USERPREF_E_INVALID_ARG; - - if (userpref_get_file_contents(LIBIMOBILEDEVICE_ROOT_CERTIF, pem_root_cert) && userpref_get_file_contents(LIBIMOBILEDEVICE_HOST_CERTIF, pem_host_cert)) - return USERPREF_E_SUCCESS; - else { - g_free(pem_root_cert->data); - g_free(pem_host_cert->data); - } - return USERPREF_E_INVALID_CONF; -} - -/** - * Create and save a configuration file containing the given data. - * - * @note: All fields must specified and be non-null - * - * @param root_key The root key - * @param root_cert The root certificate - * @param host_key The host key - * @param host_cert The host certificate - * - * @return 1 on success and 0 otherwise. - */ -userpref_error_t userpref_set_keys_and_certs(gnutls_datum_t * root_key, gnutls_datum_t * root_cert, gnutls_datum_t * host_key, gnutls_datum_t * host_cert) -{ - FILE *pFile; - gchar *pem; - - if (!root_key || !host_key || !root_cert || !host_cert) - return USERPREF_E_INVALID_ARG; - - /* Make sure config directory exists */ - userpref_create_config_dir(); - - /* Now write keys and certificates to disk */ - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_ROOT_PRIVKEY, NULL); - pFile = fopen(pem, "wb"); - fwrite(root_key->data, 1, root_key->size, pFile); - fclose(pFile); - g_free(pem); - - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_HOST_PRIVKEY, NULL); - pFile = fopen(pem, "wb"); - fwrite(host_key->data, 1, host_key->size, pFile); - fclose(pFile); - g_free(pem); - - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_ROOT_CERTIF, NULL); - pFile = fopen(pem, "wb"); - fwrite(root_cert->data, 1, root_cert->size, pFile); - fclose(pFile); - g_free(pem); - - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_HOST_CERTIF, NULL); - pFile = fopen(pem, "wb"); - fwrite(host_cert->data, 1, host_cert->size, pFile); - fclose(pFile); - g_free(pem); - - return USERPREF_E_SUCCESS; -} diff --git a/src/userpref.h b/src/userpref.h deleted file mode 100644 index f9d3913..0000000 --- a/src/userpref.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * userpref.h - * contains methods to access user specific certificates IDs and more. - * - * Copyright (c) 2008 Jonathan Beck All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef USERPREF_H -#define USERPREF_H - -#include <gnutls/gnutls.h> -#include <glib.h> - -#define USERPREF_E_SUCCESS 0 -#define USERPREF_E_INVALID_ARG -1 -#define USERPREF_E_INVALID_CONF -2 -#define USERPREF_E_SSL_ERROR -3 - -#define USERPREF_E_UNKNOWN_ERROR -256 - -typedef int16_t userpref_error_t; - -G_GNUC_INTERNAL userpref_error_t userpref_get_keys_and_certs(gnutls_x509_privkey_t root_privkey, gnutls_x509_crt_t root_crt, gnutls_x509_privkey_t host_privkey, gnutls_x509_crt_t host_crt); -G_GNUC_INTERNAL userpref_error_t userpref_set_keys_and_certs(gnutls_datum_t * root_key, gnutls_datum_t * root_cert, gnutls_datum_t * host_key, gnutls_datum_t * host_cert); -G_GNUC_INTERNAL userpref_error_t userpref_get_certs_as_pem(gnutls_datum_t *pem_root_cert, gnutls_datum_t *pem_host_cert); -G_GNUC_INTERNAL userpref_error_t userpref_set_device_public_key(const char *uuid, gnutls_datum_t public_key); -userpref_error_t userpref_remove_device_public_key(const char *uuid); -G_GNUC_INTERNAL int userpref_has_device_public_key(const char *uuid); -userpref_error_t userpref_get_paired_uuids(char ***list, unsigned int *count); -void userpref_get_host_id(char **host_id); - -#endif diff --git a/src/webinspector.c b/src/webinspector.c new file mode 100644 index 0000000..f960fcc --- /dev/null +++ b/src/webinspector.c @@ -0,0 +1,263 @@ +/* + * webinspector.c + * com.apple.webinspector service implementation. + * + * Copyright (c) 2013 Yury Melnichek All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <plist/plist.h> + +#include "webinspector.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a webinspector_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching webinspector_error_t error code, + * WEBINSPECTOR_E_UNKNOWN_ERROR otherwise. + */ +static webinspector_error_t webinspector_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return WEBINSPECTOR_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return WEBINSPECTOR_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return WEBINSPECTOR_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return WEBINSPECTOR_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return WEBINSPECTOR_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return WEBINSPECTOR_E_RECEIVE_TIMEOUT; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return WEBINSPECTOR_E_NOT_ENOUGH_DATA; + default: + break; + } + return WEBINSPECTOR_E_UNKNOWN_ERROR; +} + +webinspector_error_t webinspector_client_new(idevice_t device, lockdownd_service_descriptor_t service, webinspector_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to webinspector_client_new."); + return WEBINSPECTOR_E_INVALID_ARG; + } + + debug_info("Creating webinspector_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + webinspector_error_t ret = webinspector_error(property_list_service_client_new(device, service, &plclient)); + if (ret != WEBINSPECTOR_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + webinspector_client_t client_loc = (webinspector_client_t) malloc(sizeof(struct webinspector_client_private)); + client_loc->parent = plclient; + + *client = client_loc; + + debug_info("webinspector_client successfully created."); + return 0; +} + +webinspector_error_t webinspector_client_start_service(idevice_t device, webinspector_client_t * client, const char* label) +{ + webinspector_error_t err = WEBINSPECTOR_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, WEBINSPECTOR_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(webinspector_client_new), &err); + return err; +} + +webinspector_error_t webinspector_client_free(webinspector_client_t client) +{ + if (!client) + return WEBINSPECTOR_E_INVALID_ARG; + + webinspector_error_t err = webinspector_error(property_list_service_client_free(client->parent)); + free(client); + + return err; +} + +webinspector_error_t webinspector_send(webinspector_client_t client, plist_t plist) +{ + webinspector_error_t res = WEBINSPECTOR_E_UNKNOWN_ERROR; + + uint32_t offset = 0; + int is_final_message = 0; + + char *packet = NULL; + uint32_t packet_length = 0; + + debug_info("Sending webinspector message..."); + debug_plist(plist); + + /* convert plist to packet */ + plist_to_bin(plist, &packet, &packet_length); + if (!packet || packet_length == 0) { + debug_info("Error converting plist to binary."); + return res; + } + + do { + /* determine if we need to send partial messages */ + if (packet_length < WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE) { + is_final_message = 1; + } else { + /* send partial packet */ + is_final_message = 0; + } + + plist_t outplist = plist_new_dict(); + if (!is_final_message) { + /* split packet into partial chunks */ + plist_dict_set_item(outplist, "WIRPartialMessageKey", plist_new_data(packet + offset, WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE)); + offset += WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE; + packet_length -= WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE; + } else { + /* send final chunk */ + plist_dict_set_item(outplist, "WIRFinalMessageKey", plist_new_data(packet + offset, packet_length)); + offset += packet_length; + packet_length -= packet_length; + } + + res = webinspector_error(property_list_service_send_binary_plist(client->parent, outplist)); + plist_free(outplist); + outplist = NULL; + if (res != WEBINSPECTOR_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + } while(packet_length > 0); + + free(packet); + packet = NULL; + + return res; +} + +webinspector_error_t webinspector_receive(webinspector_client_t client, plist_t * plist) +{ + return webinspector_receive_with_timeout(client, plist, 5000); +} + +webinspector_error_t webinspector_receive_with_timeout(webinspector_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + webinspector_error_t res = WEBINSPECTOR_E_UNKNOWN_ERROR; + plist_t message = NULL; + plist_t key = NULL; + + int is_final_message = 1; + + char* buffer = NULL; + uint64_t length = 0; + + char* packet = NULL; + char* newpacket = NULL; + uint64_t packet_length = 0; + + debug_info("Receiving webinspector message..."); + + do { + /* receive message */ + res = webinspector_error(property_list_service_receive_plist_with_timeout(client->parent, &message, timeout_ms)); + if (res != WEBINSPECTOR_E_SUCCESS || !message) { + debug_info("Could not receive message, error %d", res); + plist_free(message); + return WEBINSPECTOR_E_MUX_ERROR; + } + + /* get message key */ + key = plist_dict_get_item(message, "WIRFinalMessageKey"); + if (!key) { + key = plist_dict_get_item(message, "WIRPartialMessageKey"); + if (!key) { + debug_info("ERROR: Unable to read message key."); + plist_free(message); + return WEBINSPECTOR_E_PLIST_ERROR; + } + is_final_message = 0; + } else { + is_final_message = 1; + } + + /* read partial data */ + plist_get_data_val(key, &buffer, &length); + if (!buffer || length == 0 || length > 0xFFFFFFFF) { + debug_info("ERROR: Unable to get the inner plist binary data."); + free(packet); + free(buffer); + return WEBINSPECTOR_E_PLIST_ERROR; + } + + /* (re)allocate packet data */ + if (!packet) { + packet = (char*)malloc(length * sizeof(char)); + } else { + newpacket = (char*)realloc(packet, (packet_length + length) * sizeof(char)); + packet = newpacket; + } + + /* copy partial data into final packet data */ + memcpy(packet + packet_length, buffer, length); + + /* cleanup buffer */ + free(buffer); + buffer = NULL; + + if (message) { + plist_free(message); + message = NULL; + } + + /* adjust packet length */ + packet_length += length; + length = 0; + } while(!is_final_message); + + /* read final message */ + if (packet_length) { + plist_from_bin(packet, (uint32_t)packet_length, plist); + if (!*plist) { + debug_info("Error restoring the final plist."); + free(packet); + return WEBINSPECTOR_E_PLIST_ERROR; + } + + debug_plist(*plist); + } + + if (packet) { + free(packet); + } + + return res; +} diff --git a/src/webinspector.h b/src/webinspector.h new file mode 100644 index 0000000..d249c58 --- /dev/null +++ b/src/webinspector.h @@ -0,0 +1,35 @@ +/* + * webinspector.h + * com.apple.webinspector service header file. + * + * Copyright (c) 2013 Yury Melnichek All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __WEBINSPECTOR_H +#define __WEBINSPECTOR_H + +#include "idevice.h" +#include "libimobiledevice/webinspector.h" +#include "property_list_service.h" + +#define WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE 8096 + +struct webinspector_client_private { + property_list_service_client_t parent; +}; + +#endif diff --git a/swig/Makefile.am b/swig/Makefile.am deleted file mode 100644 index 8c1f2b9..0000000 --- a/swig/Makefile.am +++ /dev/null @@ -1,34 +0,0 @@ -AM_CPPFLAGS = -I$(top_srcdir)/include $(libglib2_CFLAGS) $(libplist_CFLAGS) $(SWIG_PYTHON_CPPFLAGS) -I$(oldincludedir) - -if HAVE_SWIG -BUILT_SOURCES = imobiledevice_wrap.cxx -SWIG_SOURCES = imobiledevice.i - -CLEANFILES = \ - *.pyc \ - *.pyo \ - _imobiledevice.so \ - imobiledevice.py \ - imobiledevice_wrap.cxx - -EXTRA_DIST = \ - __init__.py \ - imobiledevice.i - -swigincludedir =$(includedir)/libimobiledevice/swig -swiginclude_HEADERS = $(SWIG_SOURCES) - -imobiledevicedir = $(pyexecdir)/imobiledevice -imobiledevice_PYTHON = __init__.py -nodist_imobiledevice_PYTHON = imobiledevice.py -imobiledevice_LTLIBRARIES = _imobiledevice.la -nodist__imobiledevice_la_SOURCES = imobiledevice_wrap.cxx $(SWIG_SOURCES) -_imobiledevice_la_CFLAGS = $(PYTHON_CPPFLAGS) -I$(top_srcdir)/src -_imobiledevice_la_LDFLAGS = -module -avoid-version $(PYTHON_LDFLAGS) -_imobiledevice_la_LIBADD = $(top_builddir)/src/libimobiledevice.la $(libplistmm_LIBS) - -imobiledevice_wrap.cxx : $(SWIG_SOURCES) - $(SWIG) $(SWIG_PYTHON_OPT) $(AM_CPPFLAGS) -I$(top_srcdir)/src -o $@ $< - -endif # HAVE_SWIG - diff --git a/swig/__init__.py b/swig/__init__.py deleted file mode 100644 index e0c053b..0000000 --- a/swig/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -from imobiledevice import * diff --git a/swig/imobiledevice.i b/swig/imobiledevice.i deleted file mode 100644 index 2de69e7..0000000 --- a/swig/imobiledevice.i +++ /dev/null @@ -1,341 +0,0 @@ - /* swig.i */ - %module imobiledevice - %feature("autodoc", "1"); - %{ - /* Includes the header in the wrapper code */ - #include <libimobiledevice/libimobiledevice.h> - #include <libimobiledevice/lockdown.h> - #include <libimobiledevice/mobilesync.h> - #include <libimobiledevice/notification_proxy.h> - #include <plist/plist.h> - #include <plist/plist++.h> - #include "../src/debug.h" - -typedef struct { - idevice_t dev; -} idevice; - -typedef struct { - idevice* dev; - lockdownd_client_t client; -} Lockdownd; - -typedef struct { - idevice* dev; - mobilesync_client_t client; -} MobileSync; - -typedef struct { - idevice* dev; - np_client_t client; -} NotificationProxy; - -//now declare funtions to handle creation and deletion of objects -static void my_delete_idevice(idevice* dev) { - if (dev) { - idevice_free(dev->dev); - free(dev); - } -} - -static Lockdownd* my_new_Lockdownd(idevice* device) { - if (!device) return NULL; - Lockdownd* client = (Lockdownd*) malloc(sizeof(Lockdownd)); - client->dev = device; - client->client = NULL; - if (LOCKDOWN_E_SUCCESS == lockdownd_client_new_with_handshake(device->dev , &(client->client), NULL)) { - return client; - } - else { - free(client); - return NULL; - } -} - -static void my_delete_Lockdownd(Lockdownd* lckd) { - if (lckd) { - lockdownd_client_free(lckd->client); - free(lckd); - } -} - -static MobileSync* my_new_MobileSync(Lockdownd* lckd) { - if (!lckd || !lckd->dev) return NULL; - MobileSync* client = NULL; - uint16_t port = 0; - if (LOCKDOWN_E_SUCCESS == lockdownd_start_service(lckd->client, "com.apple.mobilesync", &port)) { - client = (MobileSync*) malloc(sizeof(MobileSync)); - client->dev = lckd->dev; - client->client = NULL; - mobilesync_client_new(lckd->dev->dev, port, &(client->client)); - } - return client; -} - -static NotificationProxy* my_new_NotificationProxy(Lockdownd* lckd) { - if (!lckd || !lckd->dev) return NULL; - NotificationProxy* client = NULL; - uint16_t port = 0; - if (LOCKDOWN_E_SUCCESS == lockdownd_start_service(lckd->client, "com.apple.mobile.notification_proxy", &port)) { - client = (NotificationProxy*) malloc(sizeof(NotificationProxy)); - client->dev = lckd->dev; - client->client = NULL; - np_client_new(lckd->dev->dev, port, &(client->client)); - } - return client; -} - -static PList::Node* new_node_from_plist(plist_t node) -{ - PList::Node* ret = NULL; - plist_type subtype = plist_get_node_type(node); - switch(subtype) - { - case PLIST_DICT: - ret = new PList::Dictionary(node); - break; - case PLIST_ARRAY: - ret = new PList::Array(node); - break; - case PLIST_BOOLEAN: - ret = new PList::Boolean(node); - break; - case PLIST_UINT: - ret = new PList::Integer(node); - break; - case PLIST_REAL: - ret = new PList::Real(node); - break; - case PLIST_STRING: - ret = new PList::String(node); - break; - case PLIST_DATE: - ret = new PList::Date(node); - break; - case PLIST_DATA: - ret = new PList::Data(node); - break; - default: - break; - } - return ret; -} - -#ifdef SWIGPYTHON -static void NotificationProxyPythonCallback(const char *notification, void* user_data) { - PyObject *func, *arglist; - - func = (PyObject *) user_data; - arglist = Py_BuildValue("(s)",notification); - - PyEval_CallObject(func, arglist); - - Py_DECREF(arglist); -} -#endif - %} - -/* Parse the header file to generate wrappers */ -%include "stdint.i" -%include "cstring.i" -%include "plist/swig/plist.i" - -/* This needs to be here since if it's after - * the structs, SWIG won't pick it up for %extend - */ -#ifdef SWIGPYTHON -%typemap(in) (PyObject *pyfunc) { - if (!PyCallable_Check($input)) { - PyErr_SetString(PyExc_TypeError, "Need a callable object!"); - return NULL; - } - $1 = $input; -} -%typemap(in) (const char **string_list) { - /* Check if it's a list */ - if (PyList_Check($input)) { - int size = PyList_Size($input); - int i = 0; - $1 = (char **) malloc((size+1)*sizeof(char *)); - for (i = 0; i < size; i++) { - PyObject *o = PyList_GetItem($input,i); - if (PyString_Check(o)) { - $1[i] = PyString_AsString(PyList_GetItem($input,i)); - } else { - PyErr_SetString(PyExc_TypeError,"List must contain strings"); - free($1); - return NULL; - } - } - $1[i] = 0; - } else if (PyTuple_Check($input)) { - int size = PyTuple_Size($input); - int i = 0; - $1 = (char **) malloc((size+1)*sizeof(char *)); - for (i = 0; i < size; i++) { - PyObject *o = PyTuple_GetItem($input,i); - if (PyString_Check(o)) { - $1[i] = PyString_AsString(PyTuple_GetItem($input,i)); - } else { - PyErr_SetString(PyExc_TypeError,"List must contain strings"); - free($1); - return NULL; - } - } - $1[i] = 0; - } else { - PyErr_SetString(PyExc_TypeError, "not a list or tuple"); - return NULL; - } -} -%typemap(freearg) (const char **string_list) { - free((char *) $1); -} -#endif - - typedef struct { - idevice_t dev; - } idevice; - -typedef struct { - idevice* dev; - lockdownd_client_t client; -} Lockdownd; - -typedef struct { - idevice* dev; - mobilesync_client_t client; -} MobileSync; - -typedef struct { - idevice* dev; - np_client_t client; -} NotificationProxy; - - -%extend idevice { // Attach these functions to struct idevice - idevice() { - idevice* device = (idevice*) malloc(sizeof(idevice)); - device->dev = NULL; - return device; - } - - ~idevice() { - my_delete_idevice($self); - } - - void set_debug_level(int level) { - idevice_set_debug_level(level); - } - - int init_device_by_uuid(char* uuid) { - if (IDEVICE_E_SUCCESS == idevice_new(&($self->dev), uuid)) - return 1; - return 0; - } - - int init_device() { - if (IDEVICE_E_SUCCESS == idevice_new(&($self->dev), NULL)) - return 1; - return 0; - } - - %newobject get_uuid; - char* get_uuid(){ - char* uuid = NULL; - idevice_get_uuid($self->dev, &uuid); - return uuid; - } - - Lockdownd* get_lockdown_client() { - return my_new_Lockdownd($self); - } -}; - -%extend Lockdownd { // Attach these functions to struct Lockdownd - Lockdownd(idevice* device) { - return my_new_Lockdownd(device); - } - - ~Lockdownd() { - my_delete_Lockdownd($self); - } - - void send(PList::Node* node) { - lockdownd_send($self->client, node->GetPlist()); - } - - PList::Node* receive() { - plist_t node = NULL; - lockdownd_receive($self->client, &node); - return new_node_from_plist(node); - } - - MobileSync* get_mobilesync_client() { - return my_new_MobileSync($self); - } - - NotificationProxy* get_notification_proxy_client() { - return my_new_NotificationProxy($self); - } -}; - -%extend MobileSync { // Attach these functions to struct MobileSync - MobileSync(Lockdownd* lckd) { - return my_new_MobileSync(lckd); - } - - ~MobileSync() { - mobilesync_client_free($self->client); - free($self); - } - - void send(PList::Node* node) { - mobilesync_send($self->client, node->GetPlist()); - } - - PList::Node* receive() { - plist_t node = NULL; - mobilesync_receive($self->client, &node); - return new_node_from_plist(node); - } -}; - -#define NP_SYNC_WILL_START "com.apple.itunes-mobdev.syncWillStart" -#define NP_SYNC_DID_START "com.apple.itunes-mobdev.syncDidStart" -#define NP_SYNC_DID_FINISH "com.apple.itunes-mobdev.syncDidFinish" -#define NP_SYNC_LOCK_REQUEST "com.apple.itunes-mobdev.syncLockRequest" - -%extend NotificationProxy { - NotificationProxy(Lockdownd* lckd) { - return my_new_NotificationProxy(lckd); - } - - ~NotificationProxy() { - np_client_free($self->client); - free($self); - } - - int16_t post_notification(const char* notification) { - return np_post_notification($self->client, notification); - } - - int16_t observe_notification(const char* notification) { - return np_observe_notification($self->client, notification); - } - - int16_t observe_notifications(const char** string_list) { - return np_observe_notifications($self->client, string_list); - } -}; - -#ifdef SWIGPYTHON -%extend NotificationProxy { - int16_t set_callback(PyObject *pyfunc) { - int16_t res; - res = np_set_notify_callback($self->client, NotificationProxyPythonCallback, (void *) pyfunc); - Py_INCREF(pyfunc); - return res; - } -}; -#endif diff --git a/tools/Makefile.am b/tools/Makefile.am index 0a47fdc..b0c2769 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,51 +1,144 @@ -AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) -AM_CFLAGS = $(GLOBAL_CFLAGS) $(libglib2_CFLAGS) $(libgnutls_CFLAGS) $(libtasn1_CFLAGS) $(libgthread2_CFLAGS) $(LFS_CFLAGS) -AM_LDFLAGS = $(libglib2_LIBS) $(libgnutls_LIBS) $(libtasn1_LIBS) $(libgthread2_LIBS) +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + $(ssl_lib_CFLAGS) \ + $(libplist_CFLAGS) \ + $(LFS_CFLAGS) -bin_PROGRAMS = idevice_id ideviceinfo idevicepair idevicesyslog idevicebackup ideviceimagemounter idevicescreenshot ideviceenterrecovery idevicedate +AM_LDFLAGS = \ + $(libplist_LIBS) + +bin_PROGRAMS = \ + idevicebtlogger\ + idevice_id \ + ideviceinfo \ + idevicename \ + idevicepair \ + idevicesyslog \ + ideviceimagemounter \ + idevicescreenshot \ + ideviceenterrecovery \ + idevicedate \ + idevicebackup \ + idevicebackup2 \ + ideviceprovision \ + idevicedebugserverproxy \ + idevicediagnostics \ + idevicedebug \ + idevicedevmodectl \ + idevicenotificationproxy \ + idevicecrashreport \ + idevicesetlocation \ + afcclient + +idevicebtlogger_SOURCES = idevicebtlogger.c +idevicebtlogger_CFLAGS = $(AM_CFLAGS) +idevicebtlogger_LDFLAGS = $(top_builddir)/common/libinternalcommon.la $(AM_LDFLAGS) +idevicebtlogger_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la ideviceinfo_SOURCES = ideviceinfo.c -ideviceinfo_CFLAGS = $(AM_CFLAGS) -ideviceinfo_LDFLAGS = $(AM_LDFLAGS) -ideviceinfo_LDADD = ../src/libimobiledevice.la +ideviceinfo_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +ideviceinfo_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +ideviceinfo_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +idevicename_SOURCES = idevicename.c +idevicename_CFLAGS = $(AM_CFLAGS) +idevicename_LDFLAGS = $(AM_LDFLAGS) +idevicename_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la idevicepair_SOURCES = idevicepair.c -idevicepair_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/src -idevicepair_LDFLAGS = $(AM_LDFLAGS) -idevicepair_LDADD = ../src/libimobiledevice.la +idevicepair_CFLAGS = $(AM_CFLAGS) +idevicepair_LDFLAGS = $(AM_LDFLAGS) $(libusbmuxd_LIBS) $(ssl_lib_LIBS) +idevicepair_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la $(limd_glue_LIBS) idevicesyslog_SOURCES = idevicesyslog.c -idevicesyslog_CFLAGS = $(AM_CFLAGS) -idevicesyslog_LDFLAGS = $(AM_LDFLAGS) -idevicesyslog_LDADD = ../src/libimobiledevice.la +idevicesyslog_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicesyslog_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicesyslog_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la idevice_id_SOURCES = idevice_id.c idevice_id_CFLAGS = $(AM_CFLAGS) idevice_id_LDFLAGS = $(AM_LDFLAGS) -idevice_id_LDADD = ../src/libimobiledevice.la +idevice_id_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la idevicebackup_SOURCES = idevicebackup.c -idevicebackup_CFLAGS = $(AM_CFLAGS) -idevicebackup_LDFLAGS = $(AM_LDFLAGS) -idevicebackup_LDADD = ../src/libimobiledevice.la +idevicebackup_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicebackup_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicebackup_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +idevicebackup2_SOURCES = idevicebackup2.c +idevicebackup2_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicebackup2_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicebackup2_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la ideviceimagemounter_SOURCES = ideviceimagemounter.c -ideviceimagemounter_CFLAGS = $(AM_CFLAGS) -ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS) -ideviceimagemounter_LDADD = ../src/libimobiledevice.la +ideviceimagemounter_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) $(libtatsu_CFLAGS) +ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) $(ssl_lib_LIBS) $(libtatsu_LIBS) +ideviceimagemounter_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la idevicescreenshot_SOURCES = idevicescreenshot.c idevicescreenshot_CFLAGS = $(AM_CFLAGS) idevicescreenshot_LDFLAGS = $(AM_LDFLAGS) -idevicescreenshot_LDADD = ../src/libimobiledevice.la +idevicescreenshot_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la ideviceenterrecovery_SOURCES = ideviceenterrecovery.c ideviceenterrecovery_CFLAGS = $(AM_CFLAGS) ideviceenterrecovery_LDFLAGS = $(AM_LDFLAGS) -ideviceenterrecovery_LDADD = ../src/libimobiledevice.la +ideviceenterrecovery_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la idevicedate_SOURCES = idevicedate.c idevicedate_CFLAGS = $(AM_CFLAGS) idevicedate_LDFLAGS = $(AM_LDFLAGS) -idevicedate_LDADD = ../src/libimobiledevice.la +idevicedate_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +ideviceprovision_SOURCES = ideviceprovision.c +ideviceprovision_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +ideviceprovision_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +ideviceprovision_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +idevicedebugserverproxy_SOURCES = idevicedebugserverproxy.c +idevicedebugserverproxy_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicedebugserverproxy_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicedebugserverproxy_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +idevicediagnostics_SOURCES = idevicediagnostics.c +idevicediagnostics_CFLAGS = $(AM_CFLAGS) +idevicediagnostics_LDFLAGS = $(AM_LDFLAGS) +idevicediagnostics_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +idevicedebug_SOURCES = idevicedebug.c +idevicedebug_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicedebug_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicedebug_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la + +idevicedevmodectl_SOURCES = idevicedevmodectl.c +idevicedevmodectl_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicedevmodectl_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicedevmodectl_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la + +idevicenotificationproxy_SOURCES = idevicenotificationproxy.c +idevicenotificationproxy_CFLAGS = $(AM_CFLAGS) +idevicenotificationproxy_LDFLAGS = $(AM_LDFLAGS) +idevicenotificationproxy_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +idevicecrashreport_SOURCES = idevicecrashreport.c +idevicecrashreport_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicecrashreport_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicecrashreport_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +idevicesetlocation_SOURCES = idevicesetlocation.c +idevicesetlocation_CFLAGS = $(AM_CFLAGS) +idevicesetlocation_LDFLAGS = $(AM_LDFLAGS) +idevicesetlocation_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la + +afcclient_SOURCES = afcclient.c +afcclient_CFLAGS = $(AM_CFLAGS) +afcclient_LDFLAGS = $(AM_LDFLAGS) +if HAVE_READLINE + afcclient_CFLAGS += $(readline_CFLAGS) + afcclient_LDFLAGS += $(readline_LIBS) +endif +afcclient_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(limd_glue_LIBS) diff --git a/tools/afcclient.c b/tools/afcclient.c new file mode 100644 index 0000000..8f49831 --- /dev/null +++ b/tools/afcclient.c @@ -0,0 +1,1664 @@ +/* + * afcclient.c + * Utility to interact with AFC/HoustArrest service on the device + * + * Inspired by https://github.com/emonti/afcclient + * But entirely rewritten from scratch. + * + * Copyright (c) 2023 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "afcclient" + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <getopt.h> +#include <signal.h> +#include <ctype.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> + +#ifdef WIN32 +#include <windows.h> +#include <sys/time.h> +#include <conio.h> +#define sleep(x) Sleep(x*1000) +#define S_IFMT 0170000 /* [XSI] type of file mask */ +#define S_IFIFO 0010000 /* [XSI] named pipe (fifo) */ +#define S_IFCHR 0020000 /* [XSI] character special */ +#define S_IFBLK 0060000 /* [XSI] block special */ +#define S_IFLNK 0120000 /* [XSI] symbolic link */ +#define S_IFSOCK 0140000 /* [XSI] socket */ +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* block special */ +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* char special */ +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* fifo or socket */ +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* symbolic link */ +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */ +#else +#include <sys/time.h> +#include <termios.h> +#endif + +#ifdef HAVE_READLINE +#include <readline/readline.h> +#include <readline/history.h> +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/house_arrest.h> +#include <libimobiledevice/afc.h> +#include <plist/plist.h> + +#include <libimobiledevice-glue/termcolors.h> +#include <libimobiledevice-glue/utils.h> + +#undef st_mtime +#undef st_birthtime +struct afc_file_stat { + uint16_t st_mode; + uint16_t st_nlink; + uint64_t st_size; + uint64_t st_mtime; + uint64_t st_birthtime; + uint32_t st_blocks; +}; + +static char* udid = NULL; +static int connected = 0; +static int use_network = 0; +static idevice_subscription_context_t context = NULL; +static char* curdir = NULL; +static size_t curdir_len = 0; + +static int file_exists(const char* path) +{ + struct stat tst; +#ifdef WIN32 + return (stat(path, &tst) == 0); +#else + return (lstat(path, &tst) == 0); +#endif +} + +static int is_directory(const char* path) +{ + struct stat tst; +#ifdef WIN32 + return (stat(path, &tst) == 0) && S_ISDIR(tst.st_mode); +#else + return (lstat(path, &tst) == 0) && S_ISDIR(tst.st_mode); +#endif +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Interact with AFC/HouseArrest service on a connected device.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device (not recommended!)\n" + " --container <appid> Access container of given app\n" + " --documents <appid> Access Documents directory of given app\n" + " -h, --help prints usage information\n" \ + " -d, --debug enable communication debugging\n" \ + " -v, --version prints version information\n" \ + "\n" + ); + fprintf(is_error ? stderr : stdout, + "\n" \ + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +#ifndef HAVE_READLINE +#ifdef WIN32 +#define BS_CC '\b' +#else +#define BS_CC 0x7f +#define getch getchar +#endif +static void get_input(char *buf, int maxlen) +{ + int len = 0; + int c; + + while ((c = getch())) { + if ((c == '\r') || (c == '\n')) { + break; + } + if (isprint(c)) { + if (len < maxlen-1) + buf[len++] = c; + } else if (c == BS_CC) { + if (len > 0) { + fputs("\b \b", stdout); + len--; + } + } + } + buf[len] = 0; +} +#endif + +#define OPT_DOCUMENTS 1 +#define OPT_CONTAINER 2 + +int stop_requested = 0; + +static void handle_signal(int sig) +{ + stop_requested++; +#ifdef WIN32 + GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); +#else + kill(getpid(), SIGINT); +#endif +} + +static void handle_help(afc_client_t afc, int argc, char** argv) +{ + printf("Available commands:\n"); + printf("help - print list of available commands\n"); + printf("devinfo - print device information\n"); + printf("info PATH - print file attributes of file at PATH\n"); + printf("ls [-l] PATH - print directory contents of PATH\n"); + printf("mv OLD NEW - rename file OLD to NEW\n"); + printf("mkdir PATH - create directory at PATH\n"); + printf("ln [-s] FILE [LINK] - create a (symbolic) link to file named LINKNAME\n"); + printf(" NOTE: This feature has been disabled in newer versions of iOS.\n"); + printf("rm PATH - remove item at PATH\n"); + printf("get [-rf] PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n"); + printf("put [-rf] LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n"); + printf("\n"); +} + +static const char* path_get_basename(const char* path) +{ + const char *p = strrchr(path, '/'); + return p ? p + 1 : path; +} + +static int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y) +{ + /* Perform the carry for the later subtraction by updating y. */ + if (x->tv_usec < y->tv_usec) { + int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; + y->tv_usec -= 1000000 * nsec; + y->tv_sec += nsec; + } + if (x->tv_usec - y->tv_usec > 1000000) { + int nsec = (x->tv_usec - y->tv_usec) / 1000000; + y->tv_usec += 1000000 * nsec; + y->tv_sec -= nsec; + } + /* Compute the time remaining to wait. + tv_usec is certainly positive. */ + result->tv_sec = x->tv_sec - y->tv_sec; + result->tv_usec = x->tv_usec - y->tv_usec; + /* Return 1 if result is negative. */ + return x->tv_sec < y->tv_sec; +} + +struct str_item { + size_t len; + char* str; +}; + +static char* get_absolute_path(const char *path) +{ + if (*path == '/') { + return strdup(path); + } else { + size_t len = curdir_len + 1 + strlen(path) + 1; + char* result = (char*)malloc(len); + if (!strcmp(curdir, "/")) { + snprintf(result, len, "/%s", path); + } else { + snprintf(result, len, "%s/%s", curdir, path); + } + return result; + } +} + +static char* get_realpath(const char* path) +{ + if (!path) return NULL; + + int is_absolute = 0; + if (*path == '/') { + is_absolute = 1; + } + + const char* p = path; + if (is_absolute) { + while (*p == '/') p++; + } + if (*p == '\0') { + return strdup("/"); + } + + int c_count = 1; + const char* start = p; + const char* end = p; + struct str_item* comps = NULL; + + while (*p) { + if (*p == '/') { + p++; + end = p-1; + while (*p == '/') p++; + if (*p == '\0') break; + struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count); + if (!newcomps) { + free(comps); + printf("%s: out of memory?!\n", __func__); + return NULL; + } + comps = newcomps; + char *comp = (char*)malloc(end-start+1); + strncpy(comp, start, end-start); + comp[end-start] = '\0'; + comps[c_count-1].len = end-start; + comps[c_count-1].str = comp; + c_count++; + start = p; + end = p; + } + p++; + } + if (p > start) { + if (start == end) { + end = p; + } + struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count); + if (!newcomps) { + free(comps); + printf("%s: out of memory?!\n", __func__); + return NULL; + } + comps = newcomps; + char *comp = (char*)malloc(end-start+1); + strncpy(comp, start, end-start); + comp[end-start] = '\0'; + comps[c_count-1].len = end-start; + comps[c_count-1].str = comp; + } + + struct str_item* comps_final = (struct str_item*)malloc(sizeof(struct str_item)*(c_count+1)); + int o = 1; + if (is_absolute) { + comps_final[0].len = 1; + comps_final[0].str = (char*)"/"; + } else { + comps_final[0].len = curdir_len; + comps_final[0].str = curdir; + } + size_t o_len = comps_final[0].len; + + for (int i = 0; i < c_count; i++) { + if (!strcmp(comps[i].str, "..")) { + o--; + continue; + } else if (!strcmp(comps[i].str, ".")) { + continue; + } + o_len += comps[i].len; + comps_final[o].str = comps[i].str; + comps_final[o].len = comps[i].len; + o++; + } + + o_len += o; + char* result = (char*)malloc(o_len); + char* presult = result; + for (int i = 0; i < o; i++) { + if (i > 0 && strcmp(comps_final[i-1].str, "/") != 0) { + *presult = '/'; + presult++; + } + strncpy(presult, comps_final[i].str, comps_final[i].len); + presult+=comps_final[i].len; + *presult = '\0'; + } + if (presult == result) { + *presult = '/'; + presult++; + *presult = 0; + } + + for (int i = 0; i < c_count; i++) { + free(comps[i].str); + } + free(comps); + free(comps_final); + + return result; +} + +static void handle_devinfo(afc_client_t afc, int argc, char** argv) +{ + char **info = NULL; + afc_error_t err = afc_get_device_info(afc, &info); + if (err == AFC_E_SUCCESS && info) { + int i; + for (i = 0; info[i]; i += 2) { + printf("%s: %s\n", info[i], info[i+1]); + } + } else { + printf("Error: Failed to get device info: %s (%d)\n", afc_strerror(err), err); + } + afc_dictionary_free(info); +} + +static int get_file_info_stat(afc_client_t afc, const char* path, struct afc_file_stat *stbuf) +{ + char **info = NULL; + afc_error_t ret = afc_get_file_info(afc, path, &info); + memset(stbuf, 0, sizeof(struct afc_file_stat)); + if (ret != AFC_E_SUCCESS) { + return -1; + } else if (!info) { + return -1; + } else { + // get file attributes from info list + int i; + for (i = 0; info[i]; i += 2) { + if (!strcmp(info[i], "st_size")) { + stbuf->st_size = atoll(info[i+1]); + } else if (!strcmp(info[i], "st_blocks")) { + stbuf->st_blocks = atoi(info[i+1]); + } else if (!strcmp(info[i], "st_ifmt")) { + if (!strcmp(info[i+1], "S_IFREG")) { + stbuf->st_mode = S_IFREG; + } else if (!strcmp(info[i+1], "S_IFDIR")) { + stbuf->st_mode = S_IFDIR; + } else if (!strcmp(info[i+1], "S_IFLNK")) { + stbuf->st_mode = S_IFLNK; + } else if (!strcmp(info[i+1], "S_IFBLK")) { + stbuf->st_mode = S_IFBLK; + } else if (!strcmp(info[i+1], "S_IFCHR")) { + stbuf->st_mode = S_IFCHR; + } else if (!strcmp(info[i+1], "S_IFIFO")) { + stbuf->st_mode = S_IFIFO; + } else if (!strcmp(info[i+1], "S_IFSOCK")) { + stbuf->st_mode = S_IFSOCK; + } + } else if (!strcmp(info[i], "st_nlink")) { + stbuf->st_nlink = atoi(info[i+1]); + } else if (!strcmp(info[i], "st_mtime")) { + stbuf->st_mtime = (time_t)(atoll(info[i+1]) / 1000000000); + } else if (!strcmp(info[i], "st_birthtime")) { /* available on iOS 7+ */ + stbuf->st_birthtime = (time_t)(atoll(info[i+1]) / 1000000000); + } + } + afc_dictionary_free(info); + } + return 0; +} + +static void handle_file_info(afc_client_t afc, int argc, char** argv) +{ + if (argc < 1) { + printf("Error: Missing PATH.\n"); + return; + } + + char **info = NULL; + char* abspath = get_absolute_path(argv[0]); + if (!abspath) { + printf("Error: Invalid argument\n"); + return; + } + afc_error_t err = afc_get_file_info(afc, abspath, &info); + if (err == AFC_E_SUCCESS && info) { + int i; + for (i = 0; info[i]; i += 2) { + printf("%s: %s\n", info[i], info[i+1]); + } + } else { + printf("Error: Failed to get file info for %s: %s (%d)\n", argv[0], afc_strerror(err), err); + } + afc_dictionary_free(info); + free(abspath); +} + +static void print_file_info(afc_client_t afc, const char* path, int list_verbose) +{ + struct afc_file_stat st; + get_file_info_stat(afc, path, &st); + if (list_verbose) { + char timebuf[64]; + time_t t = st.st_mtime; + if (S_ISDIR(st.st_mode)) { + printf("drwxr-xr-x"); + } else if (S_ISLNK(st.st_mode)) { + printf("lrwxrwxrwx"); + } else { + if (S_ISFIFO(st.st_mode)) { + printf("f"); + } else if (S_ISBLK(st.st_mode)) { + printf("b"); + } else if (S_ISCHR(st.st_mode)) { + printf("c"); + } else if (S_ISSOCK(st.st_mode)) { + printf("s"); + } else { + printf("-"); + } + printf("rw-r--r--"); + } + printf(" "); + printf("%4d", st.st_nlink); + printf(" "); + printf("mobile"); + printf(" "); + printf("mobile"); + printf(" "); + printf("%10lld", (long long)st.st_size); + printf(" "); +#ifdef WIN32 + strftime(timebuf, 64, "%d %b %Y %H:%M:%S", localtime(&t)); +#else + strftime(timebuf, 64, "%d %h %Y %H:%M:%S", localtime(&t)); +#endif + printf("%s", timebuf); + printf(" "); + } + if (S_ISDIR(st.st_mode)) { + cprintf(FG_CYAN); + } else if (S_ISLNK(st.st_mode)) { + cprintf(FG_MAGENTA); + } else if (S_ISREG(st.st_mode)) { + cprintf(FG_DEFAULT); + } else { + cprintf(FG_YELLOW); + } + cprintf("%s" COLOR_RESET "\n", path_get_basename(path)); +} + +static void handle_list(afc_client_t afc, int argc, char** argv) +{ + const char* path = NULL; + int list_verbose = 0; + if (argc < 1) { + path = curdir; + } else { + if (!strcmp(argv[0], "-l")) { + list_verbose = 1; + if (argc == 2) { + path = argv[1]; + } else { + path = curdir; + } + } else { + path = argv[0]; + } + } + char* abspath = get_absolute_path(path); + if (!abspath) { + printf("Error: Invalid argument\n"); + return; + } + int abspath_is_root = strcmp(abspath, "/") == 0; + size_t abspath_len = (abspath_is_root) ? 0 : strlen(abspath); + char** entries = NULL; + afc_error_t err = afc_read_directory(afc, abspath, &entries); + if (err == AFC_E_READ_ERROR) { + print_file_info(afc, abspath, list_verbose); + return; + } else if (err != AFC_E_SUCCESS) { + printf("Error: Failed to list '%s': %s (%d)\n", path, afc_strerror(err), err); + free(abspath); + return; + } + + char** p = entries; + while (p && *p) { + if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) { + p++; + continue; + } + size_t len = abspath_len + 1 + strlen(*p) + 1; + char* testpath = (char*)malloc(len); + if (abspath_is_root) { + snprintf(testpath, len, "/%s", *p); + } else { + snprintf(testpath, len, "%s/%s", abspath, *p); + } + print_file_info(afc, testpath, list_verbose); + free(testpath); + p++; + } + afc_dictionary_free(entries); + free(abspath); +} + +static void handle_rename(afc_client_t afc, int argc, char** argv) +{ + if (argc != 2) { + printf("Error: Invalid number of arguments\n"); + return; + } + char* srcpath = get_absolute_path(argv[0]); + if (!srcpath) { + printf("Error: Invalid argument\n"); + return; + } + char* dstpath = get_absolute_path(argv[1]); + if (!dstpath) { + free(srcpath); + printf("Error: Invalid argument\n"); + return; + } + afc_error_t err = afc_rename_path(afc, srcpath, dstpath); + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to rename '%s' -> '%s': %s (%d)\n", argv[0], argv[1], afc_strerror(err), err); + } + free(srcpath); + free(dstpath); +} + +static void handle_mkdir(afc_client_t afc, int argc, char** argv) +{ + for (int i = 0; i < argc; i++) { + char* abspath = get_absolute_path(argv[i]); + if (!abspath) { + printf("Error: Invalid argument '%s'\n", argv[i]); + continue; + } + afc_error_t err = afc_make_directory(afc, abspath); + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to create directory '%s': %s (%d)\n", argv[i], afc_strerror(err), err); + } + free(abspath); + } +} + +static void handle_link(afc_client_t afc, int argc, char** argv) +{ + if (argc < 2) { + printf("Error: Invalid number of arguments\n"); + return; + } + afc_link_type_t link_type = AFC_HARDLINK; + if (!strcmp(argv[0], "-s")) { + argc--; + argv++; + link_type = AFC_SYMLINK; + } + if (argc < 1 || argc > 2) { + printf("Error: Invalid number of arguments\n"); + return; + } + const char *link_name = (argc == 1) ? path_get_basename(argv[0]) : argv[1]; + char* abs_link_name = get_absolute_path(link_name); + if (!abs_link_name) { + printf("Error: Invalid argument\n"); + return; + } + afc_error_t err = afc_make_link(afc, link_type, argv[0], link_name); + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to create %s link for '%s' at '%s': %s (%d)\n", (link_type == AFC_HARDLINK) ? "hard" : "symbolic", argv[0], link_name, afc_strerror(err), err); + } +} + +static int ask_yesno(const char* prompt) +{ + int ret = 0; +#ifdef HAVE_READLINE + char* result = readline(prompt); + if (result && result[0] == 'y') { + ret = 1; + } +#else + char cmdbuf[2] = {0, }; + printf("%s", prompt); + fflush(stdout); + get_input(cmdbuf, sizeof(cmdbuf)); + if (cmdbuf[0] == 'y') { + ret = 1; + } +#endif +#ifdef HAVE_READLINE + free(result); +#endif + return ret; +} + +static void handle_remove(afc_client_t afc, int argc, char** argv) +{ + int recursive = 0; + int force = 0; + int i = 0; + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + i++; + break; + } else if (!strcmp(argv[i], "-r")) { + recursive = 1; + } else if (!strcmp(argv[i], "-f")) { + force = 1; + } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) { + recursive = 1; + force = 1; + } else { + break; + } + } + if (recursive && !force) { + if (!ask_yesno("WARNING: This operation will remove all contents of the given path(s). Continue? [y/N] ")) { + printf("Aborted.\n"); + return; + } + } + for ( ; i < argc; i++) { + char* abspath = get_absolute_path(argv[i]); + if (!abspath) { + printf("Error: Invalid argument '%s'\n", argv[i]); + continue; + } + afc_error_t err; + if (recursive) { + err = afc_remove_path_and_contents(afc, abspath); + } else { + err = afc_remove_path(afc, abspath); + } + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to remove '%s': %s (%d)\n", argv[i], afc_strerror(err), err); + } + free(abspath); + } +} + +static uint8_t get_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint64_t file_size, uint8_t force_overwrite) +{ + uint64_t fh = 0; + afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh); + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to open file '%s': %s (%d)\n", srcpath, afc_strerror(err), err); + return 0; + } + if (file_exists(dstpath) && !force_overwrite) { + printf("Error: Failed to overwrite existing file without '-f' option: %s\n", dstpath); + return 0; + } + FILE *f = fopen(dstpath, "wb"); + if (!f) { + printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno)); + return 0; + } + struct timeval t1; + struct timeval t2; + struct timeval tdiff; + size_t bufsize = 0x100000; + char *buf = malloc(bufsize); + size_t total = 0; + int progress = 0; + int lastprog = 0; + if (file_size > 0x400000) { + progress = 1; + gettimeofday(&t1, NULL); + } + uint8_t succeed = 1; + while (err == AFC_E_SUCCESS) { + uint32_t bytes_read = 0; + size_t chunk = 0; + err = afc_file_read(afc, fh, buf, bufsize, &bytes_read); + if (bytes_read == 0) { + break; + } + while (chunk < bytes_read) { + size_t wr = fwrite(buf + chunk, 1, bytes_read - chunk, f); + if (wr == 0) { + if (progress) { + printf("\n"); + } + printf("Error: Failed to write to local file\n"); + succeed = 0; + break; + } + chunk += wr; + } + total += chunk; + if (progress) { + int prog = (int) ((double) total / (double) file_size * 100.0f); + if (prog > lastprog) { + gettimeofday(&t2, NULL); + timeval_subtract(&tdiff, &t2, &t1); + double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000; + printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec); + fflush(stdout); + lastprog = prog; + } + } + } + if (progress) { + printf("\n"); + } + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err); + succeed = 0; + } + free(buf); + fclose(f); + afc_file_close(afc, fh); + return succeed; +} + +static int __mkdir(const char* path) +{ +#ifdef WIN32 + return mkdir(path); +#else + return mkdir(path, 0755); +#endif +} + +static uint8_t get_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_get) +{ + char **info = NULL; + uint64_t file_size = 0; + afc_error_t err = afc_get_file_info(afc, srcpath, &info); + if (err == AFC_E_OBJECT_NOT_FOUND) { + printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err); + return 0; + } + uint8_t is_dir = 0; + if (info) { + char **p = info; + while (p && *p) { + if (!strcmp(*p, "st_size")) { + p++; + file_size = (uint64_t) strtoull(*p, NULL, 10); + } + if (!strcmp(*p, "st_ifmt")) { + p++; + is_dir = !strcmp(*p, "S_IFDIR"); + } + p++; + } + afc_dictionary_free(info); + } + uint8_t succeed = 1; + if (is_dir) { + if (!recursive_get) { + printf("Error: Failed to get a directory without '-r' option: %s\n", srcpath); + return 0; + } + char **entries = NULL; + err = afc_read_directory(afc, srcpath, &entries); + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to list '%s': %s (%d)\n", srcpath, afc_strerror(err), err); + return 0; + } + char **p = entries; + size_t srcpath_len = strlen(srcpath); + uint8_t srcpath_is_root = strcmp(srcpath, "/") == 0; + // if directory exists, check force_overwrite flag + if (is_directory(dstpath)) { + if (!force_overwrite) { + printf("Error: Failed to write into existing directory without '-f': %s\n", dstpath); + return 0; + } + } else if (__mkdir(dstpath) != 0) { + printf("Error: Failed to create directory '%s': %s\n", dstpath, strerror(errno)); + afc_dictionary_free(entries); + return 0; + } + while (p && *p) { + if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) { + p++; + continue; + } + size_t len = srcpath_is_root ? strlen(*p) + 1 : srcpath_len + 1 + strlen(*p) + 1; + char *testpath = (char *) malloc(len); + if (srcpath_is_root) { + snprintf(testpath, len, "/%s", *p); + } else { + snprintf(testpath, len, "%s/%s", srcpath, *p); + } + uint8_t dst_is_root = strcmp(srcpath, "/") == 0; + size_t dst_len = dst_is_root ? strlen(*p) + 1 : strlen(dstpath) + 1 + strlen(*p) + 1; + char *newdst = (char *) malloc(dst_len); + if (dst_is_root) { + snprintf(newdst, dst_len, "/%s", *p); + } else { + snprintf(newdst, dst_len, "%s/%s", dstpath, *p); + } + if (!get_file(afc, testpath, newdst, force_overwrite, recursive_get)) { + succeed = 0; + break; + } + free(testpath); + free(newdst); + p++; + } + afc_dictionary_free(entries); + } else { + succeed = get_single_file(afc, srcpath, dstpath, file_size, force_overwrite); + } + return succeed; +} + +static void handle_get(afc_client_t afc, int argc, char **argv) +{ + if (argc < 1) { + printf("Error: Invalid number of arguments\n"); + return; + } + uint8_t force_overwrite = 0, recursive_get = 0; + char *srcpath = NULL; + char *dstpath = NULL; + int i = 0; + for ( ; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + i++; + break; + } else if (!strcmp(argv[i], "-r")) { + recursive_get = 1; + } else if (!strcmp(argv[i], "-f")) { + force_overwrite = 1; + } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) { + recursive_get = 1; + force_overwrite = 1; + } else { + break; + } + } + if (argc - i == 1) { + char *tmp = strdup(argv[i]); + size_t src_len = strlen(tmp); + if (src_len > 1 && tmp[src_len - 1] == '/') { + tmp[src_len - 1] = '\0'; + } + srcpath = get_absolute_path(tmp); + dstpath = strdup(path_get_basename(tmp)); + free(tmp); + } else if (argc - i == 2) { + char *tmp = strdup(argv[i]); + size_t src_len = strlen(tmp); + if (src_len > 1 && tmp[src_len - 1] == '/') { + tmp[src_len - 1] = '\0'; + } + srcpath = get_absolute_path(tmp); + dstpath = strdup(argv[i + 1]); + size_t dst_len = strlen(dstpath); + if (dst_len > 1 && dstpath[dst_len - 1] == '/') { + dstpath[dst_len - 1] = '\0'; + } + free(tmp); + } else { + printf("Error: Invalid number of arguments\n"); + return; + } + + // target is a directory, put file under this target + if (is_directory(dstpath)) { + const char *basen = path_get_basename(argv[0]); + uint8_t dst_is_root = strcmp(dstpath, "/") == 0; + size_t len = dst_is_root ? (strlen(basen) + 1) : (strlen(dstpath) + 1 + strlen(basen) + 1); + char *newdst = (char *) malloc(len); + if (dst_is_root) { + snprintf(newdst, len, "/%s", basen); + } else { + snprintf(newdst, len, "%s/%s", dstpath, basen); + } + get_file(afc, srcpath, newdst, force_overwrite, recursive_get); + free(srcpath); + free(newdst); + free(dstpath); + } else { + // target is not a dir or does not exist, just try to create or rewrite it + get_file(afc, srcpath, dstpath, force_overwrite, recursive_get); + free(srcpath); + free(dstpath); + } +} + +static uint8_t put_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite) +{ + char **info = NULL; + afc_error_t ret = afc_get_file_info(afc, dstpath, &info); + // file exists, only overwrite with '-f' option was set + if (ret == AFC_E_SUCCESS && info) { + afc_dictionary_free(info); + if (!force_overwrite) { + printf("Error: Failed to write into existing file without '-f' option: %s\n", dstpath); + return 0; + } + } + FILE *f = fopen(srcpath, "rb"); + if (!f) { + printf("Error: Failed to open local file '%s': %s\n", srcpath, strerror(errno)); + return 0; + } + struct timeval t1; + struct timeval t2; + struct timeval tdiff; + struct stat fst; + int progress = 0; + size_t bufsize = 0x100000; + char *buf = malloc(bufsize); + + fstat(fileno(f), &fst); + if (fst.st_size >= 0x400000) { + progress = 1; + gettimeofday(&t1, NULL); + } + size_t total = 0; + int lastprog = 0; + uint64_t fh = 0; + afc_error_t err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh); + uint8_t succeed = 1; + while (err == AFC_E_SUCCESS) { + uint32_t bytes_read = fread(buf, 1, bufsize, f); + if (bytes_read == 0) { + if (!feof(f)) { + if (progress) { + printf("\n"); + } + printf("Error: Failed to read from local file\n"); + succeed = 0; + } + break; + } + uint32_t chunk = 0; + while (chunk < bytes_read) { + uint32_t bytes_written = 0; + err = afc_file_write(afc, fh, buf + chunk, bytes_read - chunk, &bytes_written); + if (err != AFC_E_SUCCESS) { + if (progress) { + printf("\n"); + } + printf("Error: Failed to write to device file\n"); + succeed = 0; + break; + } + chunk += bytes_written; + } + total += chunk; + if (progress) { + int prog = (int) ((double) total / (double) fst.st_size * 100.0f); + if (prog > lastprog) { + gettimeofday(&t2, NULL); + timeval_subtract(&tdiff, &t2, &t1); + double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000; + printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec); + fflush(stdout); + lastprog = prog; + } + } + } + free(buf); + afc_file_close(afc, fh); + fclose(f); + return succeed; +} + +static uint8_t put_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_put) +{ + if (is_directory(srcpath)) { + if (!recursive_put) { + printf("Error: Failed to put directory without '-r' option: %s\n", srcpath); + return 0; + } + char **info = NULL; + afc_error_t err = afc_get_file_info(afc, dstpath, &info); + //create if target directory does not exist + afc_dictionary_free(info); + if (err == AFC_E_OBJECT_NOT_FOUND) { + err = afc_make_directory(afc, dstpath); + if (err != AFC_E_SUCCESS) { + printf("Error: Failed to create directory '%s': %s (%d)\n", dstpath, afc_strerror(err), err); + return 0; + } + } else if (!force_overwrite) { + printf("Error: Failed to put existing directory without '-f' option: %s\n", dstpath); + return 0; + } + afc_get_file_info(afc, dstpath, &info); + uint8_t is_dir = 0; + if (info) { + char **p = info; + while (p && *p) { + if (!strcmp(*p, "st_ifmt")) { + p++; + is_dir = !strcmp(*p, "S_IFDIR"); + break; + } + p++; + } + afc_dictionary_free(info); + } + if (!is_dir) { + printf("Error: Failed to create or access directory: '%s'\n", dstpath); + return 0; + } + + // walk dir recursively to put files + DIR *cur_dir = opendir(srcpath); + if (cur_dir) { + struct dirent *ep; + while ((ep = readdir(cur_dir))) { + if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { + continue; + } + char *fpath = string_build_path(srcpath, ep->d_name, NULL); + if (fpath) { + uint8_t dst_is_root = strcmp(dstpath, "/") == 0; + size_t len = dst_is_root ? strlen(ep->d_name) + 1 : strlen(dstpath) + 1 + strlen(ep->d_name) + 1; + char *newdst = (char *) malloc(len); + if (dst_is_root) { + snprintf(newdst, len, "/%s", ep->d_name); + } else { + snprintf(newdst, len, "%s/%s", dstpath, ep->d_name); + } + if (!put_file(afc, fpath, newdst, force_overwrite, recursive_put)) { + free(newdst); + free(fpath); + return 0; + } + free(newdst); + free(fpath); + } + } + closedir(cur_dir); + } else { + printf("Error: Failed to visit directory: '%s': %s\n", srcpath, strerror(errno)); + return 0; + } + } else { + return put_single_file(afc, srcpath, dstpath, force_overwrite); + } + return 1; +} + +static void handle_put(afc_client_t afc, int argc, char **argv) +{ + if (argc < 1) { + printf("Error: Invalid number of arguments\n"); + return; + } + int i = 0; + uint8_t force_overwrite = 0, recursive_put = 0; + for ( ; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + i++; + break; + } else if (!strcmp(argv[i], "-r")) { + recursive_put = 1; + } else if (!strcmp(argv[i], "-f")) { + force_overwrite = 1; + } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) { + recursive_put = 1; + force_overwrite = 1; + } else { + break; + } + } + if (i >= argc) { + printf("Error: Invalid number of arguments\n"); + return; + } + char *srcpath = strdup(argv[i]); + size_t src_len = strlen(srcpath); + if (src_len > 1 && srcpath[src_len - 1] == '/') { + srcpath[src_len - 1] = '\0'; + } + char *dstpath = NULL; + if (argc - i == 1) { + dstpath = get_absolute_path(path_get_basename(srcpath)); + } else if (argc - i == 2) { + char *tmp = strdup(argv[i + 1]); + size_t dst_len = strlen(tmp); + if (dst_len > 1 && tmp[dst_len - 1] == '/') { + tmp[dst_len - 1] = '\0'; + } + dstpath = get_absolute_path(tmp); + free(tmp); + } else { + printf("Error: Invalid number of arguments\n"); + return; + } + char **info = NULL; + afc_error_t err = afc_get_file_info(afc, dstpath, &info); + // target does not exist, put directly + if (err == AFC_E_OBJECT_NOT_FOUND) { + put_file(afc, srcpath, dstpath, force_overwrite, recursive_put); + free(srcpath); + free(dstpath); + } else { + uint8_t is_dir = 0; + if (info) { + char **p = info; + while (p && *p) { + if (!strcmp(*p, "st_ifmt")) { + p++; + is_dir = !strcmp(*p, "S_IFDIR"); + break; + } + p++; + } + afc_dictionary_free(info); + } + // target is a directory, try to put under this directory + if (is_dir) { + const char *basen = path_get_basename(srcpath); + uint8_t dst_is_root = strcmp(dstpath, "/") == 0; + size_t len = dst_is_root ? strlen(basen) + 1 : strlen(dstpath) + 1 + strlen(basen) + 1; + char *newdst = (char *) malloc(len); + if (dst_is_root) { + snprintf(newdst, len, "/%s", basen); + } else { + snprintf(newdst, len, "%s/%s", dstpath, basen); + } + free(dstpath); + dstpath = get_absolute_path(newdst); + free(newdst); + put_file(afc, srcpath, dstpath, force_overwrite, recursive_put); + } else { + //target is common file, rewrite it + put_file(afc, srcpath, dstpath, force_overwrite, recursive_put); + } + free(srcpath); + free(dstpath); + } +} + +static void handle_pwd(afc_client_t afc, int argc, char** argv) +{ + printf("%s\n", curdir); +} + +static void handle_cd(afc_client_t afc, int argc, char** argv) +{ + if (argc != 1) { + printf("Error: Invalid number of arguments\n"); + return; + } + + if (!strcmp(argv[0], ".")) { + return; + } + + if (!strcmp(argv[0], "..")) { + if (!strcmp(curdir, "/")) { + return; + } + char *p = strrchr(curdir, '/'); + if (!p) { + strcpy(curdir, "/"); + return; + } + if (p == curdir) { + *(p+1) = '\0'; + } else { + *p = '\0'; + } + return; + } + + char* path = get_realpath(argv[0]); + int is_dir = 0; + char **info = NULL; + afc_error_t err = afc_get_file_info(afc, path, &info); + if (err == AFC_E_SUCCESS && info) { + int i; + for (i = 0; info[i]; i += 2) { + if (!strcmp(info[i], "st_ifmt")) { + if (!strcmp(info[i+1], "S_IFDIR")) { + is_dir = 1; + } + break; + } + } + afc_dictionary_free(info); + } else { + printf("Error: Failed to get file info for %s: %s (%d)\n", path, afc_strerror(err), err); + free(path); + return; + } + + if (!is_dir) { + printf("Error: '%s' is not a valid directory\n", path); + free(path); + return; + } + + free(curdir); + curdir = path; + curdir_len = strlen(curdir); +} + +static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline) +{ + char **argv = NULL; + int argc = 0; + size_t maxlen = strlen(cmdline); + const char* pos = cmdline; + const char* qpos = NULL; + char *tmpbuf = NULL; + int tmplen = 0; + int is_error = 0; + + /* skip initial whitespace */ + while (isspace(*pos)) pos++; + maxlen -= (pos - cmdline); + + tmpbuf = (char*)malloc(maxlen+1); + + while (!is_error) { + if (*pos == '\\') { + pos++; + switch (*pos) { + case '"': + case '\'': + case '\\': + case ' ': + tmpbuf[tmplen++] = *pos; + pos++; + break; + default: + printf("Error: Invalid escape sequence\n"); + is_error++; + break; + } + } else if (*pos == '"' || *pos == '\'') { + if (!qpos) { + qpos = pos; + } else { + qpos = NULL; + } + pos++; + } else if (*pos == '\0' || (!qpos && isspace(*pos))) { + tmpbuf[tmplen] = '\0'; + if (*pos == '\0' && qpos) { + printf("Error: Unmatched `%c`\n", *qpos); + is_error++; + break; + } + char** new_argv = (char**)realloc(argv, (argc+1)*sizeof(char*)); + if (new_argv == NULL) { + printf("Error: Out of memory?!\n"); + is_error++; + break; + } + argv = new_argv; + /* shrink buffer to actual argument size */ + argv[argc] = (char*)realloc(tmpbuf, tmplen+1); + if (!argv[argc]) { + printf("Error: Out of memory?!\n"); + is_error++; + break; + } + argc++; + tmpbuf = NULL; + if (*pos == '\0') { + break; + } + maxlen -= tmplen; + tmpbuf = (char*)malloc(maxlen+1); + tmplen = 0; + while (isspace(*pos)) pos++; + } else { + tmpbuf[tmplen++] = *pos; + pos++; + } + } + if (tmpbuf) { + free(tmpbuf); + } + if (is_error) { + int i; + for (i = 0; argv && i < argc; i++) free(argv[i]); + free(argv); + return; + } + + *p_argv = argv; + *p_argc = argc; +} + +static int process_args(afc_client_t afc, int argc, char** argv) +{ + if (!strcmp(argv[0], "q") || !strcmp(argv[0], "quit") || !strcmp(argv[0], "exit")) { + return -1; + } + else if (!strcmp(argv[0], "help")) { + handle_help(afc, argc, argv); + } + else if (!strcmp(argv[0], "devinfo") || !strcmp(argv[0], "deviceinfo")) { + handle_devinfo(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "info")) { + handle_file_info(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) { + handle_list(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) { + handle_rename(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "mkdir")) { + handle_mkdir(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "ln")) { + handle_link(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) { + handle_remove(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "get")) { + handle_get(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "put")) { + handle_put(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "pwd")) { + handle_pwd(afc, argc-1, argv+1); + } + else if (!strcmp(argv[0], "cd")) { + handle_cd(afc, argc-1, argv+1); + } + else { + printf("Unknown command '%s'. Type 'help' to get a list of available commands.\n", argv[0]); + } + return 0; +} + +static void start_cmdline(afc_client_t afc) +{ + while (!stop_requested) { + int argc = 0; + char **argv = NULL; + char prompt[128]; + int plen = curdir_len; + char *ppath = curdir; + int plim = (int)(sizeof(prompt)/2)-8; + if (plen > plim) { + ppath = curdir + (plen - plim); + plen = plim; + } + snprintf(prompt, 128, FG_BLACK BG_LIGHT_GRAY "afc:" COLOR_RESET FG_BRIGHT_YELLOW BG_BLUE "%.*s" COLOR_RESET " > ", plen, ppath); +#ifdef HAVE_READLINE + char* cmd = readline(prompt); + if (!cmd || !*cmd) { + free(cmd); + continue; + } + add_history(cmd); + parse_cmdline(&argc, &argv, cmd); +#else + char cmdbuf[4096]; + printf("%s", prompt); + fflush(stdout); + get_input(cmdbuf, sizeof(cmdbuf)); + parse_cmdline(&argc, &argv, cmdbuf); +#endif +#ifdef HAVE_READLINE + free(cmd); +#endif + /* process arguments */ + if (argv && argv[0]) { + if (process_args(afc, argc, argv) < 0) { + break; + } + } + } +} + +static void device_event_cb(const idevice_event_t* event, void* userdata) +{ + if (use_network && event->conn_type != CONNECTION_NETWORK) { + return; + } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) { + return; + } + if (event->event == IDEVICE_DEVICE_ADD) { + if (!udid) { + udid = strdup(event->udid); + } + if (strcmp(udid, event->udid) == 0) { + connected = 1; + } + } else if (event->event == IDEVICE_DEVICE_REMOVE) { + if (strcmp(udid, event->udid) == 0) { + connected = 0; + printf("\n[disconnected]\n"); + handle_signal(SIGINT); + } + } +} + +int main(int argc, char** argv) +{ + const char* appid = NULL; + int ret = 0; + idevice_t device = NULL; + lockdownd_client_t lockdown = NULL; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; + lockdownd_service_descriptor_t service = NULL; + afc_client_t afc = NULL; + house_arrest_client_t house_arrest = NULL; + const char* service_name = AFC_SERVICE_NAME; + int use_container = 0; + + int c = 0; + const struct option longopts[] = { + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "help", no_argument, NULL, 'h' }, + { "debug", no_argument, NULL, 'd' }, + { "version", no_argument, NULL, 'v' }, + { "documents", required_argument, NULL, OPT_DOCUMENTS }, + { "container", required_argument, NULL, OPT_CONTAINER }, + { NULL, 0, NULL, 0} + }; + + signal(SIGTERM, handle_signal); +#ifndef WIN32 + signal(SIGQUIT, handle_signal); + signal(SIGPIPE, SIG_IGN); +#endif + + while ((c = getopt_long(argc, argv, "du:nhv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = strdup(optarg); + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s", TOOL_NAME, PACKAGE_VERSION); +#ifdef HAVE_READLINE + printf(" (readline)"); +#endif + printf("\n"); + return 0; + case OPT_DOCUMENTS: + if (!*optarg) { + fprintf(stderr, "ERROR: '--documents' requires a non-empty app ID!\n"); + print_usage(argc, argv, 1); + return 2; + } + appid = optarg; + use_container = 0; + break; + case OPT_CONTAINER: + if (!*optarg) { + fprintf(stderr, "ERROR: '--container' requires a not-empty app ID!\n"); + print_usage(argc, argv, 1); + return 2; + } + appid = optarg; + use_container = 1; + break; + default: + print_usage(argc, argv, 1); + return 2; + } + } + + argc -= optind; + argv += optind; + + int num = 0; + idevice_info_t *devices = NULL; + idevice_get_device_list_extended(&devices, &num); + int count = 0; + for (int i = 0; i < num; i++) { + if (devices[i]->conn_type == CONNECTION_NETWORK && use_network) { + count++; + } else if (devices[i]->conn_type == CONNECTION_USBMUXD) { + count++; + } + } + idevice_device_list_extended_free(devices); + if (count == 0) { + fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n"); + return 1; + } + + idevice_events_subscribe(&context, device_event_cb, NULL); + + while (!connected && !stop_requested) { +#ifdef WIN32 + Sleep(100); +#else + usleep(100000); +#endif + } + if (stop_requested) { + return 0; + } + + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + fprintf(stderr, "ERROR: Device %s not found!\n", udid); + } else { + fprintf(stderr, "ERROR: No device found!\n"); + } + return 1; + } + + do { + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret); + ret = 1; + break; + } + + if (appid) { + service_name = HOUSE_ARREST_SERVICE_NAME; + } + + ldret = lockdownd_start_service(lockdown, service_name, &service); + if (ldret != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Failed to start service %s: %s (%d)\n", service_name, lockdownd_strerror(ldret), ldret); + ret = 1; + break; + } + + if (appid) { + house_arrest_client_new(device, service, &house_arrest); + if (!house_arrest) { + fprintf(stderr, "Could not start document sharing service!\n"); + ret = 1; + break; + } + + if (house_arrest_send_command(house_arrest, use_container ? "VendContainer": "VendDocuments", appid) != HOUSE_ARREST_E_SUCCESS) { + fprintf(stderr, "Could not send house_arrest command!\n"); + ret = 1; + break; + } + + plist_t dict = NULL; + if (house_arrest_get_result(house_arrest, &dict) != HOUSE_ARREST_E_SUCCESS) { + fprintf(stderr, "Could not get result from document sharing service!\n"); + break; + } + plist_t node = plist_dict_get_item(dict, "Error"); + if (node) { + char *str = NULL; + plist_get_string_val(node, &str); + fprintf(stderr, "ERROR: %s\n", str); + if (str && !strcmp(str, "InstallationLookupFailed")) { + fprintf(stderr, "The App '%s' is either not present on the device, or the 'UIFileSharingEnabled' key is not set in its Info.plist. Starting with iOS 8.3 this key is mandatory to allow access to an app's Documents folder.\n", appid); + } + free(str); + plist_free(dict); + break; + } + plist_free(dict); + afc_client_new_from_house_arrest_client(house_arrest, &afc); + } else { + afc_client_new(device, service, &afc); + } + lockdownd_service_descriptor_free(service); + lockdownd_client_free(lockdown); + lockdown = NULL; + + curdir = strdup("/"); + curdir_len = 1; + + if (argc > 0) { + // command line mode + process_args(afc, argc, argv); + } else { + // interactive mode + start_cmdline(afc); + } + + } while (0); + + if (afc) { + afc_client_free(afc); + } + if (lockdown) { + lockdownd_client_free(lockdown); + } + idevice_free(device); + + return ret; +} diff --git a/tools/idevice_id.c b/tools/idevice_id.c index 1facb60..540a6f2 100644 --- a/tools/idevice_id.c +++ b/tools/idevice_id.c @@ -1,6 +1,34 @@ +/* + * idevice_id.c + * Prints device name or a list of attached devices + * + * Copyright (C) 2010-2018 Nikias Bassen <nikias@gmx.li> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevice_id" + #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <getopt.h> #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> @@ -8,100 +36,139 @@ #define MODE_SHOW_ID 1 #define MODE_LIST_DEVICES 2 -static void print_usage(int argc, char **argv) +static void print_usage(int argc, char **argv, int is_error) { - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS] [UUID]\n", (name ? name + 1: argv[0])); - printf("Prints device name or a list of attached iPhone/iPod Touch devices.\n\n"); - printf(" The UUID is a 40-digit hexadecimal number of the device\n"); - printf(" for which the name should be retrieved.\n\n"); - printf(" -l, --list\t\tlist UUID of all attached devices\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [UDID]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "List attached devices or print device name of given device.\n" + "\n" \ + " If UDID is given, the name of the connected device with that UDID" + " will be retrieved.\n" + "\n" \ + "OPTIONS:\n" + " -l, --list list UDIDs of all devices attached via USB\n" + " -n, --network list UDIDs of all devices available via network\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); } int main(int argc, char **argv) { - idevice_t phone = NULL; + idevice_t device = NULL; lockdownd_client_t client = NULL; - char **dev_list = NULL; - char *devname = NULL; + idevice_info_t *dev_list = NULL; + char *device_name = NULL; int ret = 0; int i; - int mode = MODE_SHOW_ID; - char uuid[41]; - uuid[0] = 0; + int mode = MODE_LIST_DEVICES; + int include_usb = 0; + int include_network = 0; + const char* udid = NULL; - /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "list", no_argument, NULL, 'l' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + + while ((c = getopt_long(argc, argv, "dhlnv", longopts, NULL)) != -1) { + switch (c) { + case 'd': idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--list")) { + break; + case 'h': + print_usage(argc, argv, 0); + exit(EXIT_SUCCESS); + case 'l': mode = MODE_LIST_DEVICES; - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); + include_usb = 1; + break; + case 'n': + mode = MODE_LIST_DEVICES; + include_network = 1; + break; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); return 0; + default: + print_usage(argc, argv, 1); + exit(EXIT_FAILURE); } } + argc -= optind; + argv += optind; - /* check if uuid was passed */ - if (mode == MODE_SHOW_ID) { - i--; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; - } - strcpy(uuid, argv[i]); + if (argc == 1) { + mode = MODE_SHOW_ID; + } else if (argc == 0 && optind == 1) { + include_usb = 1; + include_network = 1; } + udid = argv[0]; switch (mode) { case MODE_SHOW_ID: - idevice_new(&phone, uuid); - if (!phone) { - fprintf(stderr, "ERROR: No device with UUID=%s attached.\n", uuid); + idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX | IDEVICE_LOOKUP_NETWORK); + if (!device) { + fprintf(stderr, "ERROR: No device with UDID %s attached.\n", udid); return -2; } - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(phone, &client, "idevice_id")) { - idevice_free(phone); + if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(device, &client, TOOL_NAME)) { + idevice_free(device); fprintf(stderr, "ERROR: Connecting to device failed!\n"); return -2; } - if ((LOCKDOWN_E_SUCCESS != lockdownd_get_device_name(client, &devname)) || !devname) { + if ((LOCKDOWN_E_SUCCESS != lockdownd_get_device_name(client, &device_name)) || !device_name) { fprintf(stderr, "ERROR: Could not get device name!\n"); ret = -2; } lockdownd_client_free(client); - idevice_free(phone); + idevice_free(device); if (ret == 0) { - printf("%s\n", devname); + printf("%s\n", device_name); } - if (devname) { - free(devname); + if (device_name) { + free(device_name); } + break; - return ret; case MODE_LIST_DEVICES: default: - if (idevice_get_device_list(&dev_list, &i) < 0) { + if (idevice_get_device_list_extended(&dev_list, &i) < 0) { fprintf(stderr, "ERROR: Unable to retrieve device list!\n"); return -1; } for (i = 0; dev_list[i] != NULL; i++) { - printf("%s\n", dev_list[i]); + if (dev_list[i]->conn_type == CONNECTION_USBMUXD && !include_usb) continue; + if (dev_list[i]->conn_type == CONNECTION_NETWORK && !include_network) continue; + printf("%s", dev_list[i]->udid); + if (include_usb && include_network) { + if (dev_list[i]->conn_type == CONNECTION_NETWORK) { + printf(" (Network)"); + } else { + printf(" (USB)"); + } + } + printf("\n"); } - idevice_device_list_free(dev_list); - return 0; + idevice_device_list_extended_free(dev_list); + break; } + return ret; } diff --git a/tools/idevicebackup.c b/tools/idevicebackup.c index 867eaad..c0537b8 100644 --- a/tools/idevicebackup.c +++ b/tools/idevicebackup.c @@ -9,31 +9,41 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicebackup" + #include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> #include <signal.h> -#include <glib.h> -#include <gcrypt.h> +#include <getopt.h> #include <unistd.h> +#include <ctype.h> +#include <time.h> #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> #include <libimobiledevice/mobilebackup.h> #include <libimobiledevice/notification_proxy.h> #include <libimobiledevice/afc.h> +#include <libimobiledevice-glue/sha.h> +#include <libimobiledevice-glue/utils.h> +#include <plist/plist.h> #define MOBILEBACKUP_SERVICE_NAME "com.apple.mobilebackup" #define NP_SERVICE_NAME "com.apple.mobile.notification_proxy" @@ -41,9 +51,14 @@ #define LOCK_ATTEMPTS 50 #define LOCK_WAIT 200000 +#ifdef WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#endif + static mobilebackup_client_t mobilebackup = NULL; static lockdownd_client_t client = NULL; -static idevice_t phone = NULL; +static idevice_t device = NULL; static int quit_flag = 0; @@ -53,22 +68,12 @@ enum cmd_mode { CMD_LEAVE }; -enum plist_format_t { - PLIST_FORMAT_XML, - PLIST_FORMAT_BINARY -}; - enum device_link_file_status_t { DEVICE_LINK_FILE_STATUS_NONE = 0, DEVICE_LINK_FILE_STATUS_HUNK, DEVICE_LINK_FILE_STATUS_LAST_HUNK }; -static void sha1_of_data(const char *input, uint32_t size, unsigned char *hash_out) -{ - gcry_md_hash_buffer(GCRY_MD_SHA1, hash_out, input, size); -} - static int compare_hash(const unsigned char *hash1, const unsigned char *hash2, int hash_len) { int i; @@ -82,51 +87,47 @@ static int compare_hash(const unsigned char *hash1, const unsigned char *hash2, static void compute_datahash(const char *path, const char *destpath, uint8_t greylist, const char *domain, const char *appid, const char *version, unsigned char *hash_out) { - gcry_md_hd_t hd = NULL; - gcry_md_open(&hd, GCRY_MD_SHA1, 0); - if (!hd) { - printf("ERROR: Could not initialize libgcrypt/SHA1\n"); - return; - } - gcry_md_reset(hd); - + sha1_context sha1; + sha1_init(&sha1); FILE *f = fopen(path, "rb"); if (f) { unsigned char buf[16384]; size_t len; while ((len = fread(buf, 1, 16384, f)) > 0) { - gcry_md_write(hd, buf, len); + sha1_update(&sha1, buf, len); } fclose(f); - gcry_md_write(hd, destpath, strlen(destpath)); - gcry_md_write(hd, ";", 1); + sha1_update(&sha1, destpath, strlen(destpath)); + sha1_update(&sha1, ";", 1); + if (greylist == 1) { - gcry_md_write(hd, "true", 4); + sha1_update(&sha1, "true", 4); } else { - gcry_md_write(hd, "false", 5); + sha1_update(&sha1, "false", 5); } - gcry_md_write(hd, ";", 1); + sha1_update(&sha1, ";", 1); + if (domain) { - gcry_md_write(hd, domain, strlen(domain)); + sha1_update(&sha1, domain, strlen(domain)); } else { - gcry_md_write(hd, "(null)", 6); + sha1_update(&sha1, "(null)", 6); } - gcry_md_write(hd, ";", 1); + sha1_update(&sha1, ";", 1); + if (appid) { - gcry_md_write(hd, appid, strlen(appid)); + sha1_update(&sha1, appid, strlen(appid)); } else { - gcry_md_write(hd, "(null)", 6); + sha1_update(&sha1, "(null)", 6); } - gcry_md_write(hd, ";", 1); + sha1_update(&sha1, ";", 1); + if (version) { - gcry_md_write(hd, version, strlen(version)); + sha1_update(&sha1, version, strlen(version)); } else { - gcry_md_write(hd, "(null)", 6); + sha1_update(&sha1, "(null)", 6); } - unsigned char *newhash = gcry_md_read(hd, GCRY_MD_SHA1); - memcpy(hash_out, newhash, 20); + sha1_final(&sha1, hash_out); } - gcry_md_close(hd); } static void print_hash(const unsigned char *hash, int len) @@ -147,14 +148,12 @@ static void notify_cb(const char *notification, void *userdata) } } -static plist_t mobilebackup_factory_info_plist_new() +static plist_t mobilebackup_factory_info_plist_new(const char* udid) { /* gather data from lockdown */ - GTimeVal tv = {0, 0}; plist_t value_node = NULL; plist_t root_node = NULL; - char *uuid = NULL; - char *uuid_uppercase = NULL; + char *udid_uppercase = NULL; plist_t ret = plist_new_dict(); @@ -163,45 +162,42 @@ static plist_t mobilebackup_factory_info_plist_new() /* set fields we understand */ value_node = plist_dict_get_item(root_node, "BuildVersion"); - plist_dict_insert_item(ret, "Build Version", plist_copy(value_node)); + plist_dict_set_item(ret, "Build Version", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "DeviceName"); - plist_dict_insert_item(ret, "Device Name", plist_copy(value_node)); - plist_dict_insert_item(ret, "Display Name", plist_copy(value_node)); + plist_dict_set_item(ret, "Device Name", plist_copy(value_node)); + plist_dict_set_item(ret, "Display Name", plist_copy(value_node)); /* FIXME: How is the GUID generated? */ - plist_dict_insert_item(ret, "GUID", plist_new_string("---")); + plist_dict_set_item(ret, "GUID", plist_new_string("---")); value_node = plist_dict_get_item(root_node, "InternationalMobileEquipmentIdentity"); if (value_node) - plist_dict_insert_item(ret, "IMEI", plist_copy(value_node)); + plist_dict_set_item(ret, "IMEI", plist_copy(value_node)); - g_get_current_time(&tv); - plist_dict_insert_item(ret, "Last Backup Date", plist_new_date(tv.tv_sec, tv.tv_usec)); + plist_dict_set_item(ret, "Last Backup Date", plist_new_date(time(NULL) - MAC_EPOCH, 0)); value_node = plist_dict_get_item(root_node, "ProductType"); - plist_dict_insert_item(ret, "Product Type", plist_copy(value_node)); + plist_dict_set_item(ret, "Product Type", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "ProductVersion"); - plist_dict_insert_item(ret, "Product Version", plist_copy(value_node)); + plist_dict_set_item(ret, "Product Version", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "SerialNumber"); - plist_dict_insert_item(ret, "Serial Number", plist_copy(value_node)); + plist_dict_set_item(ret, "Serial Number", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "UniqueDeviceID"); - idevice_get_uuid(phone, &uuid); - plist_dict_insert_item(ret, "Target Identifier", plist_new_string(uuid)); + plist_dict_set_item(ret, "Target Identifier", plist_new_string(udid)); /* uppercase */ - uuid_uppercase = g_ascii_strup(uuid, -1); - plist_dict_insert_item(ret, "Unique Identifier", plist_new_string(uuid_uppercase)); - free(uuid_uppercase); - free(uuid); + udid_uppercase = string_toupper((char*)udid); + plist_dict_set_item(ret, "Unique Identifier", plist_new_string(udid_uppercase)); + free(udid_uppercase); /* FIXME: Embed files as <data> nodes */ plist_t files = plist_new_dict(); - plist_dict_insert_item(ret, "iTunes Files", files); - plist_dict_insert_item(ret, "iTunes Version", plist_new_string("9.0.2")); + plist_dict_set_item(ret, "iTunes Files", files); + plist_dict_set_item(ret, "iTunes Version", plist_new_string("9.0.2")); plist_free(root_node); @@ -210,102 +206,17 @@ static plist_t mobilebackup_factory_info_plist_new() static void mobilebackup_info_update_last_backup_date(plist_t info_plist) { - GTimeVal tv = {0, 0}; plist_t node = NULL; if (!info_plist) return; - g_get_current_time(&tv); node = plist_dict_get_item(info_plist, "Last Backup Date"); - plist_set_date_val(node, tv.tv_sec, tv.tv_usec); + plist_set_date_val(node, time(NULL) - MAC_EPOCH, 0); node = NULL; } -static void buffer_read_from_filename(const char *filename, char **buffer, uint64_t *length) -{ - FILE *f; - uint64_t size; - - *length = 0; - - f = fopen(filename, "rb"); - if (!f) { - return; - } - - fseek(f, 0, SEEK_END); - size = ftell(f); - rewind(f); - - if (size == 0) { - return; - } - - *buffer = (char*)malloc(sizeof(char)*size); - fread(*buffer, sizeof(char), size, f); - fclose(f); - - *length = size; -} - -static void buffer_write_to_filename(const char *filename, const char *buffer, uint64_t length) -{ - FILE *f; - - f = fopen(filename, "ab"); - fwrite(buffer, sizeof(char), length, f); - fclose(f); -} - -static int plist_read_from_filename(plist_t *plist, const char *filename) -{ - char *buffer = NULL; - uint64_t length; - - if (!filename) - return 0; - - buffer_read_from_filename(filename, &buffer, &length); - - if (!buffer) { - return 0; - } - - if ((length > 8) && (memcmp(buffer, "bplist00", 8) == 0)) { - plist_from_bin(buffer, length, plist); - } else { - plist_from_xml(buffer, length, plist); - } - - free(buffer); - - return 1; -} - -static int plist_write_to_filename(plist_t plist, const char *filename, enum plist_format_t format) -{ - char *buffer = NULL; - uint32_t length; - - if (!plist || !filename) - return 0; - - if (format == PLIST_FORMAT_XML) - plist_to_xml(plist, &buffer, &length); - else if (format == PLIST_FORMAT_BINARY) - plist_to_bin(plist, &buffer, &length); - else - return 0; - - buffer_write_to_filename(filename, buffer, length); - - free(buffer); - - return 1; -} - static int plist_strcmp(plist_t node, const char *str) { char *buffer = NULL; @@ -321,11 +232,14 @@ static int plist_strcmp(plist_t node, const char *str) return ret; } -static gchar *mobilebackup_build_path(const char *backup_directory, const char *name, const char *extension) +static char *mobilebackup_build_path(const char *backup_directory, const char *name, const char *extension) { - gchar *filename = g_strconcat(name, extension, NULL); - gchar *path = g_build_path(G_DIR_SEPARATOR_S, backup_directory, filename, NULL); - g_free(filename); + char* filename = (char*)malloc(strlen(name)+(extension == NULL ? 0: strlen(extension))+1); + strcpy(filename, name); + if (extension != NULL) + strcat(filename, extension); + char *path = string_build_path(backup_directory, filename, NULL); + free(filename); return path; } @@ -333,28 +247,28 @@ static void mobilebackup_write_status(const char *path, int status) { struct stat st; plist_t status_plist = plist_new_dict(); - plist_dict_insert_item(status_plist, "Backup Success", plist_new_bool(status)); - gchar *file_path = mobilebackup_build_path(path, "Status", ".plist"); + plist_dict_set_item(status_plist, "Backup Success", plist_new_bool(status)); + char *file_path = mobilebackup_build_path(path, "Status", ".plist"); if (stat(file_path, &st) == 0) remove(file_path); - plist_write_to_filename(status_plist, file_path, PLIST_FORMAT_XML); + plist_write_to_file(status_plist, file_path, PLIST_FORMAT_XML, 0); plist_free(status_plist); status_plist = NULL; - g_free(file_path); + free(file_path); } static int mobilebackup_read_status(const char *path) { int ret = -1; plist_t status_plist = NULL; - gchar *file_path = mobilebackup_build_path(path, "Status", ".plist"); + char *file_path = mobilebackup_build_path(path, "Status", ".plist"); - plist_read_from_filename(&status_plist, file_path); - g_free(file_path); + plist_read_from_file(file_path, &status_plist, NULL); + free(file_path); if (!status_plist) { printf("Could not read Status.plist!\n"); return ret; @@ -387,7 +301,7 @@ static int mobilebackup_info_is_current_device(plist_t info) /* get basic device information in one go */ lockdownd_get_value(client, NULL, NULL, &root_node); - /* verify UUID */ + /* verify UDID */ value_node = plist_dict_get_item(root_node, "UniqueDeviceID"); node = plist_dict_get_item(info, "Target Identifier"); @@ -435,14 +349,14 @@ static int mobilebackup_info_is_current_device(plist_t info) static int mobilebackup_delete_backup_file_by_hash(const char *backup_directory, const char *hash) { int ret = 0; - gchar *path = mobilebackup_build_path(backup_directory, hash, ".mddata"); + char *path = mobilebackup_build_path(backup_directory, hash, ".mddata"); printf("Removing \"%s\" ", path); if (!remove( path )) ret = 1; else ret = 0; - g_free(path); + free(path); if (!ret) return ret; @@ -454,7 +368,7 @@ static int mobilebackup_delete_backup_file_by_hash(const char *backup_directory, else ret = 0; - g_free(path); + free(path); return ret; } @@ -476,7 +390,7 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const } infopath = mobilebackup_build_path(backup_directory, hash, ".mdinfo"); - plist_read_from_filename(&mdinfo, infopath); + plist_read_from_file(infopath, &mdinfo, NULL); free(infopath); if (!mdinfo) { printf("\r\n"); @@ -521,13 +435,13 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const char *version = NULL; node = plist_dict_get_item(metadata, "Version"); - if (node && (plist_get_node_type(node) == PLIST_STRING)) { + if (node && (plist_get_node_type(node) == PLIST_STRING)) { plist_get_string_val(node, &version); } char *destpath = NULL; node = plist_dict_get_item(metadata, "Path"); - if (node && (plist_get_node_type(node) == PLIST_STRING)) { + if (node && (plist_get_node_type(node) == PLIST_STRING)) { plist_get_string_val(node, &destpath); } @@ -539,7 +453,7 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const char *domain = NULL; node = plist_dict_get_item(metadata, "Domain"); - if (node && (plist_get_node_type(node) == PLIST_STRING)) { + if (node && (plist_get_node_type(node) == PLIST_STRING)) { plist_get_string_val(node, &domain); } @@ -550,14 +464,14 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const unsigned char fnhash[20]; char fnamehash[41]; char *p = fnamehash; - sha1_of_data(fnstr, strlen(fnstr), fnhash); + sha1((const unsigned char*)fnstr, strlen(fnstr), fnhash); free(fnstr); int i; for ( i = 0; i < 20; i++, p += 2 ) { snprintf (p, 3, "%02x", (unsigned char)fnhash[i] ); } - if (strcmp(fnamehash, hash)) { - printf("\r\n"); + if (strcmp(fnamehash, hash) != 0) { + printf("\r\n"); printf("WARNING: filename hash does not match for entry '%s'\n", hash); } @@ -567,7 +481,7 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const plist_get_string_val(node, &auth_version); } - if (strcmp(auth_version, "1.0")) { + if (strcmp(auth_version, "1.0") != 0) { printf("\r\n"); printf("WARNING: Unknown AuthVersion '%s', DataHash cannot be verified!\n", auth_version); } @@ -591,9 +505,9 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const hash_ok = 1; } - g_free(domain); - g_free(version); - g_free(destpath); + free(domain); + free(version); + free(destpath); if (!hash_ok) { printf("\r\n"); @@ -605,31 +519,36 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const printf("\n"); res = 0; } - g_free(data_hash); + free(data_hash); plist_free(mdinfo); return res; } static void do_post_notification(const char *notification) { - uint16_t nport = 0; + lockdownd_service_descriptor_t service = NULL; np_client_t np; if (!client) { - if (lockdownd_client_new_with_handshake(phone, &client, "idevicebackup") != LOCKDOWN_E_SUCCESS) { + if (lockdownd_client_new_with_handshake(device, &client, TOOL_NAME) != LOCKDOWN_E_SUCCESS) { return; } } - lockdownd_start_service(client, NP_SERVICE_NAME, &nport); - if (nport) { - np_client_new(phone, nport, &np); + lockdownd_error_t ldret = lockdownd_start_service(client, NP_SERVICE_NAME, &service); + if (ldret == LOCKDOWN_E_SUCCESS) { + np_client_new(device, service, &np); if (np) { np_post_notification(np, notification); np_client_free(np); } } else { - printf("Could not start %s\n", NP_SERVICE_NAME); + printf("Could not start %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret)); + } + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; } } @@ -665,29 +584,38 @@ static void clean_exit(int sig) quit_flag++; } -static void print_usage(int argc, char **argv) +static void print_usage(int argc, char **argv, int is_error) { - char *name = NULL; - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS] CMD [DIRECTORY]\n", (name ? name + 1: argv[0])); - printf("Create or restore backup from the current or specified directory.\n\n"); - printf("commands:\n"); - printf(" backup\tSaves a device backup into DIRECTORY\n"); - printf(" restore\tRestores a device backup from DIRECTORY.\n\n"); - printf("options:\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] CMD DIRECTORY\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Create or restore backup in/from the specified directory.\n" + "\n" + "CMD:\n" + " backup Saves a device backup into DIRECTORY\n" + " restore Restores a device backup from DIRECTORY.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); } int main(int argc, char *argv[]) { idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; int i; - char uuid[41]; - uint16_t port = 0; - uuid[0] = 0; + char* udid = NULL; + int use_network = 0; + lockdownd_service_descriptor_t service = NULL; int cmd = -1; int is_full_backup = 0; char *backup_directory = NULL; @@ -701,60 +629,77 @@ int main(int argc, char *argv[]) uint64_t length = 0; uint64_t backup_total_size = 0; enum device_link_file_status_t file_status = DEVICE_LINK_FILE_STATUS_NONE; - uint64_t c = 0; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; /* we need to exit cleanly on running backups and restores or we cause havok */ signal(SIGINT, clean_exit); - signal(SIGQUIT, clean_exit); signal(SIGTERM, clean_exit); +#ifndef WIN32 + signal(SIGQUIT, clean_exit); signal(SIGPIPE, SIG_IGN); +#endif /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { - i++; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; } - strcpy(uuid, argv[i]); - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); + udid = strdup(optarg); + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); return 0; - } - else if (!strcmp(argv[i], "backup")) { - cmd = CMD_BACKUP; - } - else if (!strcmp(argv[i], "restore")) { - cmd = CMD_RESTORE; - } - else if (backup_directory == NULL) { - backup_directory = argv[i]; - } - else { - print_usage(argc, argv); + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); return 0; + default: + print_usage(argc, argv, 1); + return 2; } } + argc -= optind; + argv += optind; - /* verify options */ - if (cmd == -1) { - printf("No command specified.\n"); - print_usage(argc, argv); - return -1; + if (argc < 1) { + fprintf(stderr, "ERROR: Missing command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; } - if (backup_directory == NULL) { - printf("No target backup directory specified.\n"); - print_usage(argc, argv); - return -1; + if (!strcmp(argv[0], "backup")) { + cmd = CMD_BACKUP; + } else if (!strcmp(argv[0], "restore")) { + cmd = CMD_RESTORE; + } else { + fprintf(stderr, "ERROR: Invalid command '%s'.\n", argv[0]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (argc < 2) { + fprintf(stderr, "No target backup directory specified.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; } + backup_directory = argv[1]; /* verify if passed backup directory exists */ if (stat(backup_directory, &st) != 0) { @@ -766,7 +711,7 @@ int main(int argc, char *argv[]) char *info_path = mobilebackup_build_path(backup_directory, "Info", ".plist"); if (cmd == CMD_RESTORE) { if (stat(info_path, &st) != 0) { - g_free(info_path); + free(info_path); printf("ERROR: Backup directory \"%s\" is invalid. No Info.plist found.\n", backup_directory); return -1; } @@ -774,32 +719,54 @@ int main(int argc, char *argv[]) printf("Backup directory is \"%s\"\n", backup_directory); - if (uuid[0] != 0) { - ret = idevice_new(&phone, uuid); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found with uuid %s, is it plugged in?\n", uuid); - return -1; + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); } + return -1; } - else - { - ret = idevice_new(&phone, NULL); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found, is it plugged in?\n"); - return -1; - } + + if (!udid) { + idevice_get_udid(device, &udid); } - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicebackup")) { - idevice_free(phone); + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) { + printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret); + idevice_free(device); + free(udid); return -1; } + node = NULL; + lockdownd_get_value(client, NULL, "ProductVersion", &node); + if (node) { + char* str = NULL; + if (plist_get_node_type(node) == PLIST_STRING) { + plist_get_string_val(node, &str); + } + plist_free(node); + node = NULL; + if (str) { + int maj = strtol(str, NULL, 10); + free(str); + if (maj > 3) { + printf("ERROR: This tool is only compatible with iOS 3 or below. For newer iOS versions please use the idevicebackup2 tool.\n"); + lockdownd_client_free(client); + idevice_free(device); + free(udid); + return -1; + } + } + } + /* start notification_proxy */ np_client_t np = NULL; - ret = lockdownd_start_service(client, NP_SERVICE_NAME, &port); - if ((ret == LOCKDOWN_E_SUCCESS) && port) { - np_client_new(phone, port, &np); + ldret = lockdownd_start_service(client, NP_SERVICE_NAME, &service); + if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) { + np_client_new(device, service, &np); np_set_notify_callback(np, notify_cb, NULL); const char *noties[5] = { NP_SYNC_CANCEL_REQUEST, @@ -810,25 +777,37 @@ int main(int argc, char *argv[]) }; np_observe_notifications(np, noties); } else { - printf("ERROR: Could not start service %s.\n", NP_SERVICE_NAME); + printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret)); } afc_client_t afc = NULL; if (cmd == CMD_BACKUP) { /* start AFC, we need this for the lock file */ - port = 0; - ret = lockdownd_start_service(client, "com.apple.afc", &port); - if ((ret == LOCKDOWN_E_SUCCESS) && port) { - afc_client_new(phone, port, &afc); + service->port = 0; + service->ssl_enabled = 0; + ldret = lockdownd_start_service(client, AFC_SERVICE_NAME, &service); + if ((ldret == LOCKDOWN_E_SUCCESS) && service->port) { + afc_client_new(device, service, &afc); + } else { + printf("ERROR: Could not start service %s: %s\n", AFC_SERVICE_NAME, lockdownd_strerror(ldret)); } } + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + /* start mobilebackup service and retrieve port */ - port = 0; - ret = lockdownd_start_service(client, MOBILEBACKUP_SERVICE_NAME, &port); - if ((ret == LOCKDOWN_E_SUCCESS) && port) { - printf("Started \"%s\" service on port %d.\n", MOBILEBACKUP_SERVICE_NAME, port); - mobilebackup_client_new(phone, port, &mobilebackup); + ldret = lockdownd_start_service(client, MOBILEBACKUP_SERVICE_NAME, &service); + if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) { + printf("Started \"%s\" service on port %d.\n", MOBILEBACKUP_SERVICE_NAME, service->port); + printf("%d\n", mobilebackup_client_new(device, service, &mobilebackup)); + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } /* check abort conditions */ if (quit_flag > 0) { @@ -839,7 +818,7 @@ int main(int argc, char *argv[]) /* verify existing Info.plist */ if (stat(info_path, &st) == 0) { printf("Reading Info.plist from backup.\n"); - plist_read_from_filename(&info_plist, info_path); + plist_read_from_file(info_path, &info_plist, NULL); if (!info_plist) { printf("Could not read Info.plist\n"); @@ -850,7 +829,7 @@ int main(int argc, char *argv[]) /* update the last backup time within Info.plist */ mobilebackup_info_update_last_backup_date(info_plist); remove(info_path); - plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML); + plist_write_to_file(info_plist, info_path, PLIST_FORMAT_XML, 0); } else { printf("Aborting backup. Backup is not compatible with the current device.\n"); cmd = CMD_LEAVE; @@ -883,15 +862,16 @@ int main(int argc, char *argv[]) if (aerr == AFC_E_SUCCESS) { do_post_notification(NP_SYNC_DID_START); break; - } else if (aerr == AFC_E_OP_WOULD_BLOCK) { + } + if (aerr == AFC_E_OP_WOULD_BLOCK) { usleep(LOCK_WAIT); continue; - } else { - fprintf(stderr, "ERROR: could not lock file! error code: %d\n", aerr); - afc_file_close(afc, lockfile); - lockfile = 0; - cmd = CMD_LEAVE; } + + fprintf(stderr, "ERROR: could not lock file! error code: %d\n", aerr); + afc_file_close(afc, lockfile); + lockfile = 0; + cmd = CMD_LEAVE; } if (i == LOCK_ATTEMPTS) { fprintf(stderr, "ERROR: timeout while locking for sync\n"); @@ -910,12 +890,12 @@ int main(int argc, char *argv[]) case CMD_BACKUP: printf("Starting backup...\n"); /* TODO: check domain com.apple.mobile.backup key RequiresEncrypt and WillEncrypt with lockdown */ - /* TODO: verify battery on AC enough battery remaining */ + /* TODO: verify battery on AC enough battery remaining */ /* read the last Manifest.plist */ if (!is_full_backup) { printf("Reading existing Manifest.\n"); - plist_read_from_filename(&manifest_plist, manifest_path); + plist_read_from_file(manifest_path, &manifest_plist, NULL); if (!manifest_plist) { printf("Could not read Manifest.plist, switching to full backup mode.\n"); is_full_backup = 1; @@ -932,10 +912,10 @@ int main(int argc, char *argv[]) } remove(info_path); printf("Creating Info.plist for new backup.\n"); - info_plist = mobilebackup_factory_info_plist_new(); - plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML); + info_plist = mobilebackup_factory_info_plist_new(udid); + plist_write_to_file(info_plist, info_path, PLIST_FORMAT_XML, 0); } - g_free(info_path); + free(info_path); plist_free(info_plist); info_plist = NULL; @@ -965,7 +945,7 @@ int main(int argc, char *argv[]) } else if (err == MOBILEBACKUP_E_REPLY_NOT_OK) { printf("ERROR: Could not start backup process: device refused to start the backup process.\n"); } else { - printf("ERROR: Could not start backup process: unspecified error occured\n"); + printf("ERROR: Could not start backup process: unspecified error occurred (%d)\n", err); } break; } @@ -985,8 +965,9 @@ int main(int argc, char *argv[]) char *filename_mddata = NULL; char *filename_source = NULL; char *format_size = NULL; - gboolean is_manifest = FALSE; + int is_manifest = 0; uint8_t b = 0; + uint64_t u64val = 0; /* process series of DLSendFile messages */ do { @@ -996,7 +977,7 @@ int main(int argc, char *argv[]) sleep(2); goto files_out; } - + node = plist_array_get_item(message, 0); /* get out if we don't get a DLSendFile */ @@ -1010,16 +991,16 @@ int main(int argc, char *argv[]) node = plist_dict_get_item(node_tmp, "BackupTotalSizeKey"); if (node) { plist_get_uint_val(node, &backup_total_size); - format_size = g_format_size_for_display(backup_total_size); + format_size = string_format_size(backup_total_size); printf("Backup data requires %s on the disk.\n", format_size); - g_free(format_size); + free(format_size); } } /* check DLFileStatusKey (codes: 1 = Hunk, 2 = Last Hunk) */ node = plist_dict_get_item(node_tmp, "DLFileStatusKey"); - plist_get_uint_val(node, &c); - file_status = c; + plist_get_uint_val(node, &u64val); + file_status = u64val; /* get source filename */ node = plist_dict_get_item(node_tmp, "BackupManifestKey"); @@ -1027,7 +1008,7 @@ int main(int argc, char *argv[]) if (node) { plist_get_bool_val(node, &b); } - is_manifest = (b == 1) ? TRUE: FALSE; + is_manifest = (b == 1) ? 1 : 0; if ((hunk_index == 0) && (!is_manifest)) { /* get source filename */ @@ -1040,17 +1021,17 @@ int main(int argc, char *argv[]) plist_get_uint_val(node, &file_size); backup_real_size += file_size; - format_size = g_format_size_for_display(backup_real_size); + format_size = string_format_size(backup_real_size); printf("(%s", format_size); - g_free(format_size); + free(format_size); - format_size = g_format_size_for_display(backup_total_size); + format_size = string_format_size(backup_total_size); printf("/%s): ", format_size); - g_free(format_size); + free(format_size); - format_size = g_format_size_for_display(file_size); + format_size = string_format_size(file_size); printf("Receiving file %s (%s)... \n", filename_source, format_size); - g_free(format_size); + free(format_size); if (filename_source) free(filename_source); @@ -1071,9 +1052,9 @@ int main(int argc, char *argv[]) remove(filename_mdinfo); node = plist_dict_get_item(node_tmp, "BackupFileInfo"); - plist_write_to_filename(node, filename_mdinfo, PLIST_FORMAT_BINARY); + plist_write_to_file(node, filename_mdinfo, PLIST_FORMAT_BINARY, 0); - g_free(filename_mdinfo); + free(filename_mdinfo); } file_index++; @@ -1107,15 +1088,14 @@ int main(int argc, char *argv[]) free(buffer); buffer = NULL; - g_free(filename_mddata); + free(filename_mddata); } if ((!is_manifest)) { if (hunk_index == 0 && file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) { print_progress(100); - } else { - if (file_size > 0) - print_progress((double)((file_size_current*100)/file_size)); + } else if (file_size > 0) { + print_progress((double)(file_size_current*100)/file_size); } } @@ -1145,7 +1125,7 @@ files_out: /* remove any atomic Manifest.plist.tmp */ if (manifest_path) - g_free(manifest_path); + free(manifest_path); manifest_path = mobilebackup_build_path(backup_directory, "Manifest", ".plist.tmp"); if (stat(manifest_path, &st) == 0) @@ -1184,7 +1164,7 @@ files_out: if (manifest_plist) { remove(manifest_path); printf("Storing Manifest.plist...\n"); - plist_write_to_filename(manifest_plist, manifest_path, PLIST_FORMAT_XML); + plist_write_to_file(manifest_plist, manifest_path, PLIST_FORMAT_XML, 0); } backup_ok = 1; @@ -1215,21 +1195,21 @@ files_out: } /* now make sure backup integrity is ok! verify all files */ printf("Reading existing Manifest.\n"); - plist_read_from_filename(&manifest_plist, manifest_path); + plist_read_from_file(manifest_path, &manifest_plist, NULL); if (!manifest_plist) { printf("Could not read Manifest.plist. Aborting.\n"); break; } printf("Verifying backup integrity, please wait.\n"); - char *bin = NULL; + unsigned char *bin = NULL; uint64_t binsize = 0; node = plist_dict_get_item(manifest_plist, "Data"); if (!node || (plist_get_node_type(node) != PLIST_DATA)) { printf("Could not read Data key from Manifest.plist!\n"); break; } - plist_get_data_val(node, &bin, &binsize); + plist_get_data_val(node, (char**)&bin, &binsize); plist_t backup_data = NULL; if (bin) { char *auth_ver = NULL; @@ -1246,7 +1226,7 @@ files_out: if (auth_sig && (auth_sig_len == 20)) { /* calculate the sha1, then compare */ unsigned char data_sha1[20]; - sha1_of_data(bin, binsize, data_sha1); + sha1(bin, binsize, data_sha1); if (compare_hash(auth_sig, data_sha1, 20)) { printf("AuthSignature is valid\n"); } else { @@ -1255,12 +1235,12 @@ files_out: } else { printf("Could not get AuthSignature from manifest!\n"); } - g_free(auth_sig); + free(auth_sig); } else if (auth_ver) { - printf("Unknown AuthVersion '%s', cannot verify AuthSignature\n", auth_ver); + printf("Unknown AuthVersion '%s', cannot verify AuthSignature\n", auth_ver); } - plist_from_bin(bin, (uint32_t)binsize, &backup_data); - g_free(bin); + plist_from_bin((char*)bin, (uint32_t)binsize, &backup_data); + free(bin); } if (!backup_data) { printf("Could not read plist from Manifest.plist Data key!\n"); @@ -1312,7 +1292,7 @@ files_out: } else if (err == MOBILEBACKUP_E_REPLY_NOT_OK) { printf("ERROR: Could not start restore process: device refused to start the restore process.\n"); } else { - printf("ERROR: Could not start restore process: unspecified error occured (%d)\n", err); + printf("ERROR: Could not start restore process: unspecified error occurred (%d)\n", err); } plist_free(backup_data); break; @@ -1342,7 +1322,7 @@ files_out: while (node) { /* TODO: read mddata/mdinfo files and send to device using DLSendFile */ file_info_path = mobilebackup_build_path(backup_directory, hash, ".mdinfo"); - plist_read_from_filename(&file_info, file_info_path); + plist_read_from_file(file_info_path, &file_info, NULL); /* get encryption state */ tmp_node = plist_dict_get_item(file_info, "IsEncrypted"); @@ -1360,42 +1340,62 @@ files_out: printf("Restoring file %s %d/%d (%d%%)... ", file_path, cur_file, total_files, (cur_file*100/total_files)); /* add additional device link file information keys */ - plist_dict_insert_item(file_info, "DLFileAttributesKey", plist_copy(node)); - plist_dict_insert_item(file_info, "DLFileSource", plist_new_string(file_info_path)); - plist_dict_insert_item(file_info, "DLFileDest", plist_new_string("/tmp/RestoreFile.plist")); - plist_dict_insert_item(file_info, "DLFileIsEncrypted", plist_new_bool(is_encrypted)); - plist_dict_insert_item(file_info, "DLFileOffsetKey", plist_new_uint(file_offset)); - plist_dict_insert_item(file_info, "DLFileStatusKey", plist_new_uint(file_status)); + plist_dict_set_item(file_info, "DLFileAttributesKey", plist_copy(node)); + plist_dict_set_item(file_info, "DLFileSource", plist_new_string(file_info_path)); + plist_dict_set_item(file_info, "DLFileDest", plist_new_string("/tmp/RestoreFile.plist")); + plist_dict_set_item(file_info, "DLFileIsEncrypted", plist_new_bool(is_encrypted)); + plist_dict_set_item(file_info, "DLFileOffsetKey", plist_new_uint(file_offset)); + plist_dict_set_item(file_info, "DLFileStatusKey", plist_new_uint(file_status)); /* read data from file */ free(file_info_path); file_info_path = mobilebackup_build_path(backup_directory, hash, ".mddata"); - buffer_read_from_filename(file_info_path, &buffer, &length); + + /* determine file size */ +#ifdef WIN32 + struct _stati64 fst; + if (_stati64(file_info_path, &fst) != 0) +#else + struct stat fst; + if (stat(file_info_path, &fst) != 0) +#endif + { + printf("ERROR: stat() failed for '%s': %s\n", file_info_path, strerror(errno)); + free(file_info_path); + break; + } + length = fst.st_size; + + FILE *f = fopen(file_info_path, "rb"); + if (!f) { + printf("ERROR: could not open local file '%s': %s\n", file_info_path, strerror(errno)); + free(file_info_path); + break; + } free(file_info_path); /* send DLSendFile messages */ file_offset = 0; do { - if ((length-file_offset) <= 8192) + char buf[8192]; + size_t len = fread(buf, 1, sizeof(buf), f); + + if ((length-file_offset) <= sizeof(buf)) file_status = DEVICE_LINK_FILE_STATUS_LAST_HUNK; else file_status = DEVICE_LINK_FILE_STATUS_HUNK; - + plist_dict_remove_item(file_info, "DLFileOffsetKey"); - plist_dict_insert_item(file_info, "DLFileOffsetKey", plist_new_uint(file_offset)); + plist_dict_set_item(file_info, "DLFileOffsetKey", plist_new_uint(file_offset)); plist_dict_remove_item(file_info, "DLFileStatusKey"); - plist_dict_insert_item(file_info, "DLFileStatusKey", plist_new_uint(file_status)); + plist_dict_set_item(file_info, "DLFileStatusKey", plist_new_uint(file_status)); send_file_node = plist_new_array(); plist_array_append_item(send_file_node, plist_new_string("DLSendFile")); - if (file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) - plist_array_append_item(send_file_node, plist_new_data(buffer+file_offset, length-file_offset)); - else - plist_array_append_item(send_file_node, plist_new_data(buffer+file_offset, 8192)); - + plist_array_append_item(send_file_node, plist_new_data(buf, len)); plist_array_append_item(send_file_node, plist_copy(file_info)); err = mobilebackup_send(mobilebackup, send_file_node); @@ -1413,13 +1413,13 @@ files_out: } } - file_offset += 8192; + file_offset += len; if (file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) printf("DONE\n"); plist_free(send_file_node); - + if (file_status == DEVICE_LINK_FILE_STATUS_NONE) break; @@ -1466,8 +1466,8 @@ files_out: tmp_node = plist_dict_get_item(node, "AppInfo"); dict = plist_new_dict(); - plist_dict_insert_item(dict, "AppInfo", plist_copy(tmp_node)); - plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageRestoreApplicationSent")); + plist_dict_set_item(dict, "AppInfo", plist_copy(tmp_node)); + plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageRestoreApplicationSent")); array = plist_new_array(); plist_array_append_item(array, plist_new_string("DLMessageProcessMessage")); @@ -1540,9 +1540,9 @@ files_out: do_post_notification(NP_SYNC_DID_FINISH); } if (manifest_path) - g_free(manifest_path); + free(manifest_path); } else { - printf("ERROR: Could not start service %s.\n", MOBILEBACKUP_SERVICE_NAME); + printf("ERROR: Could not start service %s: %s\n", MOBILEBACKUP_SERVICE_NAME, lockdownd_strerror(ldret)); lockdownd_client_free(client); client = NULL; } @@ -1561,7 +1561,9 @@ files_out: if (mobilebackup) mobilebackup_client_free(mobilebackup); - idevice_free(phone); + idevice_free(device); + + free(udid); return 0; } diff --git a/tools/idevicebackup2.c b/tools/idevicebackup2.c new file mode 100644 index 0000000..c73b269 --- /dev/null +++ b/tools/idevicebackup2.c @@ -0,0 +1,2684 @@ +/* + * idevicebackup2.c + * Command line interface to use the device's backup and restore service + * + * Copyright (c) 2010-2022 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2009-2010 Martin Szulecki, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicebackup2" + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <dirent.h> +#include <libgen.h> +#include <ctype.h> +#include <time.h> +#include <getopt.h> + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/mobilebackup2.h> +#include <libimobiledevice/notification_proxy.h> +#include <libimobiledevice/afc.h> +#include <libimobiledevice/installation_proxy.h> +#include <libimobiledevice/sbservices.h> +#include <libimobiledevice/diagnostics_relay.h> +#include <libimobiledevice-glue/utils.h> +#include <plist/plist.h> + +#include <endianness.h> + +#define LOCK_ATTEMPTS 50 +#define LOCK_WAIT 200000 + +#ifdef WIN32 +#include <windows.h> +#include <conio.h> +#define sleep(x) Sleep(x*1000) +#ifndef ELOOP +#define ELOOP 114 +#endif +#else +#include <termios.h> +#include <sys/statvfs.h> +#endif +#include <sys/stat.h> + +#define CODE_SUCCESS 0x00 +#define CODE_ERROR_LOCAL 0x06 +#define CODE_ERROR_REMOTE 0x0b +#define CODE_FILE_DATA 0x0c + +static int verbose = 1; +static int quit_flag = 0; + +#define PRINT_VERBOSE(min_level, ...) if (verbose >= min_level) { printf(__VA_ARGS__); }; + +enum cmd_mode { + CMD_BACKUP, + CMD_RESTORE, + CMD_INFO, + CMD_LIST, + CMD_UNBACK, + CMD_CHANGEPW, + CMD_LEAVE, + CMD_CLOUD +}; + +enum cmd_flags { + CMD_FLAG_RESTORE_SYSTEM_FILES = (1 << 1), + CMD_FLAG_RESTORE_NO_REBOOT = (1 << 2), + CMD_FLAG_RESTORE_COPY_BACKUP = (1 << 3), + CMD_FLAG_RESTORE_SETTINGS = (1 << 4), + CMD_FLAG_RESTORE_REMOVE_ITEMS = (1 << 5), + CMD_FLAG_ENCRYPTION_ENABLE = (1 << 6), + CMD_FLAG_ENCRYPTION_DISABLE = (1 << 7), + CMD_FLAG_ENCRYPTION_CHANGEPW = (1 << 8), + CMD_FLAG_FORCE_FULL_BACKUP = (1 << 9), + CMD_FLAG_CLOUD_ENABLE = (1 << 10), + CMD_FLAG_CLOUD_DISABLE = (1 << 11), + CMD_FLAG_RESTORE_SKIP_APPS = (1 << 12) +}; + +static int backup_domain_changed = 0; + +static void notify_cb(const char *notification, void *userdata) +{ + if (strlen(notification) == 0) { + return; + } + if (!strcmp(notification, NP_SYNC_CANCEL_REQUEST)) { + PRINT_VERBOSE(1, "User has cancelled the backup process on the device.\n"); + quit_flag++; + } else if (!strcmp(notification, NP_BACKUP_DOMAIN_CHANGED)) { + backup_domain_changed = 1; + } else { + PRINT_VERBOSE(1, "Unhandled notification '%s' (TODO: implement)\n", notification); + } +} + +static void mobilebackup_afc_get_file_contents(afc_client_t afc, const char *filename, char **data, uint64_t *size) +{ + if (!afc || !data || !size) { + return; + } + + char **fileinfo = NULL; + uint32_t fsize = 0; + + afc_get_file_info(afc, filename, &fileinfo); + if (!fileinfo) { + return; + } + int i; + for (i = 0; fileinfo[i]; i+=2) { + if (!strcmp(fileinfo[i], "st_size")) { + fsize = atol(fileinfo[i+1]); + break; + } + } + afc_dictionary_free(fileinfo); + + if (fsize == 0) { + return; + } + + uint64_t f = 0; + afc_file_open(afc, filename, AFC_FOPEN_RDONLY, &f); + if (!f) { + return; + } + char *buf = (char*)malloc((uint32_t)fsize); + uint32_t done = 0; + while (done < fsize) { + uint32_t bread = 0; + afc_file_read(afc, f, buf+done, 65536, &bread); + if (bread > 0) { + done += bread; + } else { + break; + } + } + if (done == fsize) { + *size = fsize; + *data = buf; + } else { + free(buf); + } + afc_file_close(afc, f); +} + +static int __mkdir(const char* path, int mode) +{ +#ifdef WIN32 + return mkdir(path); +#else + return mkdir(path, mode); +#endif +} + +static int mkdir_with_parents(const char *dir, int mode) +{ + if (!dir) return -1; + if (__mkdir(dir, mode) == 0) { + return 0; + } + if (errno == EEXIST) return 0; + int res; + char *parent = strdup(dir); + char *parentdir = dirname(parent); + if (parentdir) { + res = mkdir_with_parents(parentdir, mode); + } else { + res = -1; + } + free(parent); + if (res == 0) { + mkdir_with_parents(dir, mode); + } + return res; +} + +#ifdef WIN32 +static int win32err_to_errno(int err_value) +{ + switch (err_value) { + case ERROR_FILE_NOT_FOUND: + return ENOENT; + case ERROR_ALREADY_EXISTS: + return EEXIST; + default: + return EFAULT; + } +} +#endif + +static int remove_file(const char* path) +{ + int e = 0; +#ifdef WIN32 + if (!DeleteFile(path)) { + e = win32err_to_errno(GetLastError()); + } +#else + if (remove(path) < 0) { + e = errno; + } +#endif + return e; +} + +static int remove_directory(const char* path) +{ + int e = 0; +#ifdef WIN32 + if (!RemoveDirectory(path)) { + e = win32err_to_errno(GetLastError()); + } +#else + if (remove(path) < 0) { + e = errno; + } +#endif + return e; +} + +struct entry { + char *name; + struct entry *next; +}; + +static void scan_directory(const char *path, struct entry **files, struct entry **directories) +{ + DIR* cur_dir = opendir(path); + if (cur_dir) { + struct dirent* ep; + while ((ep = readdir(cur_dir))) { + if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { + continue; + } + char *fpath = string_build_path(path, ep->d_name, NULL); + if (fpath) { +#ifdef HAVE_DIRENT_D_TYPE + if (ep->d_type & DT_DIR) { +#else + struct stat st; + if (stat(fpath, &st) != 0) return; + if (S_ISDIR(st.st_mode)) { +#endif + struct entry *ent = malloc(sizeof(struct entry)); + if (!ent) return; + ent->name = fpath; + ent->next = *directories; + *directories = ent; + scan_directory(fpath, files, directories); + fpath = NULL; + } else { + struct entry *ent = malloc(sizeof(struct entry)); + if (!ent) return; + ent->name = fpath; + ent->next = *files; + *files = ent; + fpath = NULL; + } + } + } + closedir(cur_dir); + } +} + +static int rmdir_recursive(const char* path) +{ + int res = 0; + struct entry *files = NULL; + struct entry *directories = NULL; + struct entry *ent; + + ent = malloc(sizeof(struct entry)); + if (!ent) return ENOMEM; + ent->name = strdup(path); + ent->next = NULL; + directories = ent; + + scan_directory(path, &files, &directories); + + ent = files; + while (ent) { + struct entry *del = ent; + res = remove_file(ent->name); + free(ent->name); + ent = ent->next; + free(del); + } + ent = directories; + while (ent) { + struct entry *del = ent; + res = remove_directory(ent->name); + free(ent->name); + ent = ent->next; + free(del); + } + + return res; +} + +static char* get_uuid() +{ + const char *chars = "ABCDEF0123456789"; + int i = 0; + char *uuid = (char*)malloc(sizeof(char) * 33); + + srand(time(NULL)); + + for (i = 0; i < 32; i++) { + uuid[i] = chars[rand() % 16]; + } + + uuid[32] = '\0'; + + return uuid; +} + +static plist_t mobilebackup_factory_info_plist_new(const char* udid, idevice_t device, afc_client_t afc) +{ + /* gather data from lockdown */ + plist_t value_node = NULL; + plist_t root_node = NULL; + plist_t itunes_settings = NULL; + plist_t min_itunes_version = NULL; + char *udid_uppercase = NULL; + + lockdownd_client_t lockdown = NULL; + if (lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME) != LOCKDOWN_E_SUCCESS) { + return NULL; + } + + plist_t ret = plist_new_dict(); + + /* get basic device information in one go */ + lockdownd_get_value(lockdown, NULL, NULL, &root_node); + + /* get iTunes settings */ + lockdownd_get_value(lockdown, "com.apple.iTunes", NULL, &itunes_settings); + + /* get minimum iTunes version */ + lockdownd_get_value(lockdown, "com.apple.mobile.iTunes", "MinITunesVersion", &min_itunes_version); + + lockdownd_client_free(lockdown); + + /* get a list of installed user applications */ + plist_t app_dict = plist_new_dict(); + plist_t installed_apps = plist_new_array(); + instproxy_client_t ip = NULL; + if (instproxy_client_start_service(device, &ip, TOOL_NAME) == INSTPROXY_E_SUCCESS) { + plist_t client_opts = instproxy_client_options_new(); + instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL); + instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "ApplicationSINF", "iTunesMetadata", NULL); + + plist_t apps = NULL; + instproxy_browse(ip, client_opts, &apps); + + sbservices_client_t sbs = NULL; + if (sbservices_client_start_service(device, &sbs, TOOL_NAME) != SBSERVICES_E_SUCCESS) { + printf("Couldn't establish sbservices connection. Continuing anyway.\n"); + } + + if (apps && (plist_get_node_type(apps) == PLIST_ARRAY)) { + uint32_t app_count = plist_array_get_size(apps); + uint32_t i; + for (i = 0; i < app_count; i++) { + plist_t app_entry = plist_array_get_item(apps, i); + plist_t bundle_id = plist_dict_get_item(app_entry, "CFBundleIdentifier"); + if (bundle_id) { + char *bundle_id_str = NULL; + plist_array_append_item(installed_apps, plist_copy(bundle_id)); + + plist_get_string_val(bundle_id, &bundle_id_str); + plist_t sinf = plist_dict_get_item(app_entry, "ApplicationSINF"); + plist_t meta = plist_dict_get_item(app_entry, "iTunesMetadata"); + if (sinf && meta) { + plist_t adict = plist_new_dict(); + plist_dict_set_item(adict, "ApplicationSINF", plist_copy(sinf)); + if (sbs) { + char *pngdata = NULL; + uint64_t pngsize = 0; + sbservices_get_icon_pngdata(sbs, bundle_id_str, &pngdata, &pngsize); + if (pngdata) { + plist_dict_set_item(adict, "PlaceholderIcon", plist_new_data(pngdata, pngsize)); + free(pngdata); + } + } + plist_dict_set_item(adict, "iTunesMetadata", plist_copy(meta)); + plist_dict_set_item(app_dict, bundle_id_str, adict); + } + free(bundle_id_str); + } + } + } + plist_free(apps); + + if (sbs) { + sbservices_client_free(sbs); + } + + instproxy_client_options_free(client_opts); + + instproxy_client_free(ip); + } + + /* Applications */ + plist_dict_set_item(ret, "Applications", app_dict); + + /* set fields we understand */ + value_node = plist_dict_get_item(root_node, "BuildVersion"); + plist_dict_set_item(ret, "Build Version", plist_copy(value_node)); + + value_node = plist_dict_get_item(root_node, "DeviceName"); + plist_dict_set_item(ret, "Device Name", plist_copy(value_node)); + plist_dict_set_item(ret, "Display Name", plist_copy(value_node)); + + char *uuid = get_uuid(); + plist_dict_set_item(ret, "GUID", plist_new_string(uuid)); + free(uuid); + + value_node = plist_dict_get_item(root_node, "IntegratedCircuitCardIdentity"); + if (value_node) + plist_dict_set_item(ret, "ICCID", plist_copy(value_node)); + + value_node = plist_dict_get_item(root_node, "InternationalMobileEquipmentIdentity"); + if (value_node) + plist_dict_set_item(ret, "IMEI", plist_copy(value_node)); + + /* Installed Applications */ + plist_dict_set_item(ret, "Installed Applications", installed_apps); + + plist_dict_set_item(ret, "Last Backup Date", plist_new_date(time(NULL) - MAC_EPOCH, 0)); + + value_node = plist_dict_get_item(root_node, "MobileEquipmentIdentifier"); + if (value_node) + plist_dict_set_item(ret, "MEID", plist_copy(value_node)); + + value_node = plist_dict_get_item(root_node, "PhoneNumber"); + if (value_node && (plist_get_node_type(value_node) == PLIST_STRING)) { + plist_dict_set_item(ret, "Phone Number", plist_copy(value_node)); + } + + /* FIXME Product Name */ + + value_node = plist_dict_get_item(root_node, "ProductType"); + plist_dict_set_item(ret, "Product Type", plist_copy(value_node)); + + value_node = plist_dict_get_item(root_node, "ProductVersion"); + plist_dict_set_item(ret, "Product Version", plist_copy(value_node)); + + value_node = plist_dict_get_item(root_node, "SerialNumber"); + plist_dict_set_item(ret, "Serial Number", plist_copy(value_node)); + + /* FIXME Sync Settings? */ + + value_node = plist_dict_get_item(root_node, "UniqueDeviceID"); + plist_dict_set_item(ret, "Target Identifier", plist_new_string(udid)); + + plist_dict_set_item(ret, "Target Type", plist_new_string("Device")); + + /* uppercase */ + udid_uppercase = string_toupper((char*)udid); + plist_dict_set_item(ret, "Unique Identifier", plist_new_string(udid_uppercase)); + free(udid_uppercase); + + char *data_buf = NULL; + uint64_t data_size = 0; + mobilebackup_afc_get_file_contents(afc, "/Books/iBooksData2.plist", &data_buf, &data_size); + if (data_buf) { + plist_dict_set_item(ret, "iBooks Data 2", plist_new_data(data_buf, data_size)); + free(data_buf); + } + + plist_t files = plist_new_dict(); + const char *itunesfiles[] = { + "ApertureAlbumPrefs", + "IC-Info.sidb", + "IC-Info.sidv", + "PhotosFolderAlbums", + "PhotosFolderName", + "PhotosFolderPrefs", + "VoiceMemos.plist", + "iPhotoAlbumPrefs", + "iTunesApplicationIDs", + "iTunesPrefs", + "iTunesPrefs.plist", + NULL + }; + int i = 0; + for (i = 0; itunesfiles[i]; i++) { + data_buf = NULL; + data_size = 0; + char *fname = (char*)malloc(strlen("/iTunes_Control/iTunes/") + strlen(itunesfiles[i]) + 1); + strcpy(fname, "/iTunes_Control/iTunes/"); + strcat(fname, itunesfiles[i]); + mobilebackup_afc_get_file_contents(afc, fname, &data_buf, &data_size); + free(fname); + if (data_buf) { + plist_dict_set_item(files, itunesfiles[i], plist_new_data(data_buf, data_size)); + free(data_buf); + } + } + plist_dict_set_item(ret, "iTunes Files", files); + + plist_dict_set_item(ret, "iTunes Settings", itunes_settings ? plist_copy(itunes_settings) : plist_new_dict()); + + /* since we usually don't have iTunes, let's get the minimum required iTunes version from the device */ + if (min_itunes_version) { + plist_dict_set_item(ret, "iTunes Version", plist_copy(min_itunes_version)); + } else { + plist_dict_set_item(ret, "iTunes Version", plist_new_string("10.0.1")); + } + + plist_free(itunes_settings); + plist_free(min_itunes_version); + plist_free(root_node); + + return ret; +} + +static int write_restore_applications(plist_t info_plist, afc_client_t afc) +{ + int res = -1; + uint64_t restore_applications_file = 0; + char * applications_plist_xml = NULL; + uint32_t applications_plist_xml_length = 0; + + plist_t applications_plist = plist_dict_get_item(info_plist, "Applications"); + if (!applications_plist) { + printf("No Applications in Info.plist, skipping creation of RestoreApplications.plist\n"); + return 0; + } + plist_to_xml(applications_plist, &applications_plist_xml, &applications_plist_xml_length); + if (!applications_plist_xml) { + printf("Error preparing RestoreApplications.plist\n"); + goto leave; + } + + afc_error_t afc_err = 0; + afc_err = afc_make_directory(afc, "/iTunesRestore"); + if (afc_err != AFC_E_SUCCESS) { + printf("Error creating directory /iTunesRestore, error code %d\n", afc_err); + goto leave; + } + + afc_err = afc_file_open(afc, "/iTunesRestore/RestoreApplications.plist", AFC_FOPEN_WR, &restore_applications_file); + if (afc_err != AFC_E_SUCCESS || !restore_applications_file) { + printf("Error creating /iTunesRestore/RestoreApplications.plist, error code %d\n", afc_err); + goto leave; + } + + uint32_t bytes_written = 0; + afc_err = afc_file_write(afc, restore_applications_file, applications_plist_xml, applications_plist_xml_length, &bytes_written); + if (afc_err != AFC_E_SUCCESS || bytes_written != applications_plist_xml_length) { + printf("Error writing /iTunesRestore/RestoreApplications.plist, error code %d, wrote %u of %u bytes\n", afc_err, bytes_written, applications_plist_xml_length); + goto leave; + } + + afc_err = afc_file_close(afc, restore_applications_file); + restore_applications_file = 0; + if (afc_err != AFC_E_SUCCESS) { + goto leave; + } + /* success */ + res = 0; + +leave: + free(applications_plist_xml); + + if (restore_applications_file) { + afc_file_close(afc, restore_applications_file); + restore_applications_file = 0; + } + + return res; +} + +static int mb2_status_check_snapshot_state(const char *path, const char *udid, const char *matches) +{ + int ret = 0; + plist_t status_plist = NULL; + char *file_path = string_build_path(path, udid, "Status.plist", NULL); + + plist_read_from_file(file_path, &status_plist, NULL); + free(file_path); + if (!status_plist) { + printf("Could not read Status.plist!\n"); + return ret; + } + plist_t node = plist_dict_get_item(status_plist, "SnapshotState"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + char* sval = NULL; + plist_get_string_val(node, &sval); + if (sval) { + ret = (strcmp(sval, matches) == 0) ? 1 : 0; + free(sval); + } + } else { + printf("%s: ERROR could not get SnapshotState key from Status.plist!\n", __func__); + } + plist_free(status_plist); + return ret; +} + +static void do_post_notification(idevice_t device, const char *notification) +{ + lockdownd_service_descriptor_t service = NULL; + np_client_t np; + + lockdownd_client_t lockdown = NULL; + + if (lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME) != LOCKDOWN_E_SUCCESS) { + return; + } + + lockdownd_error_t ldret = lockdownd_start_service(lockdown, NP_SERVICE_NAME, &service); + if (ldret == LOCKDOWN_E_SUCCESS) { + np_client_new(device, service, &np); + if (np) { + np_post_notification(np, notification); + np_client_free(np); + } + } else { + printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret)); + } + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + lockdownd_client_free(lockdown); +} + +static void print_progress_real(double progress, int flush) +{ + int i = 0; + PRINT_VERBOSE(1, "\r["); + for(i = 0; i < 50; i++) { + if(i < progress / 2) { + PRINT_VERBOSE(1, "="); + } else { + PRINT_VERBOSE(1, " "); + } + } + PRINT_VERBOSE(1, "] %3.0f%%", progress); + + if (flush > 0) { + fflush(stdout); + if (progress == 100) + PRINT_VERBOSE(1, "\n"); + } +} + +static void print_progress(uint64_t current, uint64_t total) +{ + char *format_size = NULL; + double progress = ((double)current/(double)total)*100; + if (progress < 0) + return; + + if (progress > 100) + progress = 100; + + print_progress_real((double)progress, 0); + + format_size = string_format_size(current); + PRINT_VERBOSE(1, " (%s", format_size); + free(format_size); + format_size = string_format_size(total); + PRINT_VERBOSE(1, "/%s) ", format_size); + free(format_size); + + fflush(stdout); + if (progress == 100) + PRINT_VERBOSE(1, "\n"); +} + +static double overall_progress = 0; + +static void mb2_set_overall_progress(double progress) +{ + if (progress > 0.0) + overall_progress = progress; +} + +static void mb2_set_overall_progress_from_message(plist_t message, char* identifier) +{ + plist_t node = NULL; + double progress = 0.0; + + if (!strcmp(identifier, "DLMessageDownloadFiles")) { + node = plist_array_get_item(message, 3); + } else if (!strcmp(identifier, "DLMessageUploadFiles")) { + node = plist_array_get_item(message, 2); + } else if (!strcmp(identifier, "DLMessageMoveFiles") || !strcmp(identifier, "DLMessageMoveItems")) { + node = plist_array_get_item(message, 3); + } else if (!strcmp(identifier, "DLMessageRemoveFiles") || !strcmp(identifier, "DLMessageRemoveItems")) { + node = plist_array_get_item(message, 3); + } + + if (node != NULL) { + plist_get_real_val(node, &progress); + mb2_set_overall_progress(progress); + } +} + +static void mb2_multi_status_add_file_error(plist_t status_dict, const char *path, int error_code, const char *error_message) +{ + if (!status_dict) return; + plist_t filedict = plist_new_dict(); + plist_dict_set_item(filedict, "DLFileErrorString", plist_new_string(error_message)); + plist_dict_set_item(filedict, "DLFileErrorCode", plist_new_uint(error_code)); + plist_dict_set_item(status_dict, path, filedict); +} + +static int errno_to_device_error(int errno_value) +{ + switch (errno_value) { + case ENOENT: + return -6; + case EEXIST: + return -7; + case ENOTDIR: + return -8; + case EISDIR: + return -9; + case ELOOP: + return -10; + case EIO: + return -11; + case ENOSPC: + return -15; + default: + return -1; + } +} + +static int mb2_handle_send_file(mobilebackup2_client_t mobilebackup2, const char *backup_dir, const char *path, plist_t *errplist) +{ + uint32_t nlen = 0; + uint32_t pathlen = strlen(path); + uint32_t bytes = 0; + char *localfile = string_build_path(backup_dir, path, NULL); + char buf[32768]; +#ifdef WIN32 + struct _stati64 fst; +#else + struct stat fst; +#endif + + FILE *f = NULL; + uint32_t slen = 0; + int errcode = -1; + int result = -1; + uint32_t length; +#ifdef WIN32 + uint64_t total; + uint64_t sent; +#else + off_t total; + off_t sent; +#endif + + mobilebackup2_error_t err; + + /* send path length */ + nlen = htobe32(pathlen); + err = mobilebackup2_send_raw(mobilebackup2, (const char*)&nlen, sizeof(nlen), &bytes); + if (err != MOBILEBACKUP2_E_SUCCESS) { + goto leave_proto_err; + } + if (bytes != (uint32_t)sizeof(nlen)) { + err = MOBILEBACKUP2_E_MUX_ERROR; + goto leave_proto_err; + } + + /* send path */ + err = mobilebackup2_send_raw(mobilebackup2, path, pathlen, &bytes); + if (err != MOBILEBACKUP2_E_SUCCESS) { + goto leave_proto_err; + } + if (bytes != pathlen) { + err = MOBILEBACKUP2_E_MUX_ERROR; + goto leave_proto_err; + } + +#ifdef WIN32 + if (_stati64(localfile, &fst) < 0) +#else + if (stat(localfile, &fst) < 0) +#endif + { + if (errno != ENOENT) + printf("%s: stat failed on '%s': %d\n", __func__, localfile, errno); + errcode = errno; + goto leave; + } + + total = fst.st_size; + + char *format_size = string_format_size(total); + PRINT_VERBOSE(1, "Sending '%s' (%s)\n", path, format_size); + free(format_size); + + if (total == 0) { + errcode = 0; + goto leave; + } + + f = fopen(localfile, "rb"); + if (!f) { + printf("%s: Error opening local file '%s': %d\n", __func__, localfile, errno); + errcode = errno; + goto leave; + } + + sent = 0; + do { + length = ((total-sent) < (long long)sizeof(buf)) ? (uint32_t)total-sent : (uint32_t)sizeof(buf); + /* send data size (file size + 1) */ + nlen = htobe32(length+1); + memcpy(buf, &nlen, sizeof(nlen)); + buf[4] = CODE_FILE_DATA; + err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, 5, &bytes); + if (err != MOBILEBACKUP2_E_SUCCESS) { + goto leave_proto_err; + } + if (bytes != 5) { + goto leave_proto_err; + } + + /* send file contents */ + size_t r = fread(buf, 1, sizeof(buf), f); + if (r <= 0) { + printf("%s: read error\n", __func__); + errcode = errno; + goto leave; + } + err = mobilebackup2_send_raw(mobilebackup2, buf, r, &bytes); + if (err != MOBILEBACKUP2_E_SUCCESS) { + goto leave_proto_err; + } + if (bytes != (uint32_t)r) { + printf("Error: sent only %d of %d bytes\n", bytes, (int)r); + goto leave_proto_err; + } + sent += r; + } while (sent < total); + fclose(f); + f = NULL; + errcode = 0; + +leave: + if (errcode == 0) { + result = 0; + nlen = 1; + nlen = htobe32(nlen); + memcpy(buf, &nlen, 4); + buf[4] = CODE_SUCCESS; + mobilebackup2_send_raw(mobilebackup2, buf, 5, &bytes); + } else { + if (!*errplist) { + *errplist = plist_new_dict(); + } + char *errdesc = strerror(errcode); + mb2_multi_status_add_file_error(*errplist, path, errno_to_device_error(errcode), errdesc); + + length = strlen(errdesc); + nlen = htobe32(length+1); + memcpy(buf, &nlen, 4); + buf[4] = CODE_ERROR_LOCAL; + slen = 5; + memcpy(buf+slen, errdesc, length); + slen += length; + err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, slen, &bytes); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("could not send message\n"); + } + if (bytes != slen) { + printf("could only send %d from %d\n", bytes, slen); + } + } + +leave_proto_err: + if (f) + fclose(f); + free(localfile); + return result; +} + +static void mb2_handle_send_files(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir) +{ + uint32_t cnt; + uint32_t i = 0; + uint32_t sent; + plist_t errplist = NULL; + + if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || (plist_array_get_size(message) < 2) || !backup_dir) return; + + plist_t files = plist_array_get_item(message, 1); + cnt = plist_array_get_size(files); + + for (i = 0; i < cnt; i++) { + plist_t val = plist_array_get_item(files, i); + if (plist_get_node_type(val) != PLIST_STRING) { + continue; + } + char *str = NULL; + plist_get_string_val(val, &str); + if (!str) + continue; + + if (mb2_handle_send_file(mobilebackup2, backup_dir, str, &errplist) < 0) { + free(str); + //printf("Error when sending file '%s' to device\n", str); + // TODO: perhaps we can continue, we've got a multi status response?! + break; + } + free(str); + } + + /* send terminating 0 dword */ + uint32_t zero = 0; + mobilebackup2_send_raw(mobilebackup2, (char*)&zero, 4, &sent); + + if (!errplist) { + plist_t emptydict = plist_new_dict(); + mobilebackup2_send_status_response(mobilebackup2, 0, NULL, emptydict); + plist_free(emptydict); + } else { + mobilebackup2_send_status_response(mobilebackup2, -13, "Multi status", errplist); + plist_free(errplist); + } +} + +static int mb2_receive_filename(mobilebackup2_client_t mobilebackup2, char** filename) +{ + uint32_t nlen = 0; + uint32_t rlen = 0; + + do { + nlen = 0; + rlen = 0; + mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &rlen); + nlen = be32toh(nlen); + + if ((nlen == 0) && (rlen == 4)) { + // a zero length means no more files to receive + return 0; + } + if (rlen == 0) { + // device needs more time, waiting... + continue; + } + if (nlen > 4096) { + // filename length is too large + printf("ERROR: %s: too large filename length (%d)!\n", __func__, nlen); + return 0; + } + + if (*filename != NULL) { + free(*filename); + *filename = NULL; + } + + *filename = (char*)malloc(nlen+1); + + rlen = 0; + mobilebackup2_receive_raw(mobilebackup2, *filename, nlen, &rlen); + if (rlen != nlen) { + printf("ERROR: %s: could not read filename\n", __func__); + return 0; + } + + char* p = *filename; + p[rlen] = 0; + + break; + } while(1 && !quit_flag); + + return nlen; +} + +static int mb2_handle_receive_files(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir) +{ + uint64_t backup_real_size = 0; + uint64_t backup_total_size = 0; + uint32_t blocksize; + uint32_t bdone; + uint32_t rlen; + uint32_t nlen = 0; + uint32_t r; + char buf[32768]; + char *fname = NULL; + char *dname = NULL; + char *bname = NULL; + char code = 0; + char last_code = 0; + plist_t node = NULL; + FILE *f = NULL; + unsigned int file_count = 0; + int errcode = 0; + char *errdesc = NULL; + + if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 4 || !backup_dir) return 0; + + node = plist_array_get_item(message, 3); + if (plist_get_node_type(node) == PLIST_UINT) { + plist_get_uint_val(node, &backup_total_size); + } + if (backup_total_size > 0) { + PRINT_VERBOSE(1, "Receiving files\n"); + } + + do { + if (quit_flag) + break; + + nlen = mb2_receive_filename(mobilebackup2, &dname); + if (nlen == 0) { + break; + } + + nlen = mb2_receive_filename(mobilebackup2, &fname); + if (!nlen) { + break; + } + + if (bname != NULL) { + free(bname); + bname = NULL; + } + + bname = string_build_path(backup_dir, fname, NULL); + + if (fname != NULL) { + free(fname); + fname = NULL; + } + + r = 0; + nlen = 0; + mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r); + if (r != 4) { + printf("ERROR: %s: could not receive code length!\n", __func__); + break; + } + nlen = be32toh(nlen); + + last_code = code; + code = 0; + + mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r); + if (r != 1) { + printf("ERROR: %s: could not receive code!\n", __func__); + break; + } + + /* TODO remove this */ + if ((code != CODE_SUCCESS) && (code != CODE_FILE_DATA) && (code != CODE_ERROR_REMOTE)) { + PRINT_VERBOSE(1, "Found new flag %02x\n", code); + } + + remove_file(bname); + f = fopen(bname, "wb"); + while (f && (code == CODE_FILE_DATA)) { + blocksize = nlen-1; + bdone = 0; + rlen = 0; + while (bdone < blocksize) { + if ((blocksize - bdone) < sizeof(buf)) { + rlen = blocksize - bdone; + } else { + rlen = sizeof(buf); + } + mobilebackup2_receive_raw(mobilebackup2, buf, rlen, &r); + if ((int)r <= 0) { + break; + } + fwrite(buf, 1, r, f); + bdone += r; + } + if (bdone == blocksize) { + backup_real_size += blocksize; + } + if (backup_total_size > 0) { + print_progress(backup_real_size, backup_total_size); + } + if (quit_flag) + break; + nlen = 0; + mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r); + nlen = be32toh(nlen); + if (nlen > 0) { + last_code = code; + mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r); + } else { + break; + } + } + if (f) { + fclose(f); + file_count++; + } else { + errcode = errno_to_device_error(errno); + errdesc = strerror(errno); + printf("Error opening '%s' for writing: %s\n", bname, errdesc); + break; + } + if (nlen == 0) { + break; + } + + /* check if an error message was received */ + if (code == CODE_ERROR_REMOTE) { + /* error message */ + char *msg = (char*)malloc(nlen); + mobilebackup2_receive_raw(mobilebackup2, msg, nlen-1, &r); + msg[r] = 0; + /* If sent using CODE_FILE_DATA, end marker will be CODE_ERROR_REMOTE which is not an error! */ + if (last_code != CODE_FILE_DATA) { + fprintf(stdout, "\nReceived an error message from device: %s\n", msg); + } + free(msg); + } + } while (1); + + if (fname != NULL) + free(fname); + + /* if there are leftovers to read, finish up cleanly */ + if ((int)nlen-1 > 0) { + PRINT_VERBOSE(1, "\nDiscarding current data hunk.\n"); + fname = (char*)malloc(nlen-1); + mobilebackup2_receive_raw(mobilebackup2, fname, nlen-1, &r); + free(fname); + remove_file(bname); + } + + /* clean up */ + if (bname != NULL) + free(bname); + + if (dname != NULL) + free(dname); + + plist_t empty_plist = plist_new_dict(); + mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_plist); + plist_free(empty_plist); + + return file_count; +} + +static void mb2_handle_list_directory(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir) +{ + if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 2 || !backup_dir) return; + + plist_t node = plist_array_get_item(message, 1); + char *str = NULL; + if (plist_get_node_type(node) == PLIST_STRING) { + plist_get_string_val(node, &str); + } + if (!str) { + printf("ERROR: Malformed DLContentsOfDirectory message\n"); + // TODO error handling + return; + } + + char *path = string_build_path(backup_dir, str, NULL); + free(str); + + plist_t dirlist = plist_new_dict(); + + DIR* cur_dir = opendir(path); + if (cur_dir) { + struct dirent* ep; + while ((ep = readdir(cur_dir))) { + if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { + continue; + } + char *fpath = string_build_path(path, ep->d_name, NULL); + if (fpath) { + plist_t fdict = plist_new_dict(); + struct stat st; + stat(fpath, &st); + const char *ftype = "DLFileTypeUnknown"; + if (S_ISDIR(st.st_mode)) { + ftype = "DLFileTypeDirectory"; + } else if (S_ISREG(st.st_mode)) { + ftype = "DLFileTypeRegular"; + } + plist_dict_set_item(fdict, "DLFileType", plist_new_string(ftype)); + plist_dict_set_item(fdict, "DLFileSize", plist_new_uint(st.st_size)); + plist_dict_set_item(fdict, "DLFileModificationDate", + plist_new_date(st.st_mtime - MAC_EPOCH, 0)); + + plist_dict_set_item(dirlist, ep->d_name, fdict); + free(fpath); + } + } + closedir(cur_dir); + } + free(path); + + /* TODO error handling */ + mobilebackup2_error_t err = mobilebackup2_send_status_response(mobilebackup2, 0, NULL, dirlist); + plist_free(dirlist); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Could not send status response, error %d\n", err); + } +} + +static void mb2_handle_make_directory(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir) +{ + if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 2 || !backup_dir) return; + + plist_t dir = plist_array_get_item(message, 1); + char *str = NULL; + int errcode = 0; + char *errdesc = NULL; + plist_get_string_val(dir, &str); + + char *newpath = string_build_path(backup_dir, str, NULL); + free(str); + + if (mkdir_with_parents(newpath, 0755) < 0) { + errdesc = strerror(errno); + if (errno != EEXIST) { + printf("mkdir: %s (%d)\n", errdesc, errno); + } + errcode = errno_to_device_error(errno); + } + free(newpath); + mobilebackup2_error_t err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, NULL); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Could not send status response, error %d\n", err); + } +} + +static void mb2_copy_file_by_path(const char *src, const char *dst) +{ + FILE *from, *to; + char buf[BUFSIZ]; + size_t length; + + /* open source file */ + if ((from = fopen(src, "rb")) == NULL) { + printf("Cannot open source path '%s'.\n", src); + return; + } + + /* open destination file */ + if ((to = fopen(dst, "wb")) == NULL) { + printf("Cannot open destination file '%s'.\n", dst); + fclose(from); + return; + } + + /* copy the file */ + while ((length = fread(buf, 1, BUFSIZ, from)) != 0) { + fwrite(buf, 1, length, to); + } + + if(fclose(from) == EOF) { + printf("Error closing source file.\n"); + } + + if(fclose(to) == EOF) { + printf("Error closing destination file.\n"); + } +} + +static void mb2_copy_directory_by_path(const char *src, const char *dst) +{ + if (!src || !dst) { + return; + } + + struct stat st; + + /* if src does not exist */ + if ((stat(src, &st) < 0) || !S_ISDIR(st.st_mode)) { + printf("ERROR: Source directory does not exist '%s': %s (%d)\n", src, strerror(errno), errno); + return; + } + + /* if dst directory does not exist */ + if ((stat(dst, &st) < 0) || !S_ISDIR(st.st_mode)) { + /* create it */ + if (mkdir_with_parents(dst, 0755) < 0) { + printf("ERROR: Unable to create destination directory '%s': %s (%d)\n", dst, strerror(errno), errno); + return; + } + } + + /* loop over src directory contents */ + DIR *cur_dir = opendir(src); + if (cur_dir) { + struct dirent* ep; + while ((ep = readdir(cur_dir))) { + if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { + continue; + } + char *srcpath = string_build_path(src, ep->d_name, NULL); + char *dstpath = string_build_path(dst, ep->d_name, NULL); + if (srcpath && dstpath) { + /* copy file */ + mb2_copy_file_by_path(srcpath, dstpath); + } + + if (srcpath) + free(srcpath); + if (dstpath) + free(dstpath); + } + closedir(cur_dir); + } +} + +#ifdef WIN32 +#define BS_CC '\b' +#define my_getch getch +#else +#define BS_CC 0x7f +static int my_getch(void) +{ + struct termios oldt, newt; + int ch; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return ch; +} +#endif + +static void get_hidden_input(char *buf, int maxlen) +{ + int pwlen = 0; + int c; + + while ((c = my_getch())) { + if ((c == '\r') || (c == '\n')) { + break; + } + if (isprint(c)) { + if (pwlen < maxlen-1) + buf[pwlen++] = c; + fputc('*', stderr); + } else if (c == BS_CC) { + if (pwlen > 0) { + fputs("\b \b", stderr); + pwlen--; + } + } + } + buf[pwlen] = 0; +} + +static char* ask_for_password(const char* msg, int type_again) +{ + char pwbuf[256]; + + fprintf(stderr, "%s: ", msg); + fflush(stderr); + get_hidden_input(pwbuf, 256); + fputc('\n', stderr); + + if (type_again) { + char pwrep[256]; + + fprintf(stderr, "%s (repeat): ", msg); + fflush(stderr); + get_hidden_input(pwrep, 256); + fputc('\n', stderr); + + if (strcmp(pwbuf, pwrep) != 0) { + printf("ERROR: passwords don't match\n"); + return NULL; + } + } + return strdup(pwbuf); +} + +/** + * signal handler function for cleaning up properly + */ +static void clean_exit(int sig) +{ + fprintf(stderr, "Exiting...\n"); + quit_flag++; +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] CMD [CMDOPTIONS] DIRECTORY\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Create or restore backup in/from the specified directory.\n" + "\n" + "CMD:\n" + " backup create backup for the device\n" + " --full force full backup from device.\n" + " restore restore last backup to the device\n" + " --system restore system files, too.\n" + " --no-reboot do NOT reboot the device when done (default: yes).\n" + " --copy create a copy of backup folder before restoring.\n" + " --settings restore device settings from the backup.\n" + " --remove remove items which are not being restored\n" + " --skip-apps do not trigger re-installation of apps after restore\n" + " --password PWD supply the password for the encrypted source backup\n" + " info show details about last completed backup of device\n" + " list list files of last completed backup in CSV format\n" + " unback unpack a completed backup in DIRECTORY/_unback_/\n" + " encryption on|off [PWD] enable or disable backup encryption\n" + " changepw [OLD NEW] change backup password on target device\n" + " cloud on|off enable or disable cloud use (requires iCloud account)\n" + "\n" + "NOTE: Passwords will be requested in interactive mode (-i) if omitted, or can\n" + "be passed via environment variable BACKUP_PASSWORD/BACKUP_PASSWORD_NEW.\n" + "See man page for further details.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -s, --source UDID use backup data from device specified by UDID\n" + " -n, --network connect to network device\n" + " -i, --interactive request passwords interactively\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +#define DEVICE_VERSION(maj, min, patch) ((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF)) + +int main(int argc, char *argv[]) +{ + idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; + int i = 0; + char* udid = NULL; + char* source_udid = NULL; + int use_network = 0; + lockdownd_service_descriptor_t service = NULL; + int cmd = -1; + int cmd_flags = 0; + int is_full_backup = 0; + int result_code = -1; + char* backup_directory = NULL; + int interactive_mode = 0; + char* backup_password = NULL; + char* newpw = NULL; + struct stat st; + plist_t node_tmp = NULL; + plist_t info_plist = NULL; + plist_t opts = NULL; + + idevice_t device = NULL; + afc_client_t afc = NULL; + np_client_t np = NULL; + lockdownd_client_t lockdown = NULL; + mobilebackup2_client_t mobilebackup2 = NULL; + mobilebackup2_error_t err; + uint64_t lockfile = 0; + +#define OPT_SYSTEM 1 +#define OPT_REBOOT 2 +#define OPT_NO_REBOOT 3 +#define OPT_COPY 4 +#define OPT_SETTINGS 5 +#define OPT_REMOVE 6 +#define OPT_SKIP_APPS 7 +#define OPT_PASSWORD 8 +#define OPT_FULL 9 + + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "source", required_argument, NULL, 's' }, + { "interactive", no_argument, NULL, 'i' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + // command options: + { "system", no_argument, NULL, OPT_SYSTEM }, + { "reboot", no_argument, NULL, OPT_REBOOT }, + { "no-reboot", no_argument, NULL, OPT_NO_REBOOT }, + { "copy", no_argument, NULL, OPT_COPY }, + { "settings", no_argument, NULL, OPT_SETTINGS }, + { "remove", no_argument, NULL, OPT_REMOVE }, + { "skip-apps", no_argument, NULL, OPT_SKIP_APPS }, + { "password", required_argument, NULL, OPT_PASSWORD }, + { "full", no_argument, NULL, OPT_FULL }, + { NULL, 0, NULL, 0} + }; + + /* we need to exit cleanly on running backups and restores or we cause havok */ + signal(SIGINT, clean_exit); + signal(SIGTERM, clean_exit); +#ifndef WIN32 + signal(SIGQUIT, clean_exit); + signal(SIGPIPE, SIG_IGN); +#endif + + /* parse cmdline args */ + while ((c = getopt_long(argc, argv, "dhu:s:inv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = strdup(optarg); + break; + case 's': + if (!*optarg) { + fprintf(stderr, "ERROR: SOURCE argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + source_udid = strdup(optarg); + break; + case 'i': + interactive_mode = 1; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + case OPT_SYSTEM: + cmd_flags |= CMD_FLAG_RESTORE_SYSTEM_FILES; + break; + case OPT_REBOOT: + cmd_flags &= ~CMD_FLAG_RESTORE_NO_REBOOT; + break; + case OPT_NO_REBOOT: + cmd_flags |= CMD_FLAG_RESTORE_NO_REBOOT; + break; + case OPT_COPY: + cmd_flags |= CMD_FLAG_RESTORE_COPY_BACKUP; + break; + case OPT_SETTINGS: + cmd_flags |= CMD_FLAG_RESTORE_SETTINGS; + break; + case OPT_REMOVE: + cmd_flags |= CMD_FLAG_RESTORE_REMOVE_ITEMS; + break; + case OPT_SKIP_APPS: + cmd_flags |= CMD_FLAG_RESTORE_SKIP_APPS; + break; + case OPT_PASSWORD: + free(backup_password); + backup_password = strdup(optarg); + break; + case OPT_FULL: + cmd_flags |= CMD_FLAG_FORCE_FULL_BACKUP; + break; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + if (!argv[0]) { + fprintf(stderr, "ERROR: No command specified.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (!strcmp(argv[0], "backup")) { + cmd = CMD_BACKUP; + } + else if (!strcmp(argv[0], "restore")) { + cmd = CMD_RESTORE; + } + else if (!strcmp(argv[0], "cloud")) { + cmd = CMD_CLOUD; + i = 1; + if (!argv[i]) { + fprintf(stderr, "ERROR: No argument given for cloud command; requires either 'on' or 'off'.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + if (!strcmp(argv[i], "on")) { + cmd_flags |= CMD_FLAG_CLOUD_ENABLE; + } else if (!strcmp(argv[i], "off")) { + cmd_flags |= CMD_FLAG_CLOUD_DISABLE; + } else { + fprintf(stderr, "ERROR: Invalid argument '%s' for cloud command; must be either 'on' or 'off'.\n", argv[i]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + } + else if (!strcmp(argv[0], "info")) { + cmd = CMD_INFO; + verbose = 0; + } + else if (!strcmp(argv[0], "list")) { + cmd = CMD_LIST; + verbose = 0; + } + else if (!strcmp(argv[0], "unback")) { + cmd = CMD_UNBACK; + } + else if (!strcmp(argv[0], "encryption")) { + cmd = CMD_CHANGEPW; + i = 1; + if (!argv[i]) { + fprintf(stderr, "ERROR: No argument given for encryption command; requires either 'on' or 'off'.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + if (!strcmp(argv[i], "on")) { + cmd_flags |= CMD_FLAG_ENCRYPTION_ENABLE; + } else if (!strcmp(argv[i], "off")) { + cmd_flags |= CMD_FLAG_ENCRYPTION_DISABLE; + } else { + fprintf(stderr, "ERROR: Invalid argument '%s' for encryption command; must be either 'on' or 'off'.\n", argv[i]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + // check if a password was given on the command line + free(newpw); + newpw = NULL; + free(backup_password); + backup_password = NULL; + i++; + if (argv[i]) { + if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { + newpw = strdup(argv[i]); + } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { + backup_password = strdup(argv[i]); + } + } + } + else if (!strcmp(argv[0], "changepw")) { + cmd = CMD_CHANGEPW; + cmd_flags |= CMD_FLAG_ENCRYPTION_CHANGEPW; + // check if passwords were given on command line + free(newpw); + newpw = NULL; + free(backup_password); + backup_password = NULL; + i = 1; + if (argv[i]) { + backup_password = strdup(argv[i]); + i++; + if (!argv[i]) { + fprintf(stderr, "ERROR: Old and new passwords have to be passed as arguments for the changepw command\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + newpw = strdup(argv[i]); + } + } + + i++; + if (argv[i]) { + backup_directory = argv[i]; + } + + /* verify options */ + if (cmd == -1) { + fprintf(stderr, "ERROR: Unsupported command '%s'.\n", argv[0]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (cmd == CMD_CHANGEPW || cmd == CMD_CLOUD) { + backup_directory = (char*)".this_folder_is_not_present_on_purpose"; + } else { + if (backup_directory == NULL) { + fprintf(stderr, "ERROR: No target backup directory specified.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + /* verify if passed backup directory exists */ + if (stat(backup_directory, &st) != 0) { + fprintf(stderr, "ERROR: Backup directory \"%s\" does not exist!\n", backup_directory); + return -1; + } + } + + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + return -1; + } + + if (!udid) { + idevice_get_udid(device, &udid); + } + + if (!source_udid) { + source_udid = strdup(udid); + } + + uint8_t is_encrypted = 0; + char *info_path = NULL; + if (cmd == CMD_CHANGEPW) { + if (!interactive_mode) { + if (!newpw) { + newpw = getenv("BACKUP_PASSWORD_NEW"); + if (newpw) { + newpw = strdup(newpw); + } + } + if (!backup_password) { + backup_password = getenv("BACKUP_PASSWORD"); + if (backup_password) { + backup_password = strdup(backup_password); + } + } + } + if (!interactive_mode && !backup_password && !newpw) { + idevice_free(device); + printf("ERROR: Can't get password input in non-interactive mode. Either pass password(s) on the command line, or enable interactive mode with -i or --interactive.\n"); + return -1; + } + } else if (cmd != CMD_CLOUD) { + /* backup directory must contain an Info.plist */ + info_path = string_build_path(backup_directory, source_udid, "Info.plist", NULL); + if (cmd == CMD_RESTORE || cmd == CMD_UNBACK) { + if (stat(info_path, &st) != 0) { + idevice_free(device); + free(info_path); + printf("ERROR: Backup directory \"%s\" is invalid. No Info.plist found for UDID %s.\n", backup_directory, source_udid); + return -1; + } + char* manifest_path = string_build_path(backup_directory, source_udid, "Manifest.plist", NULL); + if (stat(manifest_path, &st) != 0) { + free(info_path); + } + plist_t manifest_plist = NULL; + plist_read_from_file(manifest_path, &manifest_plist, NULL); + if (!manifest_plist) { + idevice_free(device); + free(info_path); + free(manifest_path); + printf("ERROR: Backup directory \"%s\" is invalid. No Manifest.plist found for UDID %s.\n", backup_directory, source_udid); + return -1; + } + node_tmp = plist_dict_get_item(manifest_plist, "IsEncrypted"); + if (node_tmp && (plist_get_node_type(node_tmp) == PLIST_BOOLEAN)) { + plist_get_bool_val(node_tmp, &is_encrypted); + } + plist_free(manifest_plist); + free(manifest_path); + } + PRINT_VERBOSE(1, "Backup directory is \"%s\"\n", backup_directory); + } + + if (cmd != CMD_CLOUD && is_encrypted) { + PRINT_VERBOSE(1, "This is an encrypted backup.\n"); + if (backup_password == NULL) { + backup_password = getenv("BACKUP_PASSWORD"); + if (backup_password) { + backup_password = strdup(backup_password); + } + } + if (backup_password == NULL) { + if (interactive_mode) { + backup_password = ask_for_password("Enter backup password", 0); + } + if (!backup_password || (strlen(backup_password) == 0)) { + if (backup_password) { + free(backup_password); + } + idevice_free(device); + if (cmd == CMD_RESTORE) { + printf("ERROR: a backup password is required to restore an encrypted backup. Cannot continue.\n"); + } else if (cmd == CMD_UNBACK) { + printf("ERROR: a backup password is required to unback an encrypted backup. Cannot continue.\n"); + } + return -1; + } + } + } + + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret); + idevice_free(device); + return -1; + } + + uint8_t willEncrypt = 0; + node_tmp = NULL; + lockdownd_get_value(lockdown, "com.apple.mobile.backup", "WillEncrypt", &node_tmp); + if (node_tmp) { + if (plist_get_node_type(node_tmp) == PLIST_BOOLEAN) { + plist_get_bool_val(node_tmp, &willEncrypt); + } + plist_free(node_tmp); + node_tmp = NULL; + } + + /* get ProductVersion */ + char *product_version = NULL; + int device_version = 0; + node_tmp = NULL; + lockdownd_get_value(lockdown, NULL, "ProductVersion", &node_tmp); + if (node_tmp) { + if (plist_get_node_type(node_tmp) == PLIST_STRING) { + plist_get_string_val(node_tmp, &product_version); + } + plist_free(node_tmp); + node_tmp = NULL; + } + if (product_version) { + int vers[3] = { 0, 0, 0 }; + if (sscanf(product_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) { + device_version = DEVICE_VERSION(vers[0], vers[1], vers[2]); + } + } + + /* start notification_proxy */ + ldret = lockdownd_start_service(lockdown, NP_SERVICE_NAME, &service); + if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) { + np_client_new(device, service, &np); + np_set_notify_callback(np, notify_cb, NULL); + const char *noties[5] = { + NP_SYNC_CANCEL_REQUEST, + NP_SYNC_SUSPEND_REQUEST, + NP_SYNC_RESUME_REQUEST, + NP_BACKUP_DOMAIN_CHANGED, + NULL + }; + np_observe_notifications(np, noties); + } else { + printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret)); + cmd = CMD_LEAVE; + goto checkpoint; + } + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) { + /* start AFC, we need this for the lock file */ + ldret = lockdownd_start_service(lockdown, AFC_SERVICE_NAME, &service); + if ((ldret == LOCKDOWN_E_SUCCESS) && service->port) { + afc_client_new(device, service, &afc); + } else { + printf("ERROR: Could not start service %s: %s\n", AFC_SERVICE_NAME, lockdownd_strerror(ldret)); + cmd = CMD_LEAVE; + goto checkpoint; + } + } + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + /* start mobilebackup service and retrieve port */ + ldret = lockdownd_start_service_with_escrow_bag(lockdown, MOBILEBACKUP2_SERVICE_NAME, &service); + lockdownd_client_free(lockdown); + lockdown = NULL; + if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) { + PRINT_VERBOSE(1, "Started \"%s\" service on port %d.\n", MOBILEBACKUP2_SERVICE_NAME, service->port); + mobilebackup2_client_new(device, service, &mobilebackup2); + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + /* send Hello message */ + double local_versions[2] = {2.0, 2.1}; + double remote_version = 0.0; + err = mobilebackup2_version_exchange(mobilebackup2, local_versions, 2, &remote_version); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Could not perform backup protocol version exchange, error code %d\n", err); + cmd = CMD_LEAVE; + goto checkpoint; + } + + PRINT_VERBOSE(1, "Negotiated Protocol Version %.1f\n", remote_version); + + /* check abort conditions */ + if (quit_flag > 0) { + PRINT_VERBOSE(1, "Aborting as requested by user...\n"); + cmd = CMD_LEAVE; + goto checkpoint; + } + + /* verify existing Info.plist */ + if (info_path && (stat(info_path, &st) == 0) && cmd != CMD_CLOUD) { + PRINT_VERBOSE(1, "Reading Info.plist from backup.\n"); + plist_read_from_file(info_path, &info_plist, NULL); + + if (!info_plist) { + printf("Could not read Info.plist\n"); + is_full_backup = 1; + } + } else { + if (cmd == CMD_RESTORE) { + printf("Aborting restore. Info.plist is missing.\n"); + cmd = CMD_LEAVE; + } else { + is_full_backup = 1; + } + } + + if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) { + do_post_notification(device, NP_SYNC_WILL_START); + afc_file_open(afc, "/com.apple.itunes.lock_sync", AFC_FOPEN_RW, &lockfile); + } + if (lockfile) { + afc_error_t aerr; + do_post_notification(device, NP_SYNC_LOCK_REQUEST); + for (i = 0; i < LOCK_ATTEMPTS; i++) { + aerr = afc_file_lock(afc, lockfile, AFC_LOCK_EX); + if (aerr == AFC_E_SUCCESS) { + do_post_notification(device, NP_SYNC_DID_START); + break; + } + if (aerr == AFC_E_OP_WOULD_BLOCK) { + usleep(LOCK_WAIT); + continue; + } + + fprintf(stderr, "ERROR: could not lock file! error code: %d\n", aerr); + afc_file_close(afc, lockfile); + lockfile = 0; + cmd = CMD_LEAVE; + } + if (i == LOCK_ATTEMPTS) { + fprintf(stderr, "ERROR: timeout while locking for sync\n"); + afc_file_close(afc, lockfile); + lockfile = 0; + cmd = CMD_LEAVE; + } + } + +checkpoint: + + switch(cmd) { + case CMD_CLOUD: + opts = plist_new_dict(); + plist_dict_set_item(opts, "CloudBackupState", plist_new_bool(cmd_flags & CMD_FLAG_CLOUD_ENABLE ? 1: 0)); + err = mobilebackup2_send_request(mobilebackup2, "EnableCloudBackup", udid, source_udid, opts); + plist_free(opts); + opts = NULL; + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Error setting cloud backup state on device, error code %d\n", err); + cmd = CMD_LEAVE; + } + break; + case CMD_BACKUP: + PRINT_VERBOSE(1, "Starting backup...\n"); + + /* make sure backup device sub-directory exists */ + char* devbackupdir = string_build_path(backup_directory, source_udid, NULL); + __mkdir(devbackupdir, 0755); + free(devbackupdir); + + if (strcmp(source_udid, udid) != 0) { + /* handle different source backup directory */ + // make sure target backup device sub-directory exists + devbackupdir = string_build_path(backup_directory, udid, NULL); + __mkdir(devbackupdir, 0755); + free(devbackupdir); + + // use Info.plist path in target backup folder */ + free(info_path); + info_path = string_build_path(backup_directory, udid, "Info.plist", NULL); + } + + /* TODO: check domain com.apple.mobile.backup key RequiresEncrypt and WillEncrypt with lockdown */ + /* TODO: verify battery on AC enough battery remaining */ + + /* re-create Info.plist (Device infos, IC-Info.sidb, photos, app_ids, iTunesPrefs) */ + if (info_plist) { + plist_free(info_plist); + info_plist = NULL; + } + info_plist = mobilebackup_factory_info_plist_new(udid, device, afc); + if (!info_plist) { + fprintf(stderr, "Failed to generate Info.plist - aborting\n"); + cmd = CMD_LEAVE; + } + remove_file(info_path); + plist_write_to_file(info_plist, info_path, PLIST_FORMAT_XML, 0); + free(info_path); + + plist_free(info_plist); + info_plist = NULL; + + if (cmd_flags & CMD_FLAG_FORCE_FULL_BACKUP) { + PRINT_VERBOSE(1, "Enforcing full backup from device.\n"); + opts = plist_new_dict(); + plist_dict_set_item(opts, "ForceFullBackup", plist_new_bool(1)); + } + /* request backup from device with manifest from last backup */ + if (willEncrypt) { + PRINT_VERBOSE(1, "Backup will be encrypted.\n"); + } else { + PRINT_VERBOSE(1, "Backup will be unencrypted.\n"); + } + PRINT_VERBOSE(1, "Requesting backup from device...\n"); + err = mobilebackup2_send_request(mobilebackup2, "Backup", udid, source_udid, opts); + if (opts) + plist_free(opts); + if (err == MOBILEBACKUP2_E_SUCCESS) { + if (is_full_backup) { + PRINT_VERBOSE(1, "Full backup mode.\n"); + } else { + PRINT_VERBOSE(1, "Incremental backup mode.\n"); + } + } else { + if (err == MOBILEBACKUP2_E_BAD_VERSION) { + printf("ERROR: Could not start backup process: backup protocol version mismatch!\n"); + } else if (err == MOBILEBACKUP2_E_REPLY_NOT_OK) { + printf("ERROR: Could not start backup process: device refused to start the backup process.\n"); + } else { + printf("ERROR: Could not start backup process: unspecified error occurred\n"); + } + cmd = CMD_LEAVE; + } + break; + case CMD_RESTORE: + /* TODO: verify battery on AC enough battery remaining */ + + /* verify if Status.plist says we read from an successful backup */ + if (!mb2_status_check_snapshot_state(backup_directory, source_udid, "finished")) { + printf("ERROR: Cannot ensure we restore from a successful backup. Aborting.\n"); + cmd = CMD_LEAVE; + break; + } + + PRINT_VERBOSE(1, "Starting Restore...\n"); + + opts = plist_new_dict(); + plist_dict_set_item(opts, "RestoreSystemFiles", plist_new_bool(cmd_flags & CMD_FLAG_RESTORE_SYSTEM_FILES)); + PRINT_VERBOSE(1, "Restoring system files: %s\n", (cmd_flags & CMD_FLAG_RESTORE_SYSTEM_FILES ? "Yes":"No")); + if (cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT) + plist_dict_set_item(opts, "RestoreShouldReboot", plist_new_bool(0)); + PRINT_VERBOSE(1, "Rebooting after restore: %s\n", (cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT ? "No":"Yes")); + if ((cmd_flags & CMD_FLAG_RESTORE_COPY_BACKUP) == 0) + plist_dict_set_item(opts, "RestoreDontCopyBackup", plist_new_bool(1)); + PRINT_VERBOSE(1, "Don't copy backup: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_COPY_BACKUP) == 0 ? "Yes":"No")); + plist_dict_set_item(opts, "RestorePreserveSettings", plist_new_bool((cmd_flags & CMD_FLAG_RESTORE_SETTINGS) == 0)); + PRINT_VERBOSE(1, "Preserve settings of device: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_SETTINGS) == 0 ? "Yes":"No")); + plist_dict_set_item(opts, "RemoveItemsNotRestored", plist_new_bool(cmd_flags & CMD_FLAG_RESTORE_REMOVE_ITEMS)); + PRINT_VERBOSE(1, "Remove items that are not restored: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_REMOVE_ITEMS) ? "Yes":"No")); + if (backup_password != NULL) { + plist_dict_set_item(opts, "Password", plist_new_string(backup_password)); + } + PRINT_VERBOSE(1, "Backup password: %s\n", (backup_password == NULL ? "No":"Yes")); + + if (cmd_flags & CMD_FLAG_RESTORE_SKIP_APPS) { + PRINT_VERBOSE(1, "Not writing RestoreApplications.plist - apps will not be re-installed after restore\n"); + } else { + /* Write /iTunesRestore/RestoreApplications.plist so that the device will start + * restoring applications once the rest of the restore process is finished */ + if (write_restore_applications(info_plist, afc) < 0) { + cmd = CMD_LEAVE; + break; + } + PRINT_VERBOSE(1, "Wrote RestoreApplications.plist\n"); + } + + /* Start restore */ + err = mobilebackup2_send_request(mobilebackup2, "Restore", udid, source_udid, opts); + plist_free(opts); + if (err != MOBILEBACKUP2_E_SUCCESS) { + if (err == MOBILEBACKUP2_E_BAD_VERSION) { + printf("ERROR: Could not start restore process: backup protocol version mismatch!\n"); + } else if (err == MOBILEBACKUP2_E_REPLY_NOT_OK) { + printf("ERROR: Could not start restore process: device refused to start the restore process.\n"); + } else { + printf("ERROR: Could not start restore process: unspecified error occurred\n"); + } + cmd = CMD_LEAVE; + } + break; + case CMD_INFO: + PRINT_VERBOSE(1, "Requesting backup info from device...\n"); + err = mobilebackup2_send_request(mobilebackup2, "Info", udid, source_udid, NULL); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Error requesting backup info from device, error code %d\n", err); + cmd = CMD_LEAVE; + } + break; + case CMD_LIST: + PRINT_VERBOSE(1, "Requesting backup list from device...\n"); + err = mobilebackup2_send_request(mobilebackup2, "List", udid, source_udid, NULL); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Error requesting backup list from device, error code %d\n", err); + cmd = CMD_LEAVE; + } + break; + case CMD_UNBACK: + PRINT_VERBOSE(1, "Starting to unpack backup...\n"); + if (backup_password != NULL) { + opts = plist_new_dict(); + plist_dict_set_item(opts, "Password", plist_new_string(backup_password)); + } + PRINT_VERBOSE(1, "Backup password: %s\n", (backup_password == NULL ? "No":"Yes")); + err = mobilebackup2_send_request(mobilebackup2, "Unback", udid, source_udid, opts); + if (backup_password !=NULL) { + plist_free(opts); + } + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Error requesting unback operation from device, error code %d\n", err); + cmd = CMD_LEAVE; + } + break; + case CMD_CHANGEPW: + opts = plist_new_dict(); + plist_dict_set_item(opts, "TargetIdentifier", plist_new_string(udid)); + if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { + if (!willEncrypt) { + if (!newpw) { + newpw = getenv("BACKUP_PASSWORD"); + if (newpw) { + newpw = strdup(newpw); + } + } + if (!newpw) { + newpw = ask_for_password("Enter new backup password", 1); + } + if (!newpw) { + printf("No backup password given. Aborting.\n"); + } + } else { + printf("ERROR: Backup encryption is already enabled. Aborting.\n"); + cmd = CMD_LEAVE; + if (newpw) { + free(newpw); + newpw = NULL; + } + } + } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { + if (willEncrypt) { + if (!backup_password) { + backup_password = getenv("BACKUP_PASSWORD"); + if (backup_password) { + backup_password = strdup(backup_password); + } + } + if (!backup_password) { + backup_password = ask_for_password("Enter current backup password", 0); + } + } else { + printf("ERROR: Backup encryption is not enabled. Aborting.\n"); + cmd = CMD_LEAVE; + if (backup_password) { + free(backup_password); + backup_password = NULL; + } + } + } else if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) { + if (willEncrypt) { + if (!backup_password) { + backup_password = ask_for_password("Enter old backup password", 0); + newpw = ask_for_password("Enter new backup password", 1); + } + } else { + printf("ERROR: Backup encryption is not enabled so can't change password. Aborting.\n"); + cmd = CMD_LEAVE; + if (newpw) { + free(newpw); + newpw = NULL; + } + if (backup_password) { + free(backup_password); + backup_password = NULL; + } + } + } + if (newpw) { + plist_dict_set_item(opts, "NewPassword", plist_new_string(newpw)); + } + if (backup_password) { + plist_dict_set_item(opts, "OldPassword", plist_new_string(backup_password)); + } + if (newpw || backup_password) { + mobilebackup2_send_message(mobilebackup2, "ChangePassword", opts); + uint8_t passcode_hint = 0; + if (device_version >= DEVICE_VERSION(13,0,0)) { + diagnostics_relay_client_t diag = NULL; + if (diagnostics_relay_client_start_service(device, &diag, TOOL_NAME) == DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_t dict = NULL; + plist_t keys = plist_new_array(); + plist_array_append_item(keys, plist_new_string("PasswordConfigured")); + if (diagnostics_relay_query_mobilegestalt(diag, keys, &dict) == DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_t node = plist_access_path(dict, 2, "MobileGestalt", "PasswordConfigured"); + plist_get_bool_val(node, &passcode_hint); + } + plist_free(keys); + plist_free(dict); + diagnostics_relay_goodbye(diag); + diagnostics_relay_client_free(diag); + } + } + if (passcode_hint) { + if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) { + PRINT_VERBOSE(1, "Please confirm changing the backup password by entering the passcode on the device.\n"); + } else if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { + PRINT_VERBOSE(1, "Please confirm enabling the backup encryption by entering the passcode on the device.\n"); + } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { + PRINT_VERBOSE(1, "Please confirm disabling the backup encryption by entering the passcode on the device.\n"); + } + } + /*if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { + int retr = 10; + while ((retr-- >= 0) && !backup_domain_changed) { + sleep(1); + } + }*/ + } else { + cmd = CMD_LEAVE; + } + plist_free(opts); + break; + default: + break; + } + + if (cmd != CMD_LEAVE) { + /* reset operation success status */ + int operation_ok = 0; + plist_t message = NULL; + + mobilebackup2_error_t mberr; + char *dlmsg = NULL; + int file_count = 0; + int errcode = 0; + const char *errdesc = NULL; + int progress_finished = 0; + + /* process series of DLMessage* operations */ + do { + free(dlmsg); + dlmsg = NULL; + mberr = mobilebackup2_receive_message(mobilebackup2, &message, &dlmsg); + if (mberr == MOBILEBACKUP2_E_RECEIVE_TIMEOUT) { + PRINT_VERBOSE(2, "Device is not ready yet, retrying...\n"); + goto files_out; + } else if (mberr != MOBILEBACKUP2_E_SUCCESS) { + PRINT_VERBOSE(0, "ERROR: Could not receive from mobilebackup2 (%d)\n", mberr); + quit_flag++; + goto files_out; + } + + if (!strcmp(dlmsg, "DLMessageDownloadFiles")) { + /* device wants to download files from the computer */ + mb2_set_overall_progress_from_message(message, dlmsg); + mb2_handle_send_files(mobilebackup2, message, backup_directory); + } else if (!strcmp(dlmsg, "DLMessageUploadFiles")) { + /* device wants to send files to the computer */ + mb2_set_overall_progress_from_message(message, dlmsg); + file_count += mb2_handle_receive_files(mobilebackup2, message, backup_directory); + } else if (!strcmp(dlmsg, "DLMessageGetFreeDiskSpace")) { + /* device wants to know how much disk space is available on the computer */ + uint64_t freespace = 0; + int res = -1; +#ifdef WIN32 + if (GetDiskFreeSpaceEx(backup_directory, (PULARGE_INTEGER)&freespace, NULL, NULL)) { + res = 0; + } +#else + struct statvfs fs; + memset(&fs, '\0', sizeof(fs)); + res = statvfs(backup_directory, &fs); + if (res == 0) { + freespace = (uint64_t)fs.f_bavail * (uint64_t)fs.f_bsize; + } +#endif + plist_t freespace_item = plist_new_uint(freespace); + mobilebackup2_send_status_response(mobilebackup2, res, NULL, freespace_item); + plist_free(freespace_item); + } else if (!strcmp(dlmsg, "DLMessagePurgeDiskSpace")) { + /* device wants to purge disk space on the host - not supported */ + plist_t empty_dict = plist_new_dict(); + err = mobilebackup2_send_status_response(mobilebackup2, -1, "Operation not supported", empty_dict); + plist_free(empty_dict); + } else if (!strcmp(dlmsg, "DLContentsOfDirectory")) { + /* list directory contents */ + mb2_handle_list_directory(mobilebackup2, message, backup_directory); + } else if (!strcmp(dlmsg, "DLMessageCreateDirectory")) { + /* make a directory */ + mb2_handle_make_directory(mobilebackup2, message, backup_directory); + } else if (!strcmp(dlmsg, "DLMessageMoveFiles") || !strcmp(dlmsg, "DLMessageMoveItems")) { + /* perform a series of rename operations */ + mb2_set_overall_progress_from_message(message, dlmsg); + plist_t moves = plist_array_get_item(message, 1); + uint32_t cnt = plist_dict_get_size(moves); + PRINT_VERBOSE(1, "Moving %d file%s\n", cnt, (cnt == 1) ? "" : "s"); + plist_dict_iter iter = NULL; + plist_dict_new_iter(moves, &iter); + errcode = 0; + errdesc = NULL; + if (iter) { + char *key = NULL; + plist_t val = NULL; + do { + plist_dict_next_item(moves, iter, &key, &val); + if (key && (plist_get_node_type(val) == PLIST_STRING)) { + char *str = NULL; + plist_get_string_val(val, &str); + if (str) { + char *newpath = string_build_path(backup_directory, str, NULL); + free(str); + char *oldpath = string_build_path(backup_directory, key, NULL); + + if ((stat(newpath, &st) == 0) && S_ISDIR(st.st_mode)) + rmdir_recursive(newpath); + else + remove_file(newpath); + if (rename(oldpath, newpath) < 0) { + printf("Renameing '%s' to '%s' failed: %s (%d)\n", oldpath, newpath, strerror(errno), errno); + errcode = errno_to_device_error(errno); + errdesc = strerror(errno); + break; + } + free(oldpath); + free(newpath); + } + free(key); + key = NULL; + } + } while (val); + free(iter); + } else { + errcode = -1; + errdesc = "Could not create dict iterator"; + printf("Could not create dict iterator\n"); + } + plist_t empty_dict = plist_new_dict(); + err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict); + plist_free(empty_dict); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Could not send status response, error %d\n", err); + } + } else if (!strcmp(dlmsg, "DLMessageRemoveFiles") || !strcmp(dlmsg, "DLMessageRemoveItems")) { + mb2_set_overall_progress_from_message(message, dlmsg); + plist_t removes = plist_array_get_item(message, 1); + uint32_t cnt = plist_array_get_size(removes); + PRINT_VERBOSE(1, "Removing %d file%s\n", cnt, (cnt == 1) ? "" : "s"); + uint32_t ii = 0; + errcode = 0; + errdesc = NULL; + for (ii = 0; ii < cnt; ii++) { + plist_t val = plist_array_get_item(removes, ii); + if (plist_get_node_type(val) == PLIST_STRING) { + char *str = NULL; + plist_get_string_val(val, &str); + if (str) { + const char *checkfile = strchr(str, '/'); + int suppress_warning = 0; + if (checkfile) { + if (strcmp(checkfile+1, "Manifest.mbdx") == 0) { + suppress_warning = 1; + } + } + char *newpath = string_build_path(backup_directory, str, NULL); + free(str); + int res = 0; + if ((stat(newpath, &st) == 0) && S_ISDIR(st.st_mode)) { + res = rmdir_recursive(newpath); + } else { + res = remove_file(newpath); + } + if (res != 0 && res != ENOENT) { + if (!suppress_warning) + printf("Could not remove '%s': %s (%d)\n", newpath, strerror(res), res); + errcode = errno_to_device_error(res); + errdesc = strerror(res); + } + free(newpath); + } + } + } + plist_t empty_dict = plist_new_dict(); + err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict); + plist_free(empty_dict); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Could not send status response, error %d\n", err); + } + } else if (!strcmp(dlmsg, "DLMessageCopyItem")) { + plist_t srcpath = plist_array_get_item(message, 1); + plist_t dstpath = plist_array_get_item(message, 2); + errcode = 0; + errdesc = NULL; + if ((plist_get_node_type(srcpath) == PLIST_STRING) && (plist_get_node_type(dstpath) == PLIST_STRING)) { + char *src = NULL; + char *dst = NULL; + plist_get_string_val(srcpath, &src); + plist_get_string_val(dstpath, &dst); + if (src && dst) { + char *oldpath = string_build_path(backup_directory, src, NULL); + char *newpath = string_build_path(backup_directory, dst, NULL); + + PRINT_VERBOSE(1, "Copying '%s' to '%s'\n", src, dst); + + /* check that src exists */ + if ((stat(oldpath, &st) == 0) && S_ISDIR(st.st_mode)) { + mb2_copy_directory_by_path(oldpath, newpath); + } else if ((stat(oldpath, &st) == 0) && S_ISREG(st.st_mode)) { + mb2_copy_file_by_path(oldpath, newpath); + } + + free(newpath); + free(oldpath); + } + free(src); + free(dst); + } + plist_t empty_dict = plist_new_dict(); + err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict); + plist_free(empty_dict); + if (err != MOBILEBACKUP2_E_SUCCESS) { + printf("Could not send status response, error %d\n", err); + } + } else if (!strcmp(dlmsg, "DLMessageDisconnect")) { + break; + } else if (!strcmp(dlmsg, "DLMessageProcessMessage")) { + node_tmp = plist_array_get_item(message, 1); + if (plist_get_node_type(node_tmp) != PLIST_DICT) { + printf("Unknown message received!\n"); + } + plist_t nn; + int error_code = -1; + nn = plist_dict_get_item(node_tmp, "ErrorCode"); + if (nn && (plist_get_node_type(nn) == PLIST_UINT)) { + uint64_t ec = 0; + plist_get_uint_val(nn, &ec); + error_code = (uint32_t)ec; + if (error_code == 0) { + operation_ok = 1; + result_code = 0; + } else { + result_code = -error_code; + } + } + nn = plist_dict_get_item(node_tmp, "ErrorDescription"); + char *str = NULL; + if (nn && (plist_get_node_type(nn) == PLIST_STRING)) { + plist_get_string_val(nn, &str); + } + if (error_code != 0) { + if (str) { + printf("ErrorCode %d: %s\n", error_code, str); + } else { + printf("ErrorCode %d: (Unknown)\n", error_code); + } + } + if (str) { + free(str); + } + nn = plist_dict_get_item(node_tmp, "Content"); + if (nn && (plist_get_node_type(nn) == PLIST_STRING)) { + str = NULL; + plist_get_string_val(nn, &str); + PRINT_VERBOSE(1, "Content:\n"); + printf("%s", str); + free(str); + } + break; + } + + /* print status */ + if ((overall_progress > 0) && !progress_finished) { + if (overall_progress >= 100.0F) { + progress_finished = 1; + } + print_progress_real(overall_progress, 0); + PRINT_VERBOSE(1, " Finished\n"); + } + +files_out: + plist_free(message); + message = NULL; + free(dlmsg); + dlmsg = NULL; + + if (quit_flag > 0) { + /* need to cancel the backup here */ + //mobilebackup_send_error(mobilebackup, "Cancelling DLSendFile"); + + /* remove any atomic Manifest.plist.tmp */ + + /*manifest_path = mobilebackup_build_path(backup_directory, "Manifest", ".plist.tmp"); + if (stat(manifest_path, &st) == 0) + remove(manifest_path);*/ + break; + } + } while (1); + + plist_free(message); + free(dlmsg); + + /* report operation status to user */ + switch (cmd) { + case CMD_CLOUD: + if (cmd_flags & CMD_FLAG_CLOUD_ENABLE) { + if (operation_ok) { + PRINT_VERBOSE(1, "Cloud backup has been enabled successfully.\n"); + } else { + PRINT_VERBOSE(1, "Could not enable cloud backup.\n"); + } + } else if (cmd_flags & CMD_FLAG_CLOUD_DISABLE) { + if (operation_ok) { + PRINT_VERBOSE(1, "Cloud backup has been disabled successfully.\n"); + } else { + PRINT_VERBOSE(1, "Could not disable cloud backup.\n"); + } + } + break; + case CMD_BACKUP: + PRINT_VERBOSE(1, "Received %d files from device.\n", file_count); + if (operation_ok && mb2_status_check_snapshot_state(backup_directory, udid, "finished")) { + PRINT_VERBOSE(1, "Backup Successful.\n"); + } else { + if (quit_flag) { + PRINT_VERBOSE(1, "Backup Aborted.\n"); + } else { + PRINT_VERBOSE(1, "Backup Failed (Error Code %d).\n", -result_code); + } + } + break; + case CMD_UNBACK: + if (quit_flag) { + PRINT_VERBOSE(1, "Unback Aborted.\n"); + } else { + PRINT_VERBOSE(1, "The files can now be found in the \"_unback_\" directory.\n"); + PRINT_VERBOSE(1, "Unback Successful.\n"); + } + break; + case CMD_CHANGEPW: + if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { + if (operation_ok) { + PRINT_VERBOSE(1, "Backup encryption has been enabled successfully.\n"); + } else { + PRINT_VERBOSE(1, "Could not enable backup encryption.\n"); + } + } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { + if (operation_ok) { + PRINT_VERBOSE(1, "Backup encryption has been disabled successfully.\n"); + } else { + PRINT_VERBOSE(1, "Could not disable backup encryption.\n"); + } + } else if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) { + if (operation_ok) { + PRINT_VERBOSE(1, "Backup encryption password has been changed successfully.\n"); + } else { + PRINT_VERBOSE(1, "Could not change backup encryption password.\n"); + } + } + break; + case CMD_RESTORE: + if (operation_ok) { + if ((cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT) == 0) + PRINT_VERBOSE(1, "The device should reboot now.\n"); + PRINT_VERBOSE(1, "Restore Successful.\n"); + } else { + afc_remove_path(afc, "/iTunesRestore/RestoreApplications.plist"); + afc_remove_path(afc, "/iTunesRestore"); + if (quit_flag) { + PRINT_VERBOSE(1, "Restore Aborted.\n"); + } else { + PRINT_VERBOSE(1, "Restore Failed (Error Code %d).\n", -result_code); + } + } + break; + case CMD_INFO: + case CMD_LIST: + case CMD_LEAVE: + default: + if (quit_flag) { + PRINT_VERBOSE(1, "Operation Aborted.\n"); + } else if (cmd == CMD_LEAVE) { + PRINT_VERBOSE(1, "Operation Failed.\n"); + } else { + PRINT_VERBOSE(1, "Operation Successful.\n"); + } + break; + } + } + if (lockfile) { + afc_file_lock(afc, lockfile, AFC_LOCK_UN); + afc_file_close(afc, lockfile); + lockfile = 0; + if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) + do_post_notification(device, NP_SYNC_DID_FINISH); + } + } else { + printf("ERROR: Could not start service %s: %s\n", MOBILEBACKUP2_SERVICE_NAME, lockdownd_strerror(ldret)); + lockdownd_client_free(lockdown); + lockdown = NULL; + } + + if (lockdown) { + lockdownd_client_free(lockdown); + lockdown = NULL; + } + + if (mobilebackup2) { + mobilebackup2_client_free(mobilebackup2); + mobilebackup2 = NULL; + } + + if (afc) { + afc_client_free(afc); + afc = NULL; + } + + if (np) { + np_client_free(np); + np = NULL; + } + + idevice_free(device); + device = NULL; + + if (backup_password) { + free(backup_password); + } + + if (udid) { + free(udid); + udid = NULL; + } + if (source_udid) { + free(source_udid); + source_udid = NULL; + } + + return result_code; +} + diff --git a/tools/idevicebtlogger.c b/tools/idevicebtlogger.c new file mode 100644 index 0000000..8de6b22 --- /dev/null +++ b/tools/idevicebtlogger.c @@ -0,0 +1,458 @@ +/* + * idevicebt_packet_logger.c + * Capture Bluetooth HCI traffic to native PKLG or PCAP + * + * Copyright (c) 2021 Geoffrey Kruse, All Rights Reserved. + * Copyright (c) 2022 Matthias Ringwald, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define TOOL_NAME "idevicebtlogger" + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <assert.h> +#include <fcntl.h> + +#ifdef WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#else +#include <arpa/inet.h> +#endif + + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/bt_packet_logger.h> + +typedef enum { + HCI_COMMAND = 0x00, + HCI_EVENT = 0x01, + SENT_ACL_DATA = 0x02, + RECV_ACL_DATA = 0x03, + SENT_SCO_DATA = 0x08, + RECV_SCO_DATA = 0x09, +} PacketLoggerPacketType; + +static int quit_flag = 0; +static int exit_on_disconnect = 0; + +static char* udid = NULL; +static idevice_t device = NULL; +static bt_packet_logger_client_t bt_packet_logger = NULL; +static int use_network = 0; +static char* out_filename = NULL; +static char* log_format_string = NULL; +static FILE * packetlogger_file = NULL; + +static enum { + LOG_FORMAT_PACKETLOGGER, + LOG_FORMAT_PCAP +} log_format = LOG_FORMAT_PACKETLOGGER; + +const uint8_t pcap_file_header[] = { + // Magic Number + 0xA1, 0xB2, 0xC3, 0xD4, + // Major / Minor Version + 0x00, 0x02, 0x00, 0x04, + // Reserved1 + 0x00, 0x00, 0x00, 0x00, + // Reserved2 + 0x00, 0x00, 0x00, 0x00, + // Snaplen == max packet size - use 2kB (larger than any ACL) + 0x00, 0x00, 0x08, 0x00, + // LinkType: DLT_BLUETOOTH_HCI_H4_WITH_PHDR + 0x00, 0x00, 0x00, 201, +}; + +static uint32_t big_endian_read_32(const uint8_t * buffer, int position) +{ + return ((uint32_t) buffer[position+3]) | (((uint32_t)buffer[position+2]) << 8) | (((uint32_t)buffer[position+1]) << 16) | (((uint32_t) buffer[position]) << 24); +} + +static void big_endian_store_32(uint8_t * buffer, uint16_t position, uint32_t value) +{ + uint16_t pos = position; + buffer[pos++] = (uint8_t)(value >> 24); + buffer[pos++] = (uint8_t)(value >> 16); + buffer[pos++] = (uint8_t)(value >> 8); + buffer[pos++] = (uint8_t)(value); +} + +/** + * Callback from the packet logger service to handle packets and log to PacketLogger format + */ +static void bt_packet_logger_callback_packetlogger(uint8_t * data, uint16_t len, void *user_data) +{ + (void) fwrite(data, 1, len, packetlogger_file); +} + +/** + * Callback from the packet logger service to handle packets and log to pcap + */ +static void bt_packet_logger_callback_pcap(uint8_t * data, uint16_t len, void *user_data) +{ + // check len + if (len < 13) { + return; + } + + // parse packet header (ignore len field) + uint32_t ts_secs = big_endian_read_32(data, 4); + uint32_t ts_us = big_endian_read_32(data, 8); + uint8_t packet_type = data[12]; + data += 13; + len -= 13; + + // map PacketLogger packet type onto PCAP direction flag and hci_h4_type + uint8_t direction_in = 0; + uint8_t hci_h4_type = 0xff; + switch(packet_type) { + case HCI_COMMAND: + hci_h4_type = 0x01; + direction_in = 0; + break; + case SENT_ACL_DATA: + hci_h4_type = 0x02; + direction_in = 0; + break; + case RECV_ACL_DATA: + hci_h4_type = 0x02; + direction_in = 1; + break; + case SENT_SCO_DATA: + hci_h4_type = 0x03; + direction_in = 0; + break; + case RECV_SCO_DATA: + hci_h4_type = 0x03; + direction_in = 1; + break; + case HCI_EVENT: + hci_h4_type = 0x04; + direction_in = 1; + break; + default: + // unknown packet logger type, drop packet + return; + } + + // setup pcap record header, 4 byte direction flag, 1 byte HCI H4 packet type, data + uint8_t pcap_record_header[21]; + big_endian_store_32(pcap_record_header, 0, ts_secs); // Timestamp seconds + big_endian_store_32(pcap_record_header, 4, ts_us); // Timestamp microseconds + big_endian_store_32(pcap_record_header, 8, 4 + 1 + len); // Captured Packet Length + big_endian_store_32(pcap_record_header, 12, 4 + 1 + len); // Original Packet Length + big_endian_store_32(pcap_record_header, 16, direction_in); // Direction: Incoming = 1 + pcap_record_header[20] = hci_h4_type; + + // write header + (void) fwrite(pcap_record_header, 1, sizeof(pcap_record_header), packetlogger_file); + + // write packet + (void) fwrite(data, 1, len, packetlogger_file); + + // flush + (void) fflush(packetlogger_file); +} + +/** + * Disable HCI log capture + */ +static void stop_logging(void) +{ + fflush(NULL); + + if (bt_packet_logger) { + bt_packet_logger_client_free(bt_packet_logger); + bt_packet_logger = NULL; + } + + if (device) { + idevice_free(device); + device = NULL; + } +} + +/** + * Enable HCI log capture + */ +static int start_logging(void) +{ + idevice_error_t ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + fprintf(stderr, "Device with udid %s not found!?\n", udid); + return -1; + } + + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not connect to lockdownd: %d\n", lerr); + idevice_free(device); + device = NULL; + return -1; + } + + /* start bt_packet_logger service */ + bt_packet_logger_client_start_service(device, &bt_packet_logger, TOOL_NAME); + + /* start capturing bt_packet_logger */ + void (*callback)(uint8_t * data, uint16_t len, void *user_data); + switch (log_format){ + case LOG_FORMAT_PCAP: + callback = bt_packet_logger_callback_pcap; + break; + case LOG_FORMAT_PACKETLOGGER: + callback = bt_packet_logger_callback_packetlogger; + break; + default: + assert(0); + return 0; + } + bt_packet_logger_error_t serr = bt_packet_logger_start_capture(bt_packet_logger, callback, NULL); + if (serr != BT_PACKET_LOGGER_E_SUCCESS) { + fprintf(stderr, "ERROR: Unable to start capturing bt_packet_logger.\n"); + bt_packet_logger_client_free(bt_packet_logger); + bt_packet_logger = NULL; + idevice_free(device); + device = NULL; + return -1; + } + + fprintf(stderr, "[connected:%s]\n", udid); + fflush(stderr); + + return 0; +} + +/** + * Callback for device events + */ +static void device_event_cb(const idevice_event_t* event, void* userdata) +{ + if (use_network && event->conn_type != CONNECTION_NETWORK) { + return; + } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) { + return; + } + if (event->event == IDEVICE_DEVICE_ADD) { + if (!bt_packet_logger) { + if (!udid) { + udid = strdup(event->udid); + } + if (strcmp(udid, event->udid) == 0) { + if (start_logging() != 0) { + fprintf(stderr, "Could not start logger for udid %s\n", udid); + } + } + } + } else if (event->event == IDEVICE_DEVICE_REMOVE) { + if (bt_packet_logger && (strcmp(udid, event->udid) == 0)) { + stop_logging(); + fprintf(stderr, "[disconnected:%s]\n", udid); + if (exit_on_disconnect) { + quit_flag++; + } + } + } +} + +/** + * signal handler function for cleaning up properly + */ +static void clean_exit(int sig) +{ + fprintf(stderr, "\nExiting...\n"); + quit_flag++; +} + +/** + * print usage information + */ +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = NULL; + name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] <FILE>\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" \ + "Capture HCI packets from a connected device.\n" \ + "\n" \ + "OPTIONS:\n" \ + " -u, --udid UDID target specific device by UDID\n" \ + " -n, --network connect to network device\n" \ + " -f, --format FORMAT logging format: packetlogger (default) or pcap\n" \ + " -x, --exit exit when device disconnects\n" \ + " -h, --help prints usage information\n" \ + " -d, --debug enable communication debugging\n" \ + " -v, --version prints version information\n" \ + "\n" \ + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +/** + * Program entry + */ +int main(int argc, char *argv[]) +{ + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "format", required_argument, NULL, 'f' }, + { "network", no_argument, NULL, 'n' }, + { "exit", no_argument, NULL, 'x' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + + signal(SIGINT, clean_exit); + signal(SIGTERM, clean_exit); +#ifndef WIN32 + signal(SIGQUIT, clean_exit); + signal(SIGPIPE, SIG_IGN); +#endif + + while ((c = getopt_long(argc, argv, "dhu:f:nxv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + free(udid); + udid = strdup(optarg); + break; + case 'f': + if (!*optarg) { + fprintf(stderr, "ERROR: FORMAT must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + free(log_format_string); + log_format_string = strdup(optarg); + break; + case 'n': + use_network = 1; + break; + case 'x': + exit_on_disconnect = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + + if (optind < argc) { + out_filename = argv[optind]; + // printf("Output File: %s\n", out_filename); + } + else { + print_usage(argc, argv, 1); + return 2; + } + + if (log_format_string != NULL){ + if (strcmp("packetlogger", log_format_string) == 0){ + log_format = LOG_FORMAT_PACKETLOGGER; + } else if (strcmp("pcap", log_format_string) == 0){ + log_format = LOG_FORMAT_PCAP; + } else { + printf("Unknown logging format: '%s'\n", log_format_string); + print_usage(argc, argv, 1); + return 2; + } + } + + int num = 0; + idevice_info_t *devices = NULL; + idevice_get_device_list_extended(&devices, &num); + idevice_device_list_extended_free(devices); + if (num == 0) { + if (!udid) { + fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n"); + return -1; + } else { + fprintf(stderr, "Waiting for device with UDID %s to become available...\n", udid); + } + } + + // support streaming to stdout + if (strcmp(out_filename, "-") == 0){ + packetlogger_file = stdout; + } else { + packetlogger_file = fopen(out_filename, "wb"); + } + + + if (packetlogger_file == NULL){ + fprintf(stderr, "Failed to open file %s, errno = %d\n", out_filename, errno); + return -2; + } + + switch (log_format){ + case LOG_FORMAT_PCAP: + // printf("Output Format: PCAP\n"); + // write PCAP file header + (void) fwrite(&pcap_file_header, 1, sizeof(pcap_file_header), packetlogger_file); + break; + case LOG_FORMAT_PACKETLOGGER: + printf("Output Format: PacketLogger\n"); + break; + default: + assert(0); + return -2; + } + idevice_subscription_context_t context = NULL; + idevice_events_subscribe(&context, device_event_cb, NULL); + + while (!quit_flag) { + sleep(1); + } + + idevice_events_unsubscribe(context); + stop_logging(); + + fclose(packetlogger_file); + + free(udid); + + return 0; +} diff --git a/tools/idevicecrashreport.c b/tools/idevicecrashreport.c new file mode 100644 index 0000000..09bd537 --- /dev/null +++ b/tools/idevicecrashreport.c @@ -0,0 +1,529 @@ +/* + * idevicecrashreport.c + * Simple utility to move crash reports from a device to a local directory. + * + * Copyright (c) 2014 Martin Szulecki. All Rights Reserved. + * Copyright (c) 2014 Nikias Bassen. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicecrashreport" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> +#ifndef WIN32 +#include <signal.h> +#endif +#include <libimobiledevice-glue/utils.h> + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/service.h> +#include <libimobiledevice/afc.h> +#include <plist/plist.h> + +#ifdef WIN32 +#include <windows.h> +#define S_IFLNK S_IFREG +#define S_IFSOCK S_IFREG +#endif + +#define CRASH_REPORT_MOVER_SERVICE "com.apple.crashreportmover" +#define CRASH_REPORT_COPY_MOBILE_SERVICE "com.apple.crashreportcopymobile" + +const char* target_directory = NULL; +static int extract_raw_crash_reports = 0; +static int keep_crash_reports = 0; + +static int file_exists(const char* path) +{ + struct stat tst; +#ifdef WIN32 + return (stat(path, &tst) == 0); +#else + return (lstat(path, &tst) == 0); +#endif +} + +static int extract_raw_crash_report(const char* filename) +{ + int res = 0; + plist_t report = NULL; + char* raw = NULL; + char* raw_filename = strdup(filename); + + /* create filename with '.crash' extension */ + char* p = strrchr(raw_filename, '.'); + if ((p == NULL) || (strcmp(p, ".plist") != 0)) { + free(raw_filename); + return res; + } + strcpy(p, ".crash"); + + /* read plist crash report */ + if (plist_read_from_file(filename, &report, NULL)) { + plist_t description_node = plist_dict_get_item(report, "description"); + if (description_node && plist_get_node_type(description_node) == PLIST_STRING) { + plist_get_string_val(description_node, &raw); + + if (raw != NULL) { + /* write file */ + buffer_write_to_filename(raw_filename, raw, strlen(raw)); + free(raw); + res = 1; + } + } + } + + if (report) + plist_free(report); + + if (raw_filename) + free(raw_filename); + + return res; +} + +static int afc_client_copy_and_remove_crash_reports(afc_client_t afc, const char* device_directory, const char* host_directory, const char* filename_filter) +{ + afc_error_t afc_error; + int k; + int res = -1; + int crash_report_count = 0; + uint64_t handle; + char source_filename[512]; + char target_filename[512]; + + if (!afc) + return res; + + char** list = NULL; + afc_error = afc_read_directory(afc, device_directory, &list); + if (afc_error != AFC_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not read device directory '%s'\n", device_directory); + return res; + } + + /* ensure we have a trailing slash */ + strcpy(source_filename, device_directory); + if (source_filename[strlen(source_filename)-1] != '/') { + strcat(source_filename, "/"); + } + int device_directory_length = strlen(source_filename); + + /* ensure we have a trailing slash */ + strcpy(target_filename, host_directory); + if (target_filename[strlen(target_filename)-1] != '/') { + strcat(target_filename, "/"); + } + int host_directory_length = strlen(target_filename); + + /* loop over file entries */ + for (k = 0; list[k]; k++) { + if (!strcmp(list[k], ".") || !strcmp(list[k], "..")) { + continue; + } + + char **fileinfo = NULL; + struct stat stbuf; + memset(&stbuf, '\0', sizeof(struct stat)); + + /* assemble absolute source filename */ + strcpy(((char*)source_filename) + device_directory_length, list[k]); + + /* assemble absolute target filename */ +#ifdef WIN32 + /* replace every ':' with '-' since ':' is an illegal character for file names in windows */ + char* current_pos = strchr(list[k], ':'); + while (current_pos) { + *current_pos = '-'; + current_pos = strchr(current_pos, ':'); + } +#endif + char* p = strrchr(list[k], '.'); + if (p != NULL && !strncmp(p, ".synced", 7)) { + /* make sure to strip ".synced" extension as seen on iOS 5 */ + size_t newlen = p - list[k]; + strncpy(((char*)target_filename) + host_directory_length, list[k], newlen); + target_filename[host_directory_length + newlen] = '\0'; + } else { + strcpy(((char*)target_filename) + host_directory_length, list[k]); + } + + /* get file information */ + afc_get_file_info(afc, source_filename, &fileinfo); + if (!fileinfo) { + printf("Failed to read information for '%s'. Skipping...\n", source_filename); + continue; + } + + /* parse file information */ + int i; + for (i = 0; fileinfo[i]; i+=2) { + if (!strcmp(fileinfo[i], "st_size")) { + stbuf.st_size = atoll(fileinfo[i+1]); + } else if (!strcmp(fileinfo[i], "st_ifmt")) { + if (!strcmp(fileinfo[i+1], "S_IFREG")) { + stbuf.st_mode = S_IFREG; + } else if (!strcmp(fileinfo[i+1], "S_IFDIR")) { + stbuf.st_mode = S_IFDIR; + } else if (!strcmp(fileinfo[i+1], "S_IFLNK")) { + stbuf.st_mode = S_IFLNK; + } else if (!strcmp(fileinfo[i+1], "S_IFBLK")) { + stbuf.st_mode = S_IFBLK; + } else if (!strcmp(fileinfo[i+1], "S_IFCHR")) { + stbuf.st_mode = S_IFCHR; + } else if (!strcmp(fileinfo[i+1], "S_IFIFO")) { + stbuf.st_mode = S_IFIFO; + } else if (!strcmp(fileinfo[i+1], "S_IFSOCK")) { + stbuf.st_mode = S_IFSOCK; + } + } else if (!strcmp(fileinfo[i], "st_nlink")) { + stbuf.st_nlink = atoi(fileinfo[i+1]); + } else if (!strcmp(fileinfo[i], "st_mtime")) { + stbuf.st_mtime = (time_t)(atoll(fileinfo[i+1]) / 1000000000); + } else if (!strcmp(fileinfo[i], "LinkTarget")) { + /* report latest crash report filename */ + printf("Link: %s\n", (char*)target_filename + strlen(target_directory)); + + /* remove any previous symlink */ + if (file_exists(target_filename)) { + remove(target_filename); + } + +#ifndef WIN32 + /* use relative filename */ + char* b = strrchr(fileinfo[i+1], '/'); + if (b == NULL) { + b = fileinfo[i+1]; + } else { + b++; + } + + /* create a symlink pointing to latest log */ + if (symlink(b, target_filename) < 0) { + fprintf(stderr, "Can't create symlink to %s\n", b); + } +#endif + + if (!keep_crash_reports) + afc_remove_path(afc, source_filename); + + res = 0; + } + } + + /* free file information */ + afc_dictionary_free(fileinfo); + + /* recurse into child directories */ + if (S_ISDIR(stbuf.st_mode)) { +#ifdef WIN32 + mkdir(target_filename); +#else + mkdir(target_filename, 0755); +#endif + res = afc_client_copy_and_remove_crash_reports(afc, source_filename, target_filename, filename_filter); + + /* remove directory from device */ + if (!keep_crash_reports) + afc_remove_path(afc, source_filename); + } else if (S_ISREG(stbuf.st_mode)) { + if (filename_filter != NULL && strstr(source_filename, filename_filter) == NULL) { + continue; + } + + /* copy file to host */ + afc_error = afc_file_open(afc, source_filename, AFC_FOPEN_RDONLY, &handle); + if(afc_error != AFC_E_SUCCESS) { + if (afc_error == AFC_E_OBJECT_NOT_FOUND) { + continue; + } + fprintf(stderr, "Unable to open device file '%s' (%d). Skipping...\n", source_filename, afc_error); + continue; + } + + FILE* output = fopen(target_filename, "wb"); + if(output == NULL) { + fprintf(stderr, "Unable to open local file '%s'. Skipping...\n", target_filename); + afc_file_close(afc, handle); + continue; + } + + printf("%s: %s\n", (keep_crash_reports ? "Copy": "Move") , (char*)target_filename + strlen(target_directory)); + + uint32_t bytes_read = 0; + uint32_t bytes_total = 0; + unsigned char data[0x1000]; + + afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read); + while(afc_error == AFC_E_SUCCESS && bytes_read > 0) { + fwrite(data, 1, bytes_read, output); + bytes_total += bytes_read; + afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read); + } + afc_file_close(afc, handle); + fclose(output); + + if ((uint32_t)stbuf.st_size != bytes_total) { + fprintf(stderr, "File size mismatch. Skipping...\n"); + continue; + } + + /* remove file from device */ + if (!keep_crash_reports) { + afc_remove_path(afc, source_filename); + } + + /* extract raw crash information into separate '.crash' file */ + if (extract_raw_crash_reports) { + extract_raw_crash_report(target_filename); + } + + crash_report_count++; + + res = 0; + } + } + afc_dictionary_free(list); + + /* no reports, no error */ + if (crash_report_count == 0) + res = 0; + + return res; +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] DIRECTORY\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Move crash reports from device to a local DIRECTORY.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -e, --extract extract raw crash report into separate '.crash' file\n" + " -k, --keep copy but do not remove crash reports from device\n" + " -d, --debug enable communication debugging\n" + " -f, --filter NAME filter crash reports by NAME (case sensitive)\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +int main(int argc, char* argv[]) +{ + idevice_t device = NULL; + lockdownd_client_t lockdownd = NULL; + afc_client_t afc = NULL; + + idevice_error_t device_error = IDEVICE_E_SUCCESS; + lockdownd_error_t lockdownd_error = LOCKDOWN_E_SUCCESS; + afc_error_t afc_error = AFC_E_SUCCESS; + + const char* udid = NULL; + int use_network = 0; + const char* filename_filter = NULL; + + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { "filter", required_argument, NULL, 'f' }, + { "extract", no_argument, NULL, 'e' }, + { "keep", no_argument, NULL, 'k' }, + { NULL, 0, NULL, 0} + }; + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + /* parse cmdline args */ + while ((c = getopt_long(argc, argv, "dhu:nvf:ek", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + case 'f': + if (!*optarg) { + fprintf(stderr, "ERROR: filter argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + filename_filter = optarg; + break; + case 'e': + extract_raw_crash_reports = 1; + break; + case 'k': + keep_crash_reports = 1; + break; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + /* ensure a target directory was supplied */ + if (!argv[0]) { + fprintf(stderr, "ERROR: missing target directory.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + target_directory = argv[0]; + + /* check if target directory exists */ + if (!file_exists(target_directory)) { + fprintf(stderr, "ERROR: Directory '%s' does not exist.\n", target_directory); + return 1; + } + + device_error = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (device_error != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + return -1; + } + + lockdownd_error = lockdownd_client_new_with_handshake(device, &lockdownd, TOOL_NAME); + if (lockdownd_error != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lockdownd_error); + idevice_free(device); + return -1; + } + + /* start crash log mover service */ + lockdownd_service_descriptor_t service = NULL; + lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_MOVER_SERVICE, &service); + if (lockdownd_error != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_MOVER_SERVICE, lockdownd_strerror(lockdownd_error)); + lockdownd_client_free(lockdownd); + idevice_free(device); + return -1; + } + + /* trigger move operation on device */ + service_client_t svcmove = NULL; + service_error_t service_error = service_client_new(device, service, &svcmove); + lockdownd_service_descriptor_free(service); + service = NULL; + if (service_error != SERVICE_E_SUCCESS) { + lockdownd_client_free(lockdownd); + idevice_free(device); + return -1; + } + + /* read "ping" message which indicates the crash logs have been moved to a safe harbor */ + char *ping = malloc(4); + memset(ping, '\0', 4); + int attempts = 0; + while ((strncmp(ping, "ping", 4) != 0) && (attempts < 10)) { + uint32_t bytes = 0; + service_error = service_receive_with_timeout(svcmove, ping, 4, &bytes, 2000); + if (service_error == SERVICE_E_SUCCESS || service_error == SERVICE_E_TIMEOUT) { + attempts++; + continue; + } + + fprintf(stderr, "ERROR: Crash logs could not be moved. Connection interrupted (%d).\n", service_error); + break; + } + service_client_free(svcmove); + free(ping); + + if (device_error != IDEVICE_E_SUCCESS || attempts > 10) { + fprintf(stderr, "ERROR: Failed to receive ping message from crash report mover.\n"); + lockdownd_client_free(lockdownd); + idevice_free(device); + return -1; + } + + lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_COPY_MOBILE_SERVICE, &service); + if (lockdownd_error != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_COPY_MOBILE_SERVICE, lockdownd_strerror(lockdownd_error)); + lockdownd_client_free(lockdownd); + idevice_free(device); + return -1; + } + lockdownd_client_free(lockdownd); + + afc = NULL; + afc_error = afc_client_new(device, service, &afc); + if(afc_error != AFC_E_SUCCESS) { + lockdownd_client_free(lockdownd); + idevice_free(device); + return -1; + } + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + /* recursively copy crash reports from the device to a local directory */ + if (afc_client_copy_and_remove_crash_reports(afc, ".", target_directory, filename_filter) < 0) { + fprintf(stderr, "ERROR: Failed to get crash reports from device.\n"); + afc_client_free(afc); + idevice_free(device); + return -1; + } + + printf("Done.\n"); + + afc_client_free(afc); + idevice_free(device); + + return 0; +} diff --git a/tools/idevicedate.c b/tools/idevicedate.c index e4e0a33..d05f63e 100644 --- a/tools/idevicedate.c +++ b/tools/idevicedate.c @@ -1,6 +1,6 @@ /* * idevicedate.c - * Simple utility to get and set the clock on an iDevice + * Simple utility to get and set the clock on a device * * Copyright (c) 2011 Martin Szulecki All Rights Reserved. * @@ -8,176 +8,249 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicedate" + #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <getopt.h> #include <time.h> #if HAVE_LANGINFO_CODESET -# include <langinfo.h> +#include <langinfo.h> +#endif +#ifndef WIN32 +#include <signal.h> #endif #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> #ifdef _DATE_FMT -# define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT) +#define DATE_FMT_LANGINFO nl_langinfo (_DATE_FMT) +#else +#ifdef WIN32 +#define DATE_FMT_LANGINFO "%a %b %#d %H:%M:%S %Z %Y" #else -# define DATE_FMT_LANGINFO() "" +#define DATE_FMT_LANGINFO "%a %b %e %H:%M:%S %Z %Y" +#endif #endif -static void print_usage(int argc, char **argv) +static void print_usage(int argc, char **argv, int is_error) { - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); - printf("Display the current date or set it on an iDevice.\n\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -s, --set TIMESTAMP\tset UTC time described by TIMESTAMP\n"); - printf(" -c, --sync\t\tset time of device to current system time\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Display the current date or set it on a device.\n" + "\n" + "NOTE: Setting the time on iOS 6 and later is only supported\n" + " in the setup wizard screens before device activation.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -s, --set TIMESTAMP set UTC time described by TIMESTAMP\n" + " -c, --sync set time of device to current system time\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); } int main(int argc, char *argv[]) { lockdownd_client_t client = NULL; - idevice_t phone = NULL; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; + idevice_t device = NULL; idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; - int i; - char uuid[41]; + const char* udid = NULL; + int use_network = 0; time_t setdate = 0; plist_t node = NULL; - uuid[0] = 0; + int node_type = -1; uint64_t datetime = 0; time_t rawtime; struct tm * tmp; - char const *format = NULL; char buffer[80]; + int result = 0; + + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { "set", required_argument, NULL, 's' }, + { "sync", no_argument, NULL, 'c' }, + { NULL, 0, NULL, 0} + }; +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + while ((c = getopt_long(argc, argv, "dhu:nvs:c", longopts, NULL)) != -1) { + switch (c) { + case 'd': idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { - i++; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; } - strcpy(uuid, argv[i]); - continue; - } - else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--set")) { - i++; - if (!argv[i] || (strlen(argv[i]) <= 1)) { - print_usage(argc, argv); - return 0; + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + case 's': + if (!*optarg) { + fprintf(stderr, "ERROR: set argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; } - setdate = atoi(argv[i]); + setdate = atoi(optarg); if (setdate == 0) { - printf("ERROR: Invalid timestamp value.\n"); - print_usage(argc, argv); + fprintf(stderr, "ERROR: Invalid timestamp value.\n"); + print_usage(argc, argv, 1); return 0; } - continue; - } - else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--sync")) { - i++; + break; + case 'c': /* get current time */ setdate = time(NULL); /* convert it to local time which sets timezone/daylight variables */ tmp = localtime(&setdate); /* recalculate to make it UTC */ setdate = mktime(tmp); - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); - return 0; - } - else { - print_usage(argc, argv); - return 0; + break; + default: + print_usage(argc, argv, 1); + return 2; } } + argc -= optind; + argv += optind; - /* determine a date format */ - if (!format) { - format = DATE_FMT_LANGINFO (); - if (!*format) { - format = "%a %b %e %H:%M:%S %Z %Y"; + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); } + return -1; } - if (uuid[0] != 0) { - ret = idevice_new(&phone, uuid); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found with uuid %s, is it plugged in?\n", uuid); - return -1; - } + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ldret); + result = -1; + goto cleanup; } - else - { - ret = idevice_new(&phone, NULL); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found, is it plugged in?\n"); - return -1; - } + + if(lockdownd_get_value(client, NULL, "TimeIntervalSince1970", &node) != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Unable to retrieve 'TimeIntervalSince1970' node from device.\n"); + result = -1; + goto cleanup; } - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicedate")) { - idevice_free(phone); - return -1; + if (node == NULL) { + fprintf(stderr, "ERROR: Empty node for 'TimeIntervalSince1970' received.\n"); + result = -1; + goto cleanup; } + node_type = plist_get_node_type(node); + /* get or set? */ if (setdate == 0) { /* get time value from device */ - if(lockdownd_get_value(client, NULL, "TimeIntervalSince1970", &node) == LOCKDOWN_E_SUCCESS) { - if (node) { + switch (node_type) { + case PLIST_UINT: plist_get_uint_val(node, &datetime); - plist_free(node); - node = NULL; + break; + case PLIST_REAL: + { + double rv = 0; + plist_get_real_val(node, &rv); + datetime = rv; + } + break; + default: + fprintf(stderr, "ERROR: Unexpected node type for 'TimeIntervalSince1970'\n"); + break; + } + plist_free(node); + node = NULL; - /* date/time calculations */ - rawtime = (time_t)datetime; - tmp = localtime(&rawtime); + /* date/time calculations */ + rawtime = (time_t)datetime; + tmp = localtime(&rawtime); - /* finally we format and print the current date */ - strftime(buffer, 80, format, tmp); - puts(buffer); - } - } + /* finally we format and print the current date */ + strftime(buffer, 80, DATE_FMT_LANGINFO, tmp); + puts(buffer); } else { datetime = setdate; - if(lockdownd_set_value(client, NULL, "TimeIntervalSince1970", plist_new_uint(datetime)) == LOCKDOWN_E_SUCCESS) { + plist_free(node); + node = NULL; + + switch (node_type) { + case PLIST_UINT: + node = plist_new_uint(datetime); + break; + case PLIST_REAL: + node = plist_new_real((double)datetime); + break; + default: + fprintf(stderr, "ERROR: Unexpected node type for 'TimeIntervalSince1970'\n"); + break; + } + + if(lockdownd_set_value(client, NULL, "TimeIntervalSince1970", node) == LOCKDOWN_E_SUCCESS) { tmp = localtime(&setdate); - strftime(buffer, 80, format, tmp); + strftime(buffer, 80, DATE_FMT_LANGINFO, tmp); puts(buffer); } else { printf("ERROR: Failed to set date on device.\n"); } + node = NULL; } - lockdownd_client_free(client); - idevice_free(phone); +cleanup: + if (client) + lockdownd_client_free(client); - return 0; -} + if (device) + idevice_free(device); + return result; +} diff --git a/tools/idevicedebug.c b/tools/idevicedebug.c new file mode 100644 index 0000000..36c594e --- /dev/null +++ b/tools/idevicedebug.c @@ -0,0 +1,625 @@ +/* + * idevicedebug.c + * Interact with the debugserver service of a device. + * + * Copyright (c) 2014-2015 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicedebug" + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <libgen.h> +#include <getopt.h> + +#ifdef WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#endif + +#include <libimobiledevice/installation_proxy.h> +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/debugserver.h> +#include <plist/plist.h> +#include "common/debug.h" + +static int debug_level = 0; + +#define log_debug(...) if (debug_level > 0) { printf(__VA_ARGS__); fputc('\n', stdout); } + +enum cmd_mode { + CMD_NONE = 0, + CMD_RUN, + CMD_KILL +}; + +static int quit_flag = 0; + +static void on_signal(int sig) +{ + fprintf(stderr, "Exiting...\n"); + quit_flag++; +} + +static int cancel_receive() +{ + return quit_flag; +} + +static instproxy_error_t instproxy_client_get_object_by_key_from_info_dictionary_for_bundle_identifier(instproxy_client_t client, const char* appid, const char* key, plist_t* node) +{ + if (!client || !appid || !key) + return INSTPROXY_E_INVALID_ARG; + + plist_t apps = NULL; + + // create client options for any application types + plist_t client_opts = instproxy_client_options_new(); + instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL); + + // only return attributes we need + instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", key, NULL); + + // only query for specific appid + const char* appids[] = {appid, NULL}; + + // query device for list of apps + instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps); + + instproxy_client_options_free(client_opts); + + if (ierr != INSTPROXY_E_SUCCESS) { + return ierr; + } + + plist_t app_found = plist_access_path(apps, 1, appid); + if (!app_found) { + if (apps) + plist_free(apps); + *node = NULL; + return INSTPROXY_E_OP_FAILED; + } + + plist_t object = plist_dict_get_item(app_found, key); + if (object) { + *node = plist_copy(object); + } else { + log_debug("key %s not found", key); + return INSTPROXY_E_OP_FAILED; + } + + plist_free(apps); + + return INSTPROXY_E_SUCCESS; +} + +static debugserver_error_t debugserver_client_handle_response(debugserver_client_t client, char** response, int* exit_status) +{ + debugserver_error_t dres = DEBUGSERVER_E_SUCCESS; + char* o = NULL; + char* r = *response; + + /* Documentation of response codes can be found here: + https://github.com/llvm/llvm-project/blob/4fe839ef3a51e0ea2e72ea2f8e209790489407a2/lldb/docs/lldb-gdb-remote.txt#L1269 + */ + + if (r[0] == 'O') { + /* stdout/stderr */ + debugserver_decode_string(r + 1, strlen(r) - 1, &o); + printf("%s", o); + fflush(stdout); + } else if (r[0] == 'T') { + /* thread stopped information */ + log_debug("Thread stopped. Details:\n%s", r + 1); + if (exit_status != NULL) { + /* "Thread stopped" seems to happen when assert() fails. + Use bash convention where signals cause an exit + status of 128 + signal + */ + *exit_status = 128 + SIGABRT; + } + /* Break out of the loop. */ + dres = DEBUGSERVER_E_UNKNOWN_ERROR; + } else if (r[0] == 'E') { + printf("ERROR: %s\n", r + 1); + } else if (r[0] == 'W' || r[0] == 'X') { + /* process exited */ + debugserver_decode_string(r + 1, strlen(r) - 1, &o); + if (o != NULL) { + printf("Exit %s: %u\n", (r[0] == 'W' ? "status" : "due to signal"), o[0]); + if (exit_status != NULL) { + /* Use bash convention where signals cause an + exit status of 128 + signal + */ + *exit_status = o[0] + (r[0] == 'W' ? 0 : 128); + } + } else { + log_debug("Unable to decode exit status from %s", r); + dres = DEBUGSERVER_E_UNKNOWN_ERROR; + } + } else if (r && strlen(r) == 0) { + log_debug("empty response"); + } else { + log_debug("ERROR: unhandled response '%s'", r); + } + + if (o != NULL) { + free(o); + o = NULL; + } + + free(*response); + *response = NULL; + return dres; +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Interact with the debugserver service of a device.\n" + "\n" + "Where COMMAND is one of:\n" + " run BUNDLEID [ARGS...] run app with BUNDLEID and optional ARGS on device.\n" + " kill BUNDLEID kill app with BUNDLEID\n" + "\n" + "The following OPTIONS are accepted:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " --detach detach from app after launch, keeping it running\n" + " -e, --env NAME=VALUE set environment variable NAME to VALUE\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +int main(int argc, char *argv[]) +{ + int res = -1; + idevice_t device = NULL; + idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + instproxy_client_t instproxy_client = NULL; + debugserver_client_t debugserver_client = NULL; + int i; + int cmd = CMD_NONE; + const char* udid = NULL; + int use_network = 0; + int detach_after_start = 0; + const char* bundle_identifier = NULL; + char* path = NULL; + char* working_directory = NULL; + char **newlist = NULL; + char** environment = NULL; + int environment_index = 0; + int environment_count = 0; + char* response = NULL; + debugserver_command_t command = NULL; + debugserver_error_t dres = DEBUGSERVER_E_UNKNOWN_ERROR; + + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "detach", no_argument, NULL, 1 }, + { "env", required_argument, NULL, 'e' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + /* map signals */ + signal(SIGINT, on_signal); + signal(SIGTERM, on_signal); +#ifndef WIN32 + signal(SIGQUIT, on_signal); + signal(SIGPIPE, SIG_IGN); +#endif + + while ((c = getopt_long(argc, argv, "dhu:ne:v", longopts, NULL)) != -1) { + switch (c) { + case 'd': + debug_level++; + if (debug_level > 1) { + idevice_set_debug_level(debug_level-1); + } + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 1: + detach_after_start = 1; + break; + case 'e': + if (!*optarg || strchr(optarg, '=') == NULL) { + fprintf(stderr, "ERROR: environment variables need to be specified as -e KEY=VALUE\n"); + print_usage(argc, argv, 1); + res = 2; + goto cleanup; + } + /* add environment variable */ + if (!newlist) + newlist = malloc((environment_count + 1) * sizeof(char*)); + else + newlist = realloc(environment, (environment_count + 1) * sizeof(char*)); + newlist[environment_count++] = strdup(optarg); + environment = newlist; + break; + case 'h': + print_usage(argc, argv, 0); + res = 0; + goto cleanup; + break; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + res = 0; + goto cleanup; + break; + default: + print_usage(argc, argv, 1); + res = 2; + goto cleanup; + break; + } + } + argc -= optind; + argv += optind; + + if (argc < 1) { + fprintf(stderr, "ERROR: Missing command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (!strcmp(argv[0], "run")) { + cmd = CMD_RUN; + if (argc < 2) { + /* make sure at least the bundle identifier was provided */ + fprintf(stderr, "ERROR: Please supply the bundle identifier of the app to run.\n"); + print_usage(argc+optind, argv-optind, 1); + res = 2; + goto cleanup; + } + /* read bundle identifier */ + bundle_identifier = argv[1]; + i = 1; + } else if (!strcmp(argv[0], "kill")) { + cmd = CMD_KILL; + if (argc < 2) { + /* make sure at least the bundle identifier was provided */ + fprintf(stderr, "ERROR: Please supply the bundle identifier of the app to run.\n"); + print_usage(argc+optind, argv-optind, 1); + res = 2; + goto cleanup; + } + /* read bundle identifier */ + bundle_identifier = argv[1]; + i = 1; + } + + /* verify options */ + if (cmd == CMD_NONE) { + fprintf(stderr, "ERROR: Unsupported command specified.\n"); + print_usage(argc+optind, argv-optind, 1); + res = 2; + goto cleanup; + } + + if (environment) { + newlist = realloc(environment, (environment_count + 1) * sizeof(char*)); + newlist[environment_count] = NULL; + environment = newlist; + } + + /* connect to the device */ + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + goto cleanup; + } + + /* get the path to the app and it's working directory */ + if (instproxy_client_start_service(device, &instproxy_client, TOOL_NAME) != INSTPROXY_E_SUCCESS) { + fprintf(stderr, "Could not start installation proxy service.\n"); + goto cleanup; + } + + instproxy_client_get_path_for_bundle_identifier(instproxy_client, bundle_identifier, &path); + if (!path) { + fprintf(stderr, "Invalid bundle identifier: %s\n", bundle_identifier); + goto cleanup; + } + + plist_t container = NULL; + instproxy_client_get_object_by_key_from_info_dictionary_for_bundle_identifier(instproxy_client, bundle_identifier, "Container", &container); + instproxy_client_free(instproxy_client); + instproxy_client = NULL; + + if (container && (plist_get_node_type(container) == PLIST_STRING)) { + plist_get_string_val(container, &working_directory); + log_debug("working_directory: %s\n", working_directory); + plist_free(container); + } else { + plist_free(container); + fprintf(stderr, "Could not determine container path for bundle identifier %s.\n", bundle_identifier); + goto cleanup; + } + + /* start and connect to debugserver */ + if (debugserver_client_start_service(device, &debugserver_client, TOOL_NAME) != DEBUGSERVER_E_SUCCESS) { + fprintf(stderr, + "Could not start com.apple.debugserver!\n" + "Please make sure to mount the developer disk image first:\n" + " 1) Get the iOS version from `ideviceinfo -k ProductVersion`.\n" + " 2) Find the matching iPhoneOS DeveloperDiskImage.dmg files.\n" + " 3) Run `ideviceimagemounter` with the above path.\n"); + goto cleanup; + } + + /* set receive params */ + if (debugserver_client_set_receive_params(debugserver_client, cancel_receive, 250) != DEBUGSERVER_E_SUCCESS) { + fprintf(stderr, "Error in debugserver_client_set_receive_params\n"); + goto cleanup; + } + + /* enable logging for the session in debug mode */ + if (debug_level) { + log_debug("Setting logging bitmask..."); + debugserver_command_new("QSetLogging:bitmask=LOG_ALL|LOG_RNB_REMOTE|LOG_RNB_PACKETS;", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + if (response) { + if (strncmp(response, "OK", 2) != 0) { + debugserver_client_handle_response(debugserver_client, &response, NULL); + goto cleanup; + } + free(response); + response = NULL; + } + } + + /* set maximum packet size */ + log_debug("Setting maximum packet size..."); + char* packet_size[2] = { (char*)"1024", NULL}; + debugserver_command_new("QSetMaxPacketSize:", 1, packet_size, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + if (response) { + if (strncmp(response, "OK", 2) != 0) { + debugserver_client_handle_response(debugserver_client, &response, NULL); + goto cleanup; + } + free(response); + response = NULL; + } + + /* set working directory */ + log_debug("Setting working directory..."); + char* working_dir[2] = {working_directory, NULL}; + debugserver_command_new("QSetWorkingDir:", 1, working_dir, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + if (response) { + if (strncmp(response, "OK", 2) != 0) { + debugserver_client_handle_response(debugserver_client, &response, NULL); + goto cleanup; + } + free(response); + response = NULL; + } + + /* set environment */ + if (environment) { + log_debug("Setting environment..."); + for (environment_index = 0; environment_index < environment_count; environment_index++) { + log_debug("setting environment variable: %s", environment[environment_index]); + debugserver_client_set_environment_hex_encoded(debugserver_client, environment[environment_index], NULL); + } + } + + /* set arguments and run app */ + log_debug("Setting argv..."); + i++; /* i is the offset of the bundle identifier, thus skip it */ + int app_argc = (argc - i + 2); + char **app_argv = (char**)malloc(sizeof(char*) * app_argc); + app_argv[0] = path; + log_debug("app_argv[%d] = %s", 0, app_argv[0]); + app_argc = 1; + while (i < argc && argv && argv[i]) { + log_debug("app_argv[%d] = %s", app_argc, argv[i]); + app_argv[app_argc++] = argv[i]; + i++; + } + app_argv[app_argc] = NULL; + debugserver_client_set_argv(debugserver_client, app_argc, app_argv, NULL); + free(app_argv); + + /* check if launch succeeded */ + log_debug("Checking if launch succeeded..."); + debugserver_command_new("qLaunchSuccess", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + if (response) { + if (strncmp(response, "OK", 2) != 0) { + debugserver_client_handle_response(debugserver_client, &response, NULL); + goto cleanup; + } + free(response); + response = NULL; + } + + if (cmd == CMD_KILL) { + debugserver_command_new("k", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + goto cleanup; + } else + if (cmd == CMD_RUN) { + if (detach_after_start) { + log_debug("Detaching from app"); + debugserver_command_new("D", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + + res = (dres == DEBUGSERVER_E_SUCCESS) ? 0: -1; + goto cleanup; + } + + /* set thread */ + log_debug("Setting thread..."); + debugserver_command_new("Hc0", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + if (response) { + if (strncmp(response, "OK", 2) != 0) { + debugserver_client_handle_response(debugserver_client, &response, NULL); + goto cleanup; + } + free(response); + response = NULL; + } + + /* continue running process */ + log_debug("Continue running process..."); + debugserver_command_new("c", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + log_debug("Continue response: %s", response); + + /* main loop which is parsing/handling packets during the run */ + log_debug("Entering run loop..."); + while (!quit_flag) { + if (dres != DEBUGSERVER_E_SUCCESS) { + log_debug("failed to receive response; error %d", dres); + break; + } + + if (response) { + log_debug("response: %s", response); + if (strncmp(response, "OK", 2) != 0) { + dres = debugserver_client_handle_response(debugserver_client, &response, &res); + if (dres != DEBUGSERVER_E_SUCCESS) { + log_debug("failed to process response; error %d; %s", dres, response); + break; + } + } + } + if (res >= 0) { + goto cleanup; + } + + dres = debugserver_client_receive_response(debugserver_client, &response, NULL); + } + + /* ignore quit_flag after this point */ + if (debugserver_client_set_receive_params(debugserver_client, NULL, 5000) != DEBUGSERVER_E_SUCCESS) { + fprintf(stderr, "Error in debugserver_client_set_receive_params\n"); + goto cleanup; + } + + /* interrupt execution */ + debugserver_command_new("\x03", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + if (response) { + if (strncmp(response, "OK", 2) != 0) { + debugserver_client_handle_response(debugserver_client, &response, NULL); + } + free(response); + response = NULL; + } + + /* kill process after we finished */ + log_debug("Killing process..."); + debugserver_command_new("k", 0, NULL, &command); + dres = debugserver_client_send_command(debugserver_client, command, &response, NULL); + debugserver_command_free(command); + command = NULL; + if (response) { + if (strncmp(response, "OK", 2) != 0) { + debugserver_client_handle_response(debugserver_client, &response, NULL); + } + free(response); + response = NULL; + } + + if (res < 0) { + res = (dres == DEBUGSERVER_E_SUCCESS) ? 0: -1; + } + } + +cleanup: + /* cleanup the house */ + if (environment) { + for (environment_index = 0; environment_index < environment_count; environment_index++) { + free(environment[environment_index]); + } + free(environment); + } + + if (working_directory) + free(working_directory); + + if (path) + free(path); + + if (response) + free(response); + + if (debugserver_client) + debugserver_client_free(debugserver_client); + + if (device) + idevice_free(device); + + return res; +} diff --git a/tools/idevicedebugserverproxy.c b/tools/idevicedebugserverproxy.c new file mode 100644 index 0000000..9fe7051 --- /dev/null +++ b/tools/idevicedebugserverproxy.c @@ -0,0 +1,375 @@ +/* + * idevicedebugserverproxy.c + * Proxy a debugserver connection from device for remote debugging + * + * Copyright (c) 2021 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2012 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicedebugserverproxy" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <errno.h> +#include <signal.h> +#ifdef WIN32 +#include <winsock2.h> +#include <windows.h> +#else +#include <sys/select.h> +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/debugserver.h> + +#include <libimobiledevice-glue/socket.h> +#include <libimobiledevice-glue/thread.h> + +#ifndef ETIMEDOUT +#define ETIMEDOUT 138 +#endif + +#define info(...) fprintf(stdout, __VA_ARGS__); fflush(stdout) +#define debug(...) if(debug_mode) fprintf(stdout, __VA_ARGS__) + +static int support_lldb = 0; +static int debug_mode = 0; +static int quit_flag = 0; +static uint16_t local_port = 0; + +typedef struct { + int client_fd; + idevice_t device; + debugserver_client_t debugserver_client; +} socket_info_t; + +struct thread_info { + THREAD_T th; + int client_fd; + struct thread_info *next; +}; + +typedef struct thread_info thread_info_t; + + +static void clean_exit(int sig) +{ + fprintf(stderr, "Exiting...\n"); + quit_flag++; +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [PORT]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Proxy debugserver connection from device to a local socket at PORT.\n" + "If PORT is omitted, the next available port will be used and printed\n" + "to stdout.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -l, --lldb enable lldb support\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +static int intercept_packet(char *packet, ssize_t *packet_len) { + static const char kReqLaunchServer[] = "$qLaunchGDBServer;#4b"; + + char buffer[64] = {0}; + if (*packet_len == (ssize_t)(sizeof(kReqLaunchServer) - 1) + && memcmp(packet, kReqLaunchServer, sizeof(kReqLaunchServer) - 1) == 0) { + sprintf(buffer, "port:%d;", local_port); + } else { + return 0; + } + int sum = 0; + for (size_t i = 0; i < strlen(buffer); i++) { + sum += buffer[i]; + } + sum = sum & 255; + sprintf(packet, "$%s#%02x", buffer, sum); + *packet_len = strlen(packet); + return 1; +} + +static void* connection_handler(void* data) +{ + debugserver_error_t derr = DEBUGSERVER_E_SUCCESS; + socket_info_t* socket_info = (socket_info_t*)data; + const int bufsize = 65536; + char* buf; + + int client_fd = socket_info->client_fd; + + debug("%s: client_fd = %d\n", __func__, client_fd); + + derr = debugserver_client_start_service(socket_info->device, &socket_info->debugserver_client, TOOL_NAME); + if (derr != DEBUGSERVER_E_SUCCESS) { + fprintf(stderr, "Could not start debugserver on device!\nPlease make sure to mount a developer disk image first.\n"); + return NULL; + } + + buf = malloc(bufsize); + if (!buf) { + fprintf(stderr, "Failed to allocate buffer\n"); + return NULL; + } + + fd_set fds; + FD_ZERO(&fds); + FD_SET(client_fd, &fds); + + int dtimeout = 1; + + while (!quit_flag) { + ssize_t n = socket_receive_timeout(client_fd, buf, bufsize, 0, 1); + if (n != -ETIMEDOUT) { + if (n < 0) { + fprintf(stderr, "Failed to read from client fd: %s\n", strerror(-n)); + break; + } else if (n == 0) { + fprintf(stderr, "connection closed\n"); + break; + } + if (support_lldb && intercept_packet(buf, &n)) { + socket_send(client_fd, buf, n); + continue; + } + uint32_t sent = 0; + debugserver_client_send(socket_info->debugserver_client, buf, n, &sent); + } + do { + uint32_t r = 0; + derr = debugserver_client_receive_with_timeout(socket_info->debugserver_client, buf, bufsize, &r, dtimeout); + if (r > 0) { + socket_send(client_fd, buf, r); + dtimeout = 1; + } else if (derr == DEBUGSERVER_E_TIMEOUT) { + dtimeout = 5; + break; + } else { + fprintf(stderr, "debugserver connection closed\n"); + break; + } + } while (derr == DEBUGSERVER_E_SUCCESS); + if (derr != DEBUGSERVER_E_TIMEOUT && derr != DEBUGSERVER_E_SUCCESS) { + break; + } + } + free(buf); + + debug("%s: shutting down...\n", __func__); + + debugserver_client_free(socket_info->debugserver_client); + socket_info->debugserver_client = NULL; + + /* shutdown client socket */ + socket_shutdown(socket_info->client_fd, SHUT_RDWR); + socket_close(socket_info->client_fd); + + return NULL; +} + +int main(int argc, char *argv[]) +{ + idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + idevice_t device = NULL; + thread_info_t *thread_list = NULL; + const char* udid = NULL; + int use_network = 0; + int server_fd; + int result = EXIT_SUCCESS; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "lldb", no_argument, NULL, 'l' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + +#ifndef WIN32 + struct sigaction sa; + struct sigaction si; + memset(&sa, '\0', sizeof(struct sigaction)); + memset(&si, '\0', sizeof(struct sigaction)); + + sa.sa_handler = clean_exit; + sigemptyset(&sa.sa_mask); + + si.sa_handler = SIG_IGN; + sigemptyset(&si.sa_mask); + + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGPIPE, &si, NULL); +#else + /* bind signals */ + signal(SIGINT, clean_exit); + signal(SIGTERM, clean_exit); +#endif + + /* parse cmdline arguments */ + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + debug_mode = 1; + idevice_set_debug_level(1); + socket_set_verbose(3); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'l': + support_lldb = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + if (argv[0] && (atoi(argv[0]) > 0)) { + local_port = atoi(argv[0]); + } + + /* start services and connect to device */ + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + fprintf(stderr, "No device found with udid %s.\n", udid); + } else { + fprintf(stderr, "No device found.\n"); + } + result = EXIT_FAILURE; + goto leave_cleanup; + } + + /* create local socket */ + server_fd = socket_create("127.0.0.1", local_port); + if (server_fd < 0) { + fprintf(stderr, "Could not create socket\n"); + result = EXIT_FAILURE; + goto leave_cleanup; + } + + if (local_port == 0) { + /* The user asked for any available port. Report the actual port. */ + uint16_t port; + if (0 > socket_get_socket_port(server_fd, &port)) { + fprintf(stderr, "Could not determine socket port\n"); + result = EXIT_FAILURE; + goto leave_cleanup; + } + printf("Listening on port %d\n", port); + } + + while (!quit_flag) { + debug("%s: Waiting for connection on local port %d\n", __func__, local_port); + + /* wait for client */ + int client_fd = socket_accept(server_fd, local_port); + if (client_fd < 0) { + continue; + } + + debug("%s: Handling new client connection...\n", __func__); + + thread_info_t *el = (thread_info_t*)malloc(sizeof(thread_info_t)); + if (!el) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + el->client_fd = client_fd; + el->next = NULL; + + if (thread_list) { + thread_list->next = el; + } else { + thread_list = el; + } + + socket_info_t *sinfo = (socket_info_t*)malloc(sizeof(socket_info_t)); + if (!sinfo) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + sinfo->client_fd = client_fd; + sinfo->device = device; + + if (thread_new(&(el->th), connection_handler, (void*)sinfo) != 0) { + fprintf(stderr, "Could not start connection handler.\n"); + socket_shutdown(server_fd, SHUT_RDWR); + socket_close(server_fd); + break; + } + } + + debug("%s: Shutting down debugserver proxy...\n", __func__); + + /* join and clean up threads */ + while (thread_list) { + thread_info_t *el = thread_list; + socket_shutdown(el->client_fd, SHUT_RDWR); + socket_close(el->client_fd); + thread_join(el->th); + thread_free(el->th); + thread_list = el->next; + free(el); + } + +leave_cleanup: + if (device) { + idevice_free(device); + } + + return result; +} diff --git a/tools/idevicedevmodectl.c b/tools/idevicedevmodectl.c new file mode 100644 index 0000000..bd1de6a --- /dev/null +++ b/tools/idevicedevmodectl.c @@ -0,0 +1,462 @@ +/* + * idevicedevmodectl.c + * List or enable Developer Mode on iOS 16+ devices + * + * Copyright (c) 2022 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicedevmodectl" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#ifndef WIN32 +#include <signal.h> +#endif + +#ifdef WIN32 +#include <windows.h> +#define __usleep(x) Sleep(x/1000) +#else +#include <arpa/inet.h> +#include <unistd.h> +#define __usleep(x) usleep(x) +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/property_list_service.h> +#include <libimobiledevice-glue/utils.h> + +#define AMFI_LOCKDOWN_SERVICE_NAME "com.apple.amfi.lockdown" + +static char* udid = NULL; +static int use_network = 0; + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Enable Developer Mode on iOS 16+ devices or print the current status.\n" + "\n" + "Where COMMAND is one of:\n" + " list Print the Developer Mode status of all connected devices\n" + " or for a specific one if --udid is given.\n" + " enable Enable Developer Mode (device will reboot),\n" + " and confirm it after device booted up again.\n" + "\n" + " arm Arm the Developer Mode (device will reboot)\n" + " confirm Confirm enabling of Developer Mode\n" + " reveal Reveal the Developer Mode menu on the device\n" + "\n" + "The following OPTIONS are accepted:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help print usage information\n" + " -v, --version print version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +enum { + OP_LIST, + OP_ENABLE, + OP_ARM, + OP_CONFIRM, + OP_REVEAL, + NUM_OPS +}; +#define DEV_MODE_REVEAL 0 +#define DEV_MODE_ARM 1 +#define DEV_MODE_ENABLE 2 + +static int get_developer_mode_status(const char* device_udid, int _use_network) +{ + idevice_error_t ret; + idevice_t device = NULL; + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR; + plist_t val = NULL; + + ret = idevice_new_with_options(&device, device_udid, (_use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + return -1; + } + + if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + idevice_free(device); + return -1; + } + + lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr)); + lockdownd_client_free(lockdown); + idevice_free(device); + return -2; + } + + uint8_t dev_mode_status = 0; + plist_get_bool_val(val, &dev_mode_status); + plist_free(val); + + lockdownd_client_free(lockdown); + idevice_free(device); + + return dev_mode_status; +} + +static int amfi_service_send_msg(property_list_service_client_t amfi, plist_t msg) +{ + int res; + property_list_service_error_t perr; + + perr = property_list_service_send_xml_plist(amfi, plist_copy(msg)); + if (perr != PROPERTY_LIST_SERVICE_E_SUCCESS) { + fprintf(stderr, "Could not send request to device: %d\n", perr); + res = 2; + } else { + plist_t reply = NULL; + perr = property_list_service_receive_plist(amfi, &reply); + if (perr == PROPERTY_LIST_SERVICE_E_SUCCESS) { + plist_t val = plist_dict_get_item(reply, "Error"); + if (val) { + const char* err = plist_get_string_ptr(val, NULL); + fprintf(stderr, "Request failed: %s\n", err); + if (strstr(err, "passcode")) { + res = 2; + } else { + res = 1; + } + } else { + res = plist_dict_get_item(reply, "success") ? 0 : 1; + } + } else { + fprintf(stderr, "Could not receive reply from device: %d\n", perr); + res = 2; + } + plist_free(reply); + } + return res; +} + +static int amfi_send_action(idevice_t device, unsigned int action) +{ + lockdownd_client_t lockdown = NULL; + lockdownd_service_descriptor_t service = NULL; + lockdownd_error_t lerr; + + if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr); + return 1; + } + + lerr = lockdownd_start_service(lockdown, AMFI_LOCKDOWN_SERVICE_NAME, &service); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "Could not start service %s: %s\nPlease note that this feature is only available on iOS 16+.\n", AMFI_LOCKDOWN_SERVICE_NAME, lockdownd_strerror(lerr)); + lockdownd_client_free(lockdown); + return 1; + } + lockdownd_client_free(lockdown); + lockdown = NULL; + + property_list_service_client_t amfi = NULL; + if (property_list_service_client_new(device, service, &amfi) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + fprintf(stderr, "Could not connect to %s on device\n", AMFI_LOCKDOWN_SERVICE_NAME); + if (service) + lockdownd_service_descriptor_free(service); + idevice_free(device); + return 1; + } + lockdownd_service_descriptor_free(service); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "action", plist_new_uint(action)); + + int result = amfi_service_send_msg(amfi, dict); + plist_free(dict); + + property_list_service_client_free(amfi); + amfi = NULL; + + return result; +} + +static int device_connected = 0; + +static void device_event_cb(const idevice_event_t* event, void* userdata) +{ + if (use_network && event->conn_type != CONNECTION_NETWORK) { + return; + } + if (!use_network && event->conn_type != CONNECTION_USBMUXD) { + return; + } + if (event->event == IDEVICE_DEVICE_ADD) { + if (!udid) { + udid = strdup(event->udid); + } + if (strcmp(udid, event->udid) == 0) { + device_connected = 1; + } + } else if (event->event == IDEVICE_DEVICE_REMOVE) { + if (strcmp(udid, event->udid) == 0) { + device_connected = 0; + } + } +} + + +#define WAIT_INTERVAL 200000 +#define WAIT_MAX(x) (x * (1000000 / WAIT_INTERVAL)) +#define WAIT_FOR(cond, timeout) { int __repeat = WAIT_MAX(timeout); while (!(cond) && __repeat-- > 0) { __usleep(WAIT_INTERVAL); } } + +int main(int argc, char *argv[]) +{ + idevice_t device = NULL; + idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR; + int res = 0; + int i; + int op = -1; + plist_t val = NULL; + + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + /* parse cmdline args */ + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + if (!argv[0]) { + fprintf(stderr, "ERROR: Missing command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + i = 0; + if (!strcmp(argv[i], "list")) { + op = OP_LIST; + } + else if (!strcmp(argv[i], "enable")) { + op = OP_ENABLE; + } + else if (!strcmp(argv[i], "arm")) { + op = OP_ARM; + } + else if (!strcmp(argv[i], "confirm")) { + op = OP_CONFIRM; + } + else if (!strcmp(argv[i], "reveal")) { + op = OP_REVEAL; + } + + if ((op == -1) || (op >= NUM_OPS)) { + fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[i]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (op == OP_LIST) { + idevice_info_t *dev_list = NULL; + + if (idevice_get_device_list_extended(&dev_list, &i) < 0) { + fprintf(stderr, "ERROR: Unable to retrieve device list!\n"); + return -1; + } + if (i > 0) { + printf("%-40s %s\n", "Device", "DeveloperMode"); + } + for (i = 0; dev_list[i] != NULL; i++) { + if (dev_list[i]->conn_type == CONNECTION_USBMUXD && use_network) continue; + if (dev_list[i]->conn_type == CONNECTION_NETWORK && !use_network) continue; + if (udid && (strcmp(dev_list[i]->udid, udid) != 0)) continue; + int mode = get_developer_mode_status(dev_list[i]->udid, use_network); + const char *mode_str = "N/A"; + if (mode == 1) { + mode_str = "enabled"; + } else if (mode == 0) { + mode_str = "disabled"; + } + printf("%-40s %s\n", dev_list[i]->udid, mode_str); + } + idevice_device_list_extended_free(dev_list); + + return 0; + } + + idevice_subscription_context_t context = NULL; + idevice_events_subscribe(&context, device_event_cb, NULL); + + WAIT_FOR(device_connected, 10); + + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + return 1; + } + + if (!udid) { + idevice_get_udid(device, &udid); + } + + if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr); + idevice_free(device); + return 1; + } + + lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val); + lockdownd_client_free(lockdown); + lockdown = NULL; + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr)); + idevice_free(device); + return 1; + } + + uint8_t dev_mode_status = 0; + plist_get_bool_val(val, &dev_mode_status); + + if ((op == OP_ENABLE || op == OP_ARM) && dev_mode_status) { + if (dev_mode_status) { + idevice_free(device); + printf("DeveloperMode is already enabled.\n"); + return 0; + } + res = 0; + } else { + if (op == OP_ENABLE || op == OP_ARM) { + res = amfi_send_action(device, DEV_MODE_ARM); + if (res == 0) { + if (op == OP_ARM) { + printf("%s: Developer Mode armed, device will reboot now.\n", udid); + } else { + printf("%s: Developer Mode armed, waiting for reboot...\n", udid); + + do { + // waiting for device to disconnect... + idevice_free(device); + device = NULL; + WAIT_FOR(!device_connected, 40); + if (device_connected) { + printf("%s: ERROR: Device didn't reboot?!\n", udid); + res = 2; + break; + } + printf("disconnected\n"); + + // waiting for device to reconnect... + WAIT_FOR(device_connected, 60); + if (!device_connected) { + printf("%s: ERROR: Device didn't re-connect?!\n", udid); + res = 2; + break; + } + printf("connected\n"); + + idevice_new(&device, udid); + res = amfi_send_action(device, DEV_MODE_ENABLE); + } while (0); + if (res == 0) { + printf("%s: Developer Mode successfully enabled.\n", udid); + } else { + printf("%s: Failed to enable developer mode (%d)\n", udid, res); + } + } + } else if (res == 2) { + amfi_send_action(device, DEV_MODE_REVEAL); + printf("%s: Developer Mode could not be enabled because the device has a passcode set. You have to enable it on the device itself under Settings -> Privacy & Security -> Developer Mode.\n", udid); + } else { + printf("%s: Failed to arm Developer Mode (%d)\n", udid, res); + } + } else if (op == OP_CONFIRM) { + res = amfi_send_action(device, DEV_MODE_ENABLE); + if (res == 0) { + printf("%s: Developer Mode successfully enabled.\n", udid); + } else { + printf("%s: Failed to enable Developer Mode (%d)\n", udid, res); + } + } else if (op == OP_REVEAL) { + res = amfi_send_action(device, DEV_MODE_REVEAL); + if (res == 0) { + printf("%s: Developer Mode menu revealed successfully.\n", udid); + } else { + printf("%s: Failed to reveal Developer Mode menu (%d)\n", udid, res); + } + } + } + + idevice_free(device); + + return res; +} diff --git a/tools/idevicediagnostics.c b/tools/idevicediagnostics.c new file mode 100644 index 0000000..e699bc4 --- /dev/null +++ b/tools/idevicediagnostics.c @@ -0,0 +1,344 @@ +/* + * idevicediagnostics.c + * Retrieves diagnostics information from device + * + * Copyright (c) 2012 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicediagnostics" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <errno.h> +#include <time.h> +#ifndef WIN32 +#include <signal.h> +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/diagnostics_relay.h> + +enum cmd_mode { + CMD_NONE = 0, + CMD_SLEEP, + CMD_RESTART, + CMD_SHUTDOWN, + CMD_DIAGNOSTICS, + CMD_MOBILEGESTALT, + CMD_IOREGISTRY, + CMD_IOREGISTRY_ENTRY +}; + +static void print_xml(plist_t node) +{ + char *xml = NULL; + uint32_t len = 0; + plist_to_xml(node, &xml, &len); + if (xml) { + puts(xml); + } +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Use diagnostics interface of a device running iOS 4 or later.\n" + "\n" + "Where COMMAND is one of:\n" + " diagnostics [TYPE] print diagnostics information from device by TYPE (All, WiFi, GasGauge, NAND)\n" + " mobilegestalt KEY [...] print mobilegestalt keys passed as arguments separated by a space.\n" + " ioreg [PLANE] print IORegistry of device, optionally by PLANE (IODeviceTree, IOPower, IOService) (iOS 5+ only)\n" + " ioregentry [KEY] print IORegistry entry of device (AppleARMPMUCharger, ASPStorage, ...) (iOS 5+ only)\n" + " shutdown shutdown device\n" + " restart restart device\n" + " sleep put device into sleep mode (disconnects from host)\n" + "\n" + "The following OPTIONS are accepted:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +int main(int argc, char **argv) +{ + idevice_t device = NULL; + lockdownd_client_t lockdown_client = NULL; + diagnostics_relay_client_t diagnostics_client = NULL; + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + lockdownd_service_descriptor_t service = NULL; + int result = EXIT_FAILURE; + const char *udid = NULL; + int use_network = 0; + int cmd = CMD_NONE; + char* cmd_arg = NULL; + plist_t node = NULL; + plist_t keys = NULL; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + /* parse cmdline args */ + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + if (!argv[0]) { + fprintf(stderr, "ERROR: No command specified\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (!strcmp(argv[0], "sleep")) { + cmd = CMD_SLEEP; + } + else if (!strcmp(argv[0], "restart")) { + cmd = CMD_RESTART; + } + else if (!strcmp(argv[0], "shutdown")) { + cmd = CMD_SHUTDOWN; + } + else if (!strcmp(argv[0], "diagnostics")) { + cmd = CMD_DIAGNOSTICS; + /* read type */ + if (!argv[1] || ((strcmp(argv[1], "All") != 0) && (strcmp(argv[1], "WiFi") != 0) && (strcmp(argv[1], "GasGauge") != 0) && (strcmp(argv[1], "NAND") != 0) && (strcmp(argv[1], "HDMI") != 0))) { + if (argv[1] == NULL) { + cmd_arg = strdup("All"); + } else { + fprintf(stderr, "ERROR: Unknown TYPE %s\n", argv[1]); + print_usage(argc+optind, argv-optind, 1); + goto cleanup; + } + } + cmd_arg = strdup(argv[1]); + } + else if (!strcmp(argv[0], "mobilegestalt")) { + cmd = CMD_MOBILEGESTALT; + /* read keys */ + if (!argv[1] || !*argv[1]) { + fprintf(stderr, "ERROR: Please supply the key to query.\n"); + print_usage(argc, argv, 1); + goto cleanup; + } + int i = 1; + keys = plist_new_array(); + while (argv[i] && *argv[i]) { + plist_array_append_item(keys, plist_new_string(argv[i])); + i++; + } + } + else if (!strcmp(argv[0], "ioreg")) { + cmd = CMD_IOREGISTRY; + /* read plane */ + if (argv[1]) { + cmd_arg = strdup(argv[1]); + } + } + else if (!strcmp(argv[0], "ioregentry")) { + cmd = CMD_IOREGISTRY_ENTRY; + /* read key */ + if (argv[1]) { + cmd_arg = strdup(argv[1]); + } + } + + /* verify options */ + if (cmd == CMD_NONE) { + fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[0]); + print_usage(argc+optind, argv-optind, 1); + goto cleanup; + } + + if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + goto cleanup; + } + + if (LOCKDOWN_E_SUCCESS != (ret = lockdownd_client_new_with_handshake(device, &lockdown_client, TOOL_NAME))) { + idevice_free(device); + printf("ERROR: Could not connect to lockdownd, error code %d\n", ret); + goto cleanup; + } + + /* attempt to use newer diagnostics service available on iOS 5 and later */ + ret = lockdownd_start_service(lockdown_client, "com.apple.mobile.diagnostics_relay", &service); + if (ret == LOCKDOWN_E_INVALID_SERVICE) { + /* attempt to use older diagnostics service */ + ret = lockdownd_start_service(lockdown_client, "com.apple.iosdiagnostics.relay", &service); + } + lockdownd_client_free(lockdown_client); + + if (ret != LOCKDOWN_E_SUCCESS) { + idevice_free(device); + printf("ERROR: Could not start diagnostics relay service: %s\n", lockdownd_strerror(ret)); + goto cleanup; + } + + result = EXIT_FAILURE; + + if ((ret == LOCKDOWN_E_SUCCESS) && service && (service->port > 0)) { + if (diagnostics_relay_client_new(device, service, &diagnostics_client) != DIAGNOSTICS_RELAY_E_SUCCESS) { + printf("ERROR: Could not connect to diagnostics_relay!\n"); + } else { + switch (cmd) { + case CMD_SLEEP: + if (diagnostics_relay_sleep(diagnostics_client) == DIAGNOSTICS_RELAY_E_SUCCESS) { + printf("Putting device into deep sleep mode.\n"); + result = EXIT_SUCCESS; + } else { + printf("ERROR: Failed to put device into deep sleep mode.\n"); + } + break; + case CMD_RESTART: + if (diagnostics_relay_restart(diagnostics_client, DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) == DIAGNOSTICS_RELAY_E_SUCCESS) { + printf("Restarting device.\n"); + result = EXIT_SUCCESS; + } else { + printf("ERROR: Failed to restart device.\n"); + } + break; + case CMD_SHUTDOWN: + if (diagnostics_relay_shutdown(diagnostics_client, DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) == DIAGNOSTICS_RELAY_E_SUCCESS) { + printf("Shutting down device.\n"); + result = EXIT_SUCCESS; + } else { + printf("ERROR: Failed to shutdown device.\n"); + } + break; + case CMD_MOBILEGESTALT: + if (diagnostics_relay_query_mobilegestalt(diagnostics_client, keys, &node) == DIAGNOSTICS_RELAY_E_SUCCESS) { + if (node) { + print_xml(node); + result = EXIT_SUCCESS; + } + } else { + printf("ERROR: Unable to query mobilegestalt keys.\n"); + } + break; + case CMD_IOREGISTRY_ENTRY: + if (diagnostics_relay_query_ioregistry_entry(diagnostics_client, cmd_arg == NULL ? "": cmd_arg, "", &node) == DIAGNOSTICS_RELAY_E_SUCCESS) { + if (node) { + print_xml(node); + result = EXIT_SUCCESS; + } + } else { + printf("ERROR: Unable to retrieve IORegistry from device.\n"); + } + break; + case CMD_IOREGISTRY: + if (diagnostics_relay_query_ioregistry_plane(diagnostics_client, cmd_arg == NULL ? "": cmd_arg, &node) == DIAGNOSTICS_RELAY_E_SUCCESS) { + if (node) { + print_xml(node); + result = EXIT_SUCCESS; + } + } else { + printf("ERROR: Unable to retrieve IORegistry from device.\n"); + } + break; + case CMD_DIAGNOSTICS: + default: + if (diagnostics_relay_request_diagnostics(diagnostics_client, cmd_arg, &node) == DIAGNOSTICS_RELAY_E_SUCCESS) { + if (node) { + print_xml(node); + result = EXIT_SUCCESS; + } + } else { + printf("ERROR: Unable to retrieve diagnostics from device.\n"); + } + break; + } + + diagnostics_relay_goodbye(diagnostics_client); + diagnostics_relay_client_free(diagnostics_client); + } + } else { + printf("ERROR: Could not start diagnostics service!\n"); + } + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + idevice_free(device); + +cleanup: + if (node) { + plist_free(node); + } + if (keys) { + plist_free(keys); + } + if (cmd_arg) { + free(cmd_arg); + } + return result; +} diff --git a/tools/ideviceenterrecovery.c b/tools/ideviceenterrecovery.c index 827946b..29cc5c9 100644 --- a/tools/ideviceenterrecovery.c +++ b/tools/ideviceenterrecovery.c @@ -8,86 +8,132 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "ideviceenterrecovery" + #include <stdio.h> #include <string.h> -#include <errno.h> #include <stdlib.h> +#include <getopt.h> +#include <errno.h> +#ifndef WIN32 +#include <signal.h> +#endif #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> -static void print_usage(int argc, char **argv) +static void print_usage(int argc, char **argv, int is_error) { - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS] UUID\n", (name ? name + 1: argv[0])); - printf("Makes a device with the supplied 40-digit UUID enter recovery mode immediately.\n\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] UDID\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Makes a device with the supplied UDID enter recovery mode immediately.\n" + "\n" + "OPTIONS:\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); } int main(int argc, char *argv[]) { lockdownd_client_t client = NULL; - idevice_t phone = NULL; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; + idevice_t device = NULL; idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; - int i; - char uuid[41]; - uuid[0] = 0; + const char* udid = NULL; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + while ((c = getopt_long(argc, argv, "dhv", longopts, NULL)) != -1) { + switch (c) { + case 'd': idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); return 0; + default: + print_usage(argc, argv, 1); + return 2; } } + argc -= optind; + argv += optind; - i--; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; + if (!argv[0]) { + fprintf(stderr, "ERROR: No UDID specified\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; } - strcpy(uuid, argv[i]); + udid = argv[0]; - ret = idevice_new(&phone, uuid); + ret = idevice_new(&device, udid); if (ret != IDEVICE_E_SUCCESS) { - printf("No device found with uuid %s, is it plugged in?\n", uuid); - return -1; + printf("No device found with udid %s.\n", udid); + return 1; } - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(phone, &client, "ideviceenterrecovery")) { - idevice_free(phone); - return -1; + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new(device, &client, TOOL_NAME))) { + printf("ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret); + idevice_free(device); + return 1; } - /* run query and output information */ - printf("Telling device with uuid %s to enter recovery mode.\n", uuid); - if(lockdownd_enter_recovery(client) != LOCKDOWN_E_SUCCESS) - { + int res = 0; + printf("Telling device with udid %s to enter recovery mode.\n", udid); + ldret = lockdownd_enter_recovery(client); + if (ldret == LOCKDOWN_E_SESSION_INACTIVE) { + lockdownd_client_free(client); + client = NULL; + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) { + printf("ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret); + idevice_free(device); + return 1; + } + ldret = lockdownd_enter_recovery(client); + } + if (ldret != LOCKDOWN_E_SUCCESS) { printf("Failed to enter recovery mode.\n"); + res = 1; + } else { + printf("Device is successfully switching to recovery mode.\n"); } - printf("Device is successfully switching to recovery mode.\n"); lockdownd_client_free(client); - idevice_free(phone); + idevice_free(device); - return 0; + return res; } diff --git a/tools/ideviceimagemounter.c b/tools/ideviceimagemounter.c index 3d299f8..511583e 100644 --- a/tools/ideviceimagemounter.c +++ b/tools/ideviceimagemounter.c @@ -1,26 +1,30 @@ -/** - * ideviceimagemounter -- Mount developer/debug disk images on the iPhone/iPod +/* + * ideviceimagemounter.c + * Mount developer/debug disk images on the device * * Copyright (C) 2010 Nikias Bassen <nikias@gmx.li> * - * Licensed under the GNU General Public License Version 2 + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, + * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more profile. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - * USA + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "ideviceimagemounter" + #include <stdlib.h> #define _GNU_SOURCE 1 #define __USE_GNU 1 @@ -28,297 +32,311 @@ #include <string.h> #include <getopt.h> #include <errno.h> -#include <glib.h> #include <libgen.h> +#include <time.h> +#include <sys/time.h> +#include <inttypes.h> +#ifndef WIN32 +#include <signal.h> +#endif #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> #include <libimobiledevice/afc.h> #include <libimobiledevice/notification_proxy.h> #include <libimobiledevice/mobile_image_mounter.h> - -static int indent_level = 0; +#include <libimobiledevice-glue/sha.h> +#include <libimobiledevice-glue/utils.h> +#include <asprintf.h> +#include <plist/plist.h> +#include <libtatsu/tss.h> static int list_mode = 0; +static int use_network = 0; static int xml_mode = 0; -static char *uuid = NULL; -static char *imagetype = NULL; +static const char *udid = NULL; +static const char *imagetype = NULL; static const char PKG_PATH[] = "PublicStaging"; static const char PATH_PREFIX[] = "/private/var/mobile/Media"; -static void print_usage(int argc, char **argv) +typedef enum { + DISK_IMAGE_UPLOAD_TYPE_AFC, + DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE +} disk_image_upload_type_t; + +enum cmd_mode { + CMD_NONE = 0, + CMD_MOUNT, + CMD_UNMOUNT, + CMD_LIST, + CMD_DEVMODESTATUS +}; + +int cmd = CMD_NONE; + +static void print_usage(int argc, char **argv, int is_error) { - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS] IMAGE_FILE IMAGE_SIGNATURE_FILE\n\n", (name ? name + 1: argv[0])); - printf("Mounts the specified disk image on the device.\n\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -l, --list\t\tList mount information\n"); - printf(" -t, --imagetype\tImage type to use, default is 'Developer'\n"); - printf(" -x, --xml\t\tUse XML output\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND [COMMAND OPTIONS...]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Mount, list, or unmount a disk image on the device.\n" + "\n" + "COMMANDS:\n" + " mount PATH Mount the developer disk image at PATH.\n" + " For iOS 17+, PATH is a directory containing a .dmg image,\n" + " a BuildManifest.plist, and a Firmware sub-directory;\n" + " for older versions PATH is a .dmg filename with a" + " .dmg.signature in the same directory, or with another\n" + " parameter pointing to a file elsewhere.\n" + " list List mounted disk images.\n" + " unmount PATH Unmount the image mounted at PATH.\n" + " devmodestatus Query the developer mode status (iOS 16+)\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -t, --imagetype TYPE Image type to use, default is 'Developer'\n" + " -x, --xml Use XML output\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); } static void parse_opts(int argc, char **argv) { + int debug_level = 0; static struct option longopts[] = { - {"help", 0, NULL, 'h'}, - {"uuid", 0, NULL, 'u'}, - {"list", 0, NULL, 'l'}, - {"imagetype", 0, NULL, 't'}, - {"xml", 0, NULL, 'x'}, - {"debug", 0, NULL, 'd'}, - {NULL, 0, NULL, 0} + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "imagetype", required_argument, NULL, 't' }, + { "xml", no_argument, NULL, 'x' }, + { "debug", no_argument, NULL, 'd' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } }; int c; while (1) { - c = getopt_long(argc, argv, "hu:lt:xd", longopts, - (int *) 0); + c = getopt_long(argc, argv, "hu:t:xdnv", longopts, NULL); if (c == -1) { break; } switch (c) { case 'h': - print_usage(argc, argv); + print_usage(argc, argv, 0); exit(0); case 'u': - if (strlen(optarg) != 40) { - printf("%s: invalid UUID specified (length != 40)\n", - argv[0]); - print_usage(argc, argv); + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); exit(2); } - uuid = strdup(optarg); + udid = optarg; break; - case 'l': - list_mode = 1; + case 'n': + use_network = 1; break; case 't': - imagetype = strdup(optarg); + imagetype = optarg; break; case 'x': xml_mode = 1; break; case 'd': - idevice_set_debug_level(1); + debug_level++; break; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + exit(0); default: - print_usage(argc, argv); + print_usage(argc, argv, 1); exit(2); } } + idevice_set_debug_level(debug_level); + tss_set_debug_level(debug_level); } -static void plist_node_to_string(plist_t node); - -static void plist_array_to_string(plist_t node) +static ssize_t mim_upload_cb(void* buf, size_t size, void* userdata) { - /* iterate over items */ - int i, count; - plist_t subnode = NULL; - - count = plist_array_get_size(node); - - for (i = 0; i < count; i++) { - subnode = plist_array_get_item(node, i); - printf("%*s", indent_level, ""); - printf("%d: ", i); - plist_node_to_string(subnode); - } -} - -static void plist_dict_to_string(plist_t node) -{ - /* iterate over key/value pairs */ - plist_dict_iter it = NULL; - - char* key = NULL; - plist_t subnode = NULL; - plist_dict_new_iter(node, &it); - plist_dict_next_item(node, it, &key, &subnode); - while (subnode) - { - printf("%*s", indent_level, ""); - printf("%s", key); - if (plist_get_node_type(subnode) == PLIST_ARRAY) - printf("[%d]: ", plist_array_get_size(subnode)); - else - printf(": "); - free(key); - key = NULL; - plist_node_to_string(subnode); - plist_dict_next_item(node, it, &key, &subnode); - } - free(it); -} - -static void plist_node_to_string(plist_t node) -{ - char *s = NULL; - char *data = NULL; - double d; - uint8_t b; - uint64_t u = 0; - GTimeVal tv = { 0, 0 }; - - plist_type t; - - if (!node) - return; - - t = plist_get_node_type(node); - - switch (t) { - case PLIST_BOOLEAN: - plist_get_bool_val(node, &b); - printf("%s\n", (b ? "true" : "false")); - break; - - case PLIST_UINT: - plist_get_uint_val(node, &u); - printf("%llu\n", (long long)u); - break; - - case PLIST_REAL: - plist_get_real_val(node, &d); - printf("%f\n", d); - break; - - case PLIST_STRING: - plist_get_string_val(node, &s); - printf("%s\n", s); - free(s); - break; - - case PLIST_KEY: - plist_get_key_val(node, &s); - printf("%s: ", s); - free(s); - break; - - case PLIST_DATA: - plist_get_data_val(node, &data, &u); - uint64_t i; - for (i = 0; i < u; i++) { - printf("%02x", (unsigned char)data[i]); - } - free(data); - printf("\n"); - g_free(s); - break; - - case PLIST_DATE: - plist_get_date_val(node, (int32_t*)&tv.tv_sec, (int32_t*)&tv.tv_usec); - s = g_time_val_to_iso8601(&tv); - printf("%s\n", s); - free(s); - break; - - case PLIST_ARRAY: - printf("\n"); - indent_level++; - plist_array_to_string(node); - indent_level--; - break; - - case PLIST_DICT: - printf("\n"); - indent_level++; - plist_dict_to_string(node); - indent_level--; - break; - - default: - break; - } -} - -static void print_xml(plist_t node) -{ - char *xml = NULL; - uint32_t len = 0; - plist_to_xml(node, &xml, &len); - if (xml) - puts(xml); + return fread(buf, 1, size, (FILE*)userdata); } int main(int argc, char **argv) { idevice_t device = NULL; lockdownd_client_t lckd = NULL; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; mobile_image_mounter_client_t mim = NULL; afc_client_t afc = NULL; - uint16_t port = 0; + lockdownd_service_descriptor_t service = NULL; int res = -1; char *image_path = NULL; + size_t image_size = 0; char *image_sig_path = NULL; +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif parse_opts(argc, argv); argc -= optind; argv += optind; - if (!list_mode) { - if (argc < 1) { - printf("ERROR: No IMAGE_FILE has been given!\n"); - return -1; - } - image_path = strdup(argv[0]); - if (argc >= 2) { - image_sig_path = strdup(argv[1]); + if (argc == 0) { + fprintf(stderr, "ERROR: Missing command.\n\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + char* cmdstr = argv[0]; + + int optind2 = 0; + if (!strcmp(cmdstr, "mount")) { + cmd = CMD_MOUNT; + optind2++; + } else if (!strcmp(cmdstr, "list")) { + cmd = CMD_LIST; + optind2++; + } else if (!strcmp(cmdstr, "umount") || !strcmp(cmdstr, "unmount")) { + cmd = CMD_UNMOUNT; + optind2++; + } else if (!strcmp(cmdstr, "devmodestatus")) { + cmd = CMD_DEVMODESTATUS; + optind2++; + } else { + // assume mount command, unless -l / --list was specified + if (list_mode) { + cmd = CMD_LIST; } else { - if (asprintf(&image_sig_path, "%s.signature", image_path) < 0) { - printf("Out of memory?!\n"); - return -1; - } + cmd = CMD_MOUNT; } } - if (IDEVICE_E_SUCCESS != idevice_new(&device, uuid)) { - printf("No device found, is it plugged in?\n"); - return -1; + argc -= optind2; + argv += optind2; + optind += optind2; + + switch (cmd) { + case CMD_MOUNT: + if (argc < 1) { + fprintf(stderr, "ERROR: Missing IMAGE_FILE for mount command\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + image_path = strdup(argv[0]); + if (argc >= 2) { + image_sig_path = strdup(argv[1]); + } else { + if (asprintf(&image_sig_path, "%s.signature", image_path) < 0) { + printf("Out of memory?!\n"); + return 1; + } + } + break; + case CMD_UNMOUNT: + if (argc != 1) { + fprintf(stderr, "ERROR: Missing mount path (argc = %d)\n", argc); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + break; + default: + break; + } + + if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + return 1; } - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lckd, "ideviceimagemounter")) { - printf("ERROR: could not connect to lockdown. Exiting.\n"); + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lckd, TOOL_NAME))) { + printf("ERROR: Could not connect to lockdown, error code %d.\n", ldret); goto leave; } - lockdownd_start_service(lckd, "com.apple.mobile.mobile_image_mounter", &port); + plist_t pver = NULL; + char *product_version = NULL; + lockdownd_get_value(lckd, NULL, "ProductVersion", &pver); + if (pver && plist_get_node_type(pver) == PLIST_STRING) { + plist_get_string_val(pver, &product_version); + } + disk_image_upload_type_t disk_image_upload_type = DISK_IMAGE_UPLOAD_TYPE_AFC; + int product_version_major = 0; + int product_version_minor = 0; + if (product_version) { + if (sscanf(product_version, "%d.%d.%*d", &product_version_major, &product_version_minor) == 2) { + if (product_version_major >= 7) + disk_image_upload_type = DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE; + } + } - if (port == 0) { + if (product_version_major >= 16) { + uint8_t dev_mode_status = 0; + plist_t val = NULL; + ldret = lockdownd_get_value(lckd, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val); + if (ldret == LOCKDOWN_E_SUCCESS) { + plist_get_bool_val(val, &dev_mode_status); + plist_free(val); + } + if (!dev_mode_status) { + printf("ERROR: You have to enable Developer Mode on the given device in order to allowing mounting a developer disk image.\n"); + goto leave; + } + } + + lockdownd_start_service(lckd, "com.apple.mobile.mobile_image_mounter", &service); + + if (!service || service->port == 0) { printf("ERROR: Could not start mobile_image_mounter service!\n"); goto leave; } - if (mobile_image_mounter_new(device, port, &mim) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + if (mobile_image_mounter_new(device, service, &mim) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { printf("ERROR: Could not connect to mobile_image_mounter!\n"); goto leave; - } + } + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } - if (!list_mode) { + if (cmd == CMD_MOUNT) { struct stat fst; - port = 0; - if ((lockdownd_start_service(lckd, "com.apple.afc", &port) != - LOCKDOWN_E_SUCCESS) || !port) { - fprintf(stderr, "Could not start com.apple.afc!\n"); - goto leave; - } - if (afc_client_new(device, port, &afc) != AFC_E_SUCCESS) { - fprintf(stderr, "Could not connect to AFC!\n"); - goto leave; + if (disk_image_upload_type == DISK_IMAGE_UPLOAD_TYPE_AFC) { + if ((lockdownd_start_service(lckd, "com.apple.afc", &service) != + LOCKDOWN_E_SUCCESS) || !service || !service->port) { + fprintf(stderr, "Could not start com.apple.afc!\n"); + goto leave; + } + if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) { + fprintf(stderr, "Could not connect to AFC!\n"); + goto leave; + } + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } } if (stat(image_path, &fst) != 0) { fprintf(stderr, "ERROR: stat: %s: %s\n", image_path, strerror(errno)); goto leave; } - if (stat(image_sig_path, &fst) != 0) { + image_size = fst.st_size; + if (product_version_major < 17 && stat(image_sig_path, &fst) != 0) { fprintf(stderr, "ERROR: stat: %s: %s\n", image_sig_path, strerror(errno)); goto leave; } @@ -327,49 +345,240 @@ int main(int argc, char **argv) lockdownd_client_free(lckd); lckd = NULL; - mobile_image_mounter_error_t err; + mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; plist_t result = NULL; - if (list_mode) { + if (cmd == CMD_LIST) { /* list mounts mode */ if (!imagetype) { - imagetype = strdup("Developer"); + if (product_version_major < 17) { + imagetype = "Developer"; + } else { + imagetype = "Personalized"; + } } err = mobile_image_mounter_lookup_image(mim, imagetype, &result); - free(imagetype); if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { res = 0; - if (xml_mode) { - print_xml(result); - } else { - plist_dict_to_string(result); - } + plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0); } else { printf("Error: lookup_image returned %d\n", err); } - } else { - char sig[8192]; + } else if (cmd == CMD_MOUNT) { + unsigned char *sig = NULL; size_t sig_length = 0; - FILE *f = fopen(image_sig_path, "r"); - if (!f) { - fprintf(stderr, "Error opening signature file '%s': %s\n", image_sig_path, strerror(errno)); - goto leave; - } - sig_length = fread(sig, 1, sizeof(sig), f); - fclose(f); - if (sig_length == 0) { - fprintf(stderr, "Could not read signature from file '%s'\n", image_sig_path); - goto leave; - } + FILE *f; + struct stat fst; + plist_t mount_options = NULL; - f = fopen(image_path, "r"); - if (!f) { - fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno)); - goto leave; + if (product_version_major < 17) { + f = fopen(image_sig_path, "rb"); + if (!f) { + fprintf(stderr, "Error opening signature file '%s': %s\n", image_sig_path, strerror(errno)); + goto leave; + } + if (fstat(fileno(f), &fst) != 0) { + fprintf(stderr, "Error: fstat: %s\n", strerror(errno)); + goto leave; + } + sig = malloc(fst.st_size); + sig_length = fread(sig, 1, fst.st_size, f); + fclose(f); + if (sig_length == 0) { + fprintf(stderr, "Could not read signature from file '%s'\n", image_sig_path); + goto leave; + } + + f = fopen(image_path, "rb"); + if (!f) { + fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno)); + goto leave; + } + } else { + if (stat(image_path, &fst) != 0) { + fprintf(stderr, "Error: stat: '%s': %s\n", image_path, strerror(errno)); + goto leave; + } + if (!S_ISDIR(fst.st_mode)) { + fprintf(stderr, "Error: Personalized Disk Image mount expects a directory as image path.\n"); + goto leave; + } + char* build_manifest_path = string_build_path(image_path, "BuildManifest.plist", NULL); + plist_t build_manifest = NULL; + if (plist_read_from_file(build_manifest_path, &build_manifest, NULL) != 0) { + free(build_manifest_path); + build_manifest_path = string_build_path(image_path, "Restore", "BuildManifest.plist", NULL); + if (plist_read_from_file(build_manifest_path, &build_manifest, NULL) == 0) { + char* image_path_new = string_build_path(image_path, "Restore", NULL); + free(image_path); + image_path = image_path_new; + } + } + if (!build_manifest) { + fprintf(stderr, "Error: Could not locate BuildManifest.plist inside given disk image path!\n"); + goto leave; + } + + plist_t identifiers = NULL; + mobile_image_mounter_error_t merr = mobile_image_mounter_query_personalization_identifiers(mim, NULL, &identifiers); + if (merr != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + fprintf(stderr, "Failed to query personalization identifiers: %d\n", merr); + goto error_out; + } + + unsigned int board_id = plist_dict_get_uint(identifiers, "BoardId"); + unsigned int chip_id = plist_dict_get_uint(identifiers, "ChipID"); + + plist_t build_identities = plist_dict_get_item(build_manifest, "BuildIdentities"); + plist_array_iter iter; + plist_array_new_iter(build_identities, &iter); + plist_t item = NULL; + plist_t build_identity = NULL; + do { + plist_array_next_item(build_identities, iter, &item); + if (!item) { + break; + } + unsigned int bi_board_id = (unsigned int)plist_dict_get_uint(item, "ApBoardID"); + unsigned int bi_chip_id = (unsigned int)plist_dict_get_uint(item, "ApChipID"); + if (bi_chip_id == chip_id && bi_board_id == board_id) { + build_identity = item; + break; + } + } while (item); + plist_mem_free(iter); + if (!build_identity) { + fprintf(stderr, "Error: The given disk image is not compatible with the current device.\n"); + goto leave; + } + plist_t p_tc_path = plist_access_path(build_identity, 4, "Manifest", "LoadableTrustCache", "Info", "Path"); + if (!p_tc_path) { + fprintf(stderr, "Error: Could not determine path for trust cache!\n"); + goto leave; + } + plist_t p_dmg_path = plist_access_path(build_identity, 4, "Manifest", "PersonalizedDMG", "Info", "Path"); + if (!p_dmg_path) { + fprintf(stderr, "Error: Could not determine path for disk image!\n"); + goto leave; + } + char *tc_path = string_build_path(image_path, plist_get_string_ptr(p_tc_path, NULL), NULL); + unsigned char* trust_cache = NULL; + uint64_t trust_cache_size = 0; + if (!buffer_read_from_filename(tc_path, (char**)&trust_cache, &trust_cache_size)) { + fprintf(stderr, "Error: Trust cache does not exist at '%s'!\n", tc_path); + goto leave; + } + mount_options = plist_new_dict(); + plist_dict_set_item(mount_options, "ImageTrustCache", plist_new_data((char*)trust_cache, trust_cache_size)); + free(trust_cache); + char *dmg_path = string_build_path(image_path, plist_get_string_ptr(p_dmg_path, NULL), NULL); + free(image_path); + image_path = dmg_path; + f = fopen(image_path, "rb"); + if (!f) { + fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno)); + goto leave; + } + + unsigned char buf[8192]; + unsigned char sha384_digest[48]; + sha384_context ctx; + sha384_init(&ctx); + fstat(fileno(f), &fst); + image_size = fst.st_size; + while (!feof(f)) { + ssize_t fr = fread(buf, 1, sizeof(buf), f); + if (fr <= 0) { + break; + } + sha384_update(&ctx, buf, fr); + } + rewind(f); + sha384_final(&ctx, sha384_digest); + unsigned char* manifest = NULL; + unsigned int manifest_size = 0; + /* check if the device already has a personalization manifest for this image */ + if (mobile_image_mounter_query_personalization_manifest(mim, "DeveloperDiskImage", sha384_digest, sizeof(sha384_digest), &manifest, &manifest_size) == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + printf("Using existing personalization manifest from device.\n"); + } else { + /* we need to re-connect in this case */ + mobile_image_mounter_free(mim); + mim = NULL; + if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + goto error_out; + } + printf("No personalization manifest, requesting from TSS...\n"); + unsigned char* nonce = NULL; + unsigned int nonce_size = 0; + + /* create new TSS request and fill parameters */ + plist_t request = tss_request_new(NULL); + plist_t params = plist_new_dict(); + tss_parameters_add_from_manifest(params, build_identity, 1); + + /* copy all `Ap,*` items from identifiers */ + plist_dict_iter di = NULL; + plist_dict_new_iter(identifiers, &di); + plist_t node = NULL; + do { + char* key = NULL; + plist_dict_next_item(identifiers, di, &key, &node); + if (node) { + if (!strncmp(key, "Ap,", 3)) { + plist_dict_set_item(request, key, plist_copy(node)); + } + } + free(key); + } while (node); + plist_mem_free(di); + + plist_dict_copy_uint(params, identifiers, "ApECID", "UniqueChipID"); + plist_dict_set_item(params, "ApProductionMode", plist_new_bool(1)); + plist_dict_set_item(params, "ApSecurityMode", plist_new_bool(1)); + plist_dict_set_item(params, "ApSupportsImg4", plist_new_bool(1)); + + /* query nonce from image mounter service */ + merr = mobile_image_mounter_query_nonce(mim, "DeveloperDiskImage", &nonce, &nonce_size); + if (merr == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + plist_dict_set_item(params, "ApNonce", plist_new_data((char*)nonce, nonce_size)); + } else { + fprintf(stderr, "ERROR: Failed to query nonce for developer disk image: %d\n", merr); + goto error_out; + } + mobile_image_mounter_free(mim); + mim = NULL; + + plist_dict_set_item(params, "ApSepNonce", plist_new_data("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 20)); + plist_dict_set_item(params, "UID_MODE", plist_new_bool(0)); + tss_request_add_ap_tags(request, params, NULL); + tss_request_add_common_tags(request, params, NULL); + tss_request_add_ap_img4_tags(request, params); + plist_free(params); + + /* request IM4M from TSS */ + plist_t response = tss_request_send(request, NULL); + plist_free(request); + + plist_t p_manifest = plist_dict_get_item(response, "ApImg4Ticket"); + if (!PLIST_IS_DATA(p_manifest)) { + fprintf(stderr, "Failed to get Img4Ticket\n"); + goto error_out; + } + + uint64_t m4m_len = 0; + plist_get_data_val(p_manifest, (char**)&manifest, &m4m_len); + manifest_size = m4m_len; + plist_free(response); + printf("Done.\n"); + } + sig = manifest; + sig_length = manifest_size; + + imagetype = "Personalized"; } char *targetname = NULL; - if (asprintf(&targetname, "%s/%s", PKG_PATH, basename(image_path)) < 0) { + if (asprintf(&targetname, "%s/%s", PKG_PATH, "staging.dimage") < 0) { fprintf(stderr, "Out of memory!?\n"); goto leave; } @@ -379,68 +588,91 @@ int main(int argc, char **argv) goto leave; } - printf("Copying '%s' --> '%s'\n", image_path, targetname); - - char **strs = NULL; - if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) { - if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) { - fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH); - } - } - if (strs) { - int i = 0; - while (strs[i]) { - free(strs[i]); - i++; - } - free(strs); + if (!imagetype) { + imagetype = "Developer"; } - uint64_t af = 0; - if ((afc_file_open(afc, targetname, AFC_FOPEN_WRONLY, &af) != - AFC_E_SUCCESS) || !af) { - fclose(f); - fprintf(stderr, "afc_file_open on '%s' failed!\n", targetname); - goto leave; + if (!mim) { + if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + goto error_out; + } } - char buf[8192]; - size_t amount = 0; - do { - amount = fread(buf, 1, sizeof(buf), f); - if (amount > 0) { - uint32_t written, total = 0; - while (total < amount) { - written = 0; - if (afc_file_write(afc, af, buf, amount, &written) != - AFC_E_SUCCESS) { - fprintf(stderr, "AFC Write error!\n"); - break; + switch(disk_image_upload_type) { + case DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE: + printf("Uploading %s\n", image_path); + err = mobile_image_mounter_upload_image(mim, imagetype, image_size, sig, sig_length, mim_upload_cb, f); + break; + case DISK_IMAGE_UPLOAD_TYPE_AFC: + default: + printf("Uploading %s --> afc:///%s\n", image_path, targetname); + char **strs = NULL; + if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) { + if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) { + fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH); + } + } + if (strs) { + int i = 0; + while (strs[i]) { + free(strs[i]); + i++; } - total += written; + free(strs); } - if (total != amount) { - fprintf(stderr, "Error: wrote only %d of %d\n", total, - (unsigned int)amount); - afc_file_close(afc, af); + + uint64_t af = 0; + if ((afc_file_open(afc, targetname, AFC_FOPEN_WRONLY, &af) != + AFC_E_SUCCESS) || !af) { fclose(f); + fprintf(stderr, "afc_file_open on '%s' failed!\n", targetname); goto leave; } - } + + char buf[8192]; + size_t amount = 0; + do { + amount = fread(buf, 1, sizeof(buf), f); + if (amount > 0) { + uint32_t written, total = 0; + while (total < amount) { + written = 0; + if (afc_file_write(afc, af, buf + total, amount - total, &written) != + AFC_E_SUCCESS) { + fprintf(stderr, "AFC Write error!\n"); + break; + } + total += written; + } + if (total != amount) { + fprintf(stderr, "Error: wrote only %d of %d\n", total, + (unsigned int)amount); + afc_file_close(afc, af); + fclose(f); + goto leave; + } + } + } + while (amount > 0); + + afc_file_close(afc, af); + break; } - while (amount > 0); - afc_file_close(afc, af); fclose(f); + if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { + printf("ERROR: Device is locked, can't mount. Unlock device and try again.\n"); + } else { + printf("ERROR: Unknown error occurred, can't mount.\n"); + } + goto error_out; + } printf("done.\n"); printf("Mounting...\n"); - if (!imagetype) { - imagetype = strdup("Developer"); - } - err = mobile_image_mounter_mount_image(mim, mountname, sig, sig_length, imagetype, &result); - free(imagetype); + err = mobile_image_mounter_mount_image_with_options(mim, mountname, sig, sig_length, imagetype, mount_options, &result); if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { if (result) { plist_t node = plist_dict_get_item(result, "Status"); @@ -453,20 +685,12 @@ int main(int argc, char **argv) res = 0; } else { printf("unexpected status value:\n"); - if (xml_mode) { - print_xml(result); - } else { - plist_dict_to_string(result); - } + plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0); } free(status); } else { printf("unexpected result:\n"); - if (xml_mode) { - print_xml(result); - } else { - plist_dict_to_string(result); - } + plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0); } } node = plist_dict_get_item(result, "Error"); @@ -478,31 +702,58 @@ int main(int argc, char **argv) free(error); } else { printf("unexpected result:\n"); - if (xml_mode) { - print_xml(result); - } else { - plist_dict_to_string(result); - } + plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0); } - - } else { - if (xml_mode) { - print_xml(result); - } else { - plist_dict_to_string(result); + node = plist_dict_get_item(result, "DetailedError"); + if (node) { + printf("DetailedError: %s\n", plist_get_string_ptr(node, NULL)); } + } else { + plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0); } } } else { printf("Error: mount_image returned %d\n", err); } + } else if (cmd == CMD_UNMOUNT) { + err = mobile_image_mounter_unmount_image(mim, argv[0]); + switch (err) { + case MOBILE_IMAGE_MOUNTER_E_SUCCESS: + printf("Success\n"); + res = 0; + break; + case MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED: + printf("Error: '%s' is not mounted\n", argv[0]); + res = 1; + break; + case MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED: + printf("Error: 'unmount' is not supported on this device\n"); + res = 1; + break; + case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED: + printf("Error: device is locked\n"); + res = 1; + break; + default: + printf("Error: unmount returned %d\n", err); + break; + } + } else if (cmd == CMD_DEVMODESTATUS) { + err = mobile_image_mounter_query_developer_mode_status(mim, &result); + if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + res = 0; + plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0); + } else { + printf("Error: query_developer_mode_status returned %d\n", err); + } } if (result) { plist_free(result); } +error_out: /* perform hangup command */ mobile_image_mounter_hangup(mim); /* free client */ @@ -518,7 +769,7 @@ leave: idevice_free(device); if (image_path) - free(image_path); + free(image_path); if (image_sig_path) free(image_sig_path); diff --git a/tools/ideviceinfo.c b/tools/ideviceinfo.c index e05165b..fd45763 100644 --- a/tools/ideviceinfo.c +++ b/tools/ideviceinfo.c @@ -2,40 +2,59 @@ * ideviceinfo.c * Simple utility to show information about an attached device * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "ideviceinfo" + #include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> -#include <glib.h> +#include <getopt.h> +#ifndef WIN32 +#include <signal.h> +#endif #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> +#include <plist/plist.h> #define FORMAT_KEY_VALUE 1 #define FORMAT_XML 2 static const char *domains[] = { "com.apple.disk_usage", + "com.apple.disk_usage.factory", "com.apple.mobile.battery", -/* FIXME: For some reason lockdownd segfaults on this, works sometimes though +/* FIXME: For some reason lockdownd segfaults on this, works sometimes though "com.apple.mobile.debug",. */ + "com.apple.iqagent", + "com.apple.purplebuddy", + "com.apple.PurpleBuddy", + "com.apple.mobile.chaperone", + "com.apple.mobile.third_party_termination", + "com.apple.mobile.lockdownd", + "com.apple.mobile.lockdown_cache", "com.apple.xcode.developerdomain", "com.apple.international", "com.apple.mobile.data_sync", @@ -55,12 +74,12 @@ static const char *domains[] = { "com.apple.iTunes", "com.apple.mobile.iTunes.store", "com.apple.mobile.iTunes", + "com.apple.fmip", + "com.apple.Accessibility", NULL }; -static int indent_level = 0; - -static int is_domain_known(char *domain) +static int is_domain_known(const char *domain) { int i = 0; while (domains[i] != NULL) { @@ -71,244 +90,147 @@ static int is_domain_known(char *domain) return 0; } -static void plist_node_to_string(plist_t node); - -static void plist_array_to_string(plist_t node) -{ - /* iterate over items */ - int i, count; - plist_t subnode = NULL; - - count = plist_array_get_size(node); - - for (i = 0; i < count; i++) { - subnode = plist_array_get_item(node, i); - printf("%*s", indent_level, ""); - printf("%d: ", i); - plist_node_to_string(subnode); - } -} - -static void plist_dict_to_string(plist_t node) -{ - /* iterate over key/value pairs */ - plist_dict_iter it = NULL; - - char* key = NULL; - plist_t subnode = NULL; - plist_dict_new_iter(node, &it); - plist_dict_next_item(node, it, &key, &subnode); - while (subnode) - { - printf("%*s", indent_level, ""); - printf("%s", key); - if (plist_get_node_type(subnode) == PLIST_ARRAY) - printf("[%d]: ", plist_array_get_size(subnode)); - else - printf(": "); - free(key); - key = NULL; - plist_node_to_string(subnode); - plist_dict_next_item(node, it, &key, &subnode); - } - free(it); -} - -static void plist_node_to_string(plist_t node) -{ - char *s = NULL; - char *data = NULL; - double d; - uint8_t b; - uint64_t u = 0; - GTimeVal tv = { 0, 0 }; - - plist_type t; - - if (!node) - return; - - t = plist_get_node_type(node); - - switch (t) { - case PLIST_BOOLEAN: - plist_get_bool_val(node, &b); - printf("%s\n", (b ? "true" : "false")); - break; - - case PLIST_UINT: - plist_get_uint_val(node, &u); - printf("%llu\n", (long long)u); - break; - - case PLIST_REAL: - plist_get_real_val(node, &d); - printf("%f\n", d); - break; - - case PLIST_STRING: - plist_get_string_val(node, &s); - printf("%s\n", s); - free(s); - break; - - case PLIST_KEY: - plist_get_key_val(node, &s); - printf("%s: ", s); - free(s); - break; - - case PLIST_DATA: - plist_get_data_val(node, &data, &u); - s = g_base64_encode((guchar *)data, u); - free(data); - printf("%s\n", s); - g_free(s); - break; - - case PLIST_DATE: - plist_get_date_val(node, (int32_t*)&tv.tv_sec, (int32_t*)&tv.tv_usec); - s = g_time_val_to_iso8601(&tv); - printf("%s\n", s); - free(s); - break; - - case PLIST_ARRAY: - printf("\n"); - indent_level++; - plist_array_to_string(node); - indent_level--; - break; - - case PLIST_DICT: - printf("\n"); - indent_level++; - plist_dict_to_string(node); - indent_level--; - break; - - default: - break; - } -} - -static void print_usage(int argc, char **argv) +static void print_usage(int argc, char **argv, int is_error) { int i = 0; - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); - printf("Show information about a connected iPhone/iPod Touch.\n\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -s, --simple\t\tuse a simple connection to avoid auto-pairing with the device\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -q, --domain NAME\tset domain of query to NAME. Default: None\n"); - printf(" -k, --key NAME\tonly query key specified by NAME. Default: All keys.\n"); - printf(" -x, --xml\t\toutput information as xml plist instead of key/value pairs\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); - printf(" Known domains are:\n\n"); + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Show information about a connected device.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -s, --simple use simple connection to avoid auto-pairing with device\n" + " -q, --domain NAME set domain of query to NAME. Default: None\n" \ + " -k, --key NAME only query key specified by NAME. Default: All keys.\n" \ + " -x, --xml output information in XML property list format\n" \ + " -h, --help prints usage information\n" \ + " -d, --debug enable communication debugging\n" \ + " -v, --version prints version information\n" \ + "\n" + ); + fprintf(is_error ? stderr : stdout, "Known domains are:\n\n"); while (domains[i] != NULL) { - printf(" %s\n", domains[i++]); + fprintf(is_error ? stderr : stdout, " %s\n", domains[i++]); } - printf("\n"); + fprintf(is_error ? stderr : stdout, + "\n" \ + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); } int main(int argc, char *argv[]) { lockdownd_client_t client = NULL; - idevice_t phone = NULL; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; + idevice_t device = NULL; idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; - int i; int simple = 0; int format = FORMAT_KEY_VALUE; - char uuid[41]; - char *domain = NULL; - char *key = NULL; + const char* udid = NULL; + int use_network = 0; + const char *domain = NULL; + const char *key = NULL; char *xml_doc = NULL; uint32_t xml_length; plist_t node = NULL; - plist_type node_type; - uuid[0] = 0; - /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "domain", required_argument, NULL, 'q' }, + { "key", required_argument, NULL, 'k' }, + { "simple", no_argument, NULL, 's' }, + { "xml", no_argument, NULL, 'x' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + while ((c = getopt_long(argc, argv, "dhu:nq:k:sxv", longopts, NULL)) != -1) { + switch (c) { + case 'd': idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { - i++; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; - } - strcpy(uuid, argv[i]); - continue; - } - else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--domain")) { - i++; - if (!argv[i] || (strlen(argv[i]) < 4)) { - print_usage(argc, argv); - return 0; + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; } - if (!is_domain_known(argv[i])) { - fprintf(stderr, "WARNING: Sending query with unknown domain \"%s\".\n", argv[i]); + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'q': + if (!*optarg) { + fprintf(stderr, "ERROR: 'domain' must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; } - domain = strdup(argv[i]); - continue; - } - else if (!strcmp(argv[i], "-k") || !strcmp(argv[i], "--key")) { - i++; - if (!argv[i] || (strlen(argv[i]) <= 1)) { - print_usage(argc, argv); - return 0; + domain = optarg; + break; + case 'k': + if (!*optarg) { + fprintf(stderr, "ERROR: 'key' must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; } - key = strdup(argv[i]); - continue; - } - else if (!strcmp(argv[i], "-x") || !strcmp(argv[i], "--xml")) { + key = optarg; + break; + case 'x': format = FORMAT_XML; - continue; - } - else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--simple")) { + break; + case 's': simple = 1; - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); + break; + case 'h': + print_usage(argc, argv, 0); return 0; - } - else { - print_usage(argc, argv); + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); return 0; + default: + print_usage(argc, argv, 1); + return 2; } } - if (uuid[0] != 0) { - ret = idevice_new(&phone, uuid); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found with uuid %s, is it plugged in?\n", uuid); - return -1; - } - } - else - { - ret = idevice_new(&phone, NULL); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found, is it plugged in?\n"); - return -1; + argc -= optind; + argv += optind; + + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + fprintf(stderr, "ERROR: Device %s not found!\n", udid); + } else { + fprintf(stderr, "ERROR: No device found!\n"); } + return -1; } - if (LOCKDOWN_E_SUCCESS != (simple ? - lockdownd_client_new(phone, &client, "ideviceinfo"): - lockdownd_client_new_with_handshake(phone, &client, "ideviceinfo"))) { - idevice_free(phone); + if (LOCKDOWN_E_SUCCESS != (ldret = simple ? + lockdownd_client_new(device, &client, TOOL_NAME): + lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret); + idevice_free(device); return -1; } + if (domain && !is_domain_known(domain)) { + fprintf(stderr, "WARNING: Sending query with unknown domain \"%s\".\n", domain); + } + /* run query and output information */ if(lockdownd_get_value(client, domain, key, &node) == LOCKDOWN_E_SUCCESS) { if (node) { @@ -319,16 +241,11 @@ int main(int argc, char *argv[]) free(xml_doc); break; case FORMAT_KEY_VALUE: - node_type = plist_get_node_type(node); - if (node_type == PLIST_DICT) { - plist_dict_to_string(node); - } else if (node_type == PLIST_ARRAY) { - plist_array_to_string(node); - break; - } + plist_write_to_stream(node, stdout, PLIST_FORMAT_LIMD, 0); + break; default: if (key != NULL) - plist_node_to_string(node); + plist_write_to_stream(node, stdout, PLIST_FORMAT_LIMD, 0); break; } plist_free(node); @@ -336,10 +253,8 @@ int main(int argc, char *argv[]) } } - if (domain != NULL) - free(domain); lockdownd_client_free(client); - idevice_free(phone); + idevice_free(device); return 0; } diff --git a/tools/idevicename.c b/tools/idevicename.c new file mode 100644 index 0000000..69b76f6 --- /dev/null +++ b/tools/idevicename.c @@ -0,0 +1,159 @@ +/* + * idevicename.c + * Simple utility to get or set the device name + * + * Copyright (c) 2014 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicename" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <getopt.h> +#ifndef WIN32 +#include <signal.h> +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> + +static void print_usage(int argc, char** argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [NAME]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Display the device name or set it to NAME if specified.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help print usage information\n" + " -v, --version print version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +int main(int argc, char** argv) +{ + int c = 0; + const struct option longopts[] = { + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + int res = -1; + const char* udid = NULL; + int use_network = 0; + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + while ((c = getopt_long(argc, argv, "du:hnv", longopts, NULL)) != -1) { + switch (c) { + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + exit(2); + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'd': + idevice_set_debug_level(1); + break; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + + argc -= optind; + argv += optind; + + if (argc > 1) { + print_usage(argc, argv, 1); + return 2; + } + + idevice_t device = NULL; + if (idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) { + if (udid) { + fprintf(stderr, "ERROR: No device found with udid %s.\n", udid); + } else { + fprintf(stderr, "ERROR: No device found.\n"); + } + return -1; + } + + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME); + if (lerr != LOCKDOWN_E_SUCCESS) { + idevice_free(device); + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr); + return -1; + } + + if (argc == 0) { + // getting device name + char* name = NULL; + lerr = lockdownd_get_device_name(lockdown, &name); + if (name) { + printf("%s\n", name); + free(name); + res = 0; + } else { + fprintf(stderr, "ERROR: Could not get device name, lockdown error %d\n", lerr); + } + } else { + // setting device name + lerr = lockdownd_set_value(lockdown, NULL, "DeviceName", plist_new_string(argv[0])); + if (lerr == LOCKDOWN_E_SUCCESS) { + printf("device name set to '%s'\n", argv[0]); + res = 0; + } else { + fprintf(stderr, "ERROR: Could not set device name, lockdown error %d\n", lerr); + } + } + + lockdownd_client_free(lockdown); + idevice_free(device); + + return res; +} diff --git a/tools/idevicenotificationproxy.c b/tools/idevicenotificationproxy.c new file mode 100644 index 0000000..d1e25c1 --- /dev/null +++ b/tools/idevicenotificationproxy.c @@ -0,0 +1,293 @@ +/* + * idevicenotificationproxy.c + * Simple client for the notification_proxy service + * + * Copyright (c) 2009-2015 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicenotificationproxy" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <errno.h> +#include <signal.h> + +#ifdef WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#else +#include <unistd.h> +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/notification_proxy.h> + +enum cmd_mode { + CMD_NONE = 0, + CMD_OBSERVE, + CMD_POST +}; + +static int quit_flag = 0; + +/** + * signal handler function for cleaning up properly + */ +static void clean_exit(int sig) +{ + fprintf(stderr, "Exiting...\n"); + quit_flag++; +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Post or observe notifications on a device.\n" + "\n" + "Where COMMAND is one of:\n" + " post ID [...] post notification IDs to device and exit\n" + " observe ID [...] observe notification IDs in foreground until CTRL+C\n" + " or signal is received\n" + "\n" + "The following OPTIONS are accepted:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +static void notify_cb(const char *notification, void *user_data) +{ + printf("> %s\n", notification); +} + +int main(int argc, char *argv[]) +{ + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + lockdownd_service_descriptor_t service = NULL; + lockdownd_client_t client = NULL; + idevice_t device = NULL; + np_client_t gnp = NULL; + + int result = -1; + int i = 0; + const char* udid = NULL; + int use_network = 0; + int cmd = CMD_NONE; + char* cmd_arg = NULL; + + int count = 0; + char **nspec = NULL; + char **nspectmp = NULL; + + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + + signal(SIGINT, clean_exit); + signal(SIGTERM, clean_exit); +#ifndef WIN32 + signal(SIGQUIT, clean_exit); + signal(SIGPIPE, SIG_IGN); +#endif + + /* parse cmdline args */ + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + if (!argv[i]) { + fprintf(stderr, "ERROR: Missing command\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (!strcmp(argv[i], "post")) { + cmd = CMD_POST; + } else if (!strcmp(argv[i], "observe")) { + cmd = CMD_OBSERVE; + } + + if (cmd == CMD_POST || cmd == CMD_OBSERVE) { + i++; + if (!argv[i]) { + fprintf(stderr, "ERROR: Please supply a valid notification identifier.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + count = 0; + nspec = malloc(sizeof(char*) * (count+1)); + + while(1) { + if (argv[i] && (strlen(argv[i]) >= 2) && (strncmp(argv[i], "-", 1) != 0)) { + nspectmp = realloc(nspec, sizeof(char*) * (count+1)); + nspectmp[count] = strdup(argv[i]); + nspec = nspectmp; + count = count+1; + i++; + } else { + i--; + break; + } + } + + nspectmp = realloc(nspec, sizeof(char*) * (count+1)); + nspectmp[count] = NULL; + nspec = nspectmp; + } + + /* verify options */ + if (cmd == CMD_NONE) { + fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[0]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + goto cleanup; + } + + if (LOCKDOWN_E_SUCCESS != (ret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ret); + goto cleanup; + } + + ret = lockdownd_start_service(client, NP_SERVICE_NAME, &service); + + lockdownd_client_free(client); + + if ((ret == LOCKDOWN_E_SUCCESS) && (service->port > 0)) { + if (np_client_new(device, service, &gnp) != NP_E_SUCCESS) { + printf("Could not connect to notification_proxy!\n"); + result = -1; + } else { + np_set_notify_callback(gnp, notify_cb, NULL); + + switch (cmd) { + case CMD_POST: + i = 0; + while(nspec[i] != NULL && i < (count+1)) { + printf("< posting \"%s\"\n", nspec[i]); + np_post_notification(gnp, nspec[i]); + i++; + } + break; + case CMD_OBSERVE: + default: + i = 0; + while(nspec[i] != NULL && i < (count+1)) { + printf("! observing \"%s\"\n", nspec[i]); + np_observe_notification(gnp, nspec[i]); + i++; + } + + /* just sleep and wait for notifications */ + while (!quit_flag) { + sleep(1); + } + + break; + } + + result = EXIT_SUCCESS; + + if (gnp) { + np_client_free(gnp); + gnp = NULL; + } + } + } else { + printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ret)); + } + + if (service) { + lockdownd_service_descriptor_free(service); + service = NULL; + } + +cleanup: + if (nspec) { + i = 0; + while(nspec[i] != NULL && i < (count+1)) { + free(nspec[i]); + i++; + } + free(nspec); + } + + if (cmd_arg) { + free(cmd_arg); + } + + if (device) + idevice_free(device); + + return result; +} diff --git a/tools/idevicepair.c b/tools/idevicepair.c index b9676b9..94d3f04 100644 --- a/tools/idevicepair.c +++ b/tools/idevicepair.c @@ -1,114 +1,314 @@ /* * idevicepair.c - * Simple utility to pair/unpair an iDevice + * Manage pairings with devices and this host * - * Copyright (c) 2010 Nikias Bassen All Rights Reserved. + * Copyright (c) 2010-2021 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2014 Martin Szulecki, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicepair" + #include <stdio.h> #include <string.h> #include <stdlib.h> #include <getopt.h> -#include "userpref.h" +#include <ctype.h> +#include <unistd.h> +#ifdef WIN32 +#include <windows.h> +#include <conio.h> +#else +#include <termios.h> +#include <signal.h> +#endif + +#include "common/userpref.h" #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> +#include <plist/plist.h> + +static char *udid = NULL; -static char *uuid = NULL; +#ifdef HAVE_WIRELESS_PAIRING -static void print_usage(int argc, char **argv) +#ifdef WIN32 +#define BS_CC '\b' +#define my_getch getch +#else +#define BS_CC 0x7f +static int my_getch(void) { - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("\n%s - Manage pairings with iPhone/iPod Touch/iPad devices and this host.\n\n", (name ? name + 1: argv[0])); - printf("Usage: %s [OPTIONS] COMMAND\n\n", (name ? name + 1: argv[0])); - printf(" Where COMMAND is one of:\n"); - printf(" hostid print the host id of this computer\n"); - printf(" pair pair device with this computer\n"); - printf(" validate validate if device is paired with this computer\n"); - printf(" unpair unpair device with this computer\n"); - printf(" list list devices paired with this computer\n\n"); - printf(" The following OPTIONS are accepted:\n"); - printf(" -d, --debug enable communication debugging\n"); - printf(" -u, --uuid UUID target specific device by its 40-digit device UUID\n"); - printf(" -h, --help prints usage information\n"); - printf("\n"); + struct termios oldt, newt; + int ch; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return ch; } +#endif -static void parse_opts(int argc, char **argv) +static int get_hidden_input(char *buf, int maxlen) { - static struct option longopts[] = { - {"help", 0, NULL, 'h'}, - {"uuid", 1, NULL, 'u'}, - {"debug", 0, NULL, 'd'}, - {NULL, 0, NULL, 0} - }; + int pwlen = 0; int c; - while (1) { - c = getopt_long(argc, argv, "hu:d", longopts, (int*)0); - if (c == -1) { + while ((c = my_getch())) { + if ((c == '\r') || (c == '\n')) { break; + } else if (isprint(c)) { + if (pwlen < maxlen-1) + buf[pwlen++] = c; + fputc('*', stderr); + } else if (c == BS_CC) { + if (pwlen > 0) { + fputs("\b \b", stderr); + pwlen--; + } } + } + buf[pwlen] = 0; + return pwlen; +} - switch (c) { - case 'h': - print_usage(argc, argv); - exit(EXIT_SUCCESS); - case 'u': - if (strlen(optarg) != 40) { - printf("%s: invalid UUID specified (length != 40)\n", argv[0]); - print_usage(argc, argv); - exit(2); - } - uuid = strdup(optarg); +static void pairing_cb(lockdownd_cu_pairing_cb_type_t cb_type, void *user_data, void* data_ptr, unsigned int* data_size) +{ + if (cb_type == LOCKDOWN_CU_PAIRING_PIN_REQUESTED) { + printf("Enter PIN: "); + fflush(stdout); + + *data_size = get_hidden_input((char*)data_ptr, *data_size); + + printf("\n"); + } else if (cb_type == LOCKDOWN_CU_PAIRING_DEVICE_INFO) { + printf("Device info:\n"); + plist_write_to_stream((plist_t)data_ptr, stdout, PLIST_FORMAT_LIMD, PLIST_OPT_INDENT | PLIST_OPT_INDENT_BY(2)); + } else if (cb_type == LOCKDOWN_CU_PAIRING_ERROR) { + printf("ERROR: %s\n", (data_ptr) ? (char*)data_ptr : "(unknown)"); + } +} + +#endif /* HAVE_WIRELESS_PAIRING */ + +static void print_error_message(lockdownd_error_t err) +{ + switch (err) { + case LOCKDOWN_E_PASSWORD_PROTECTED: + printf("ERROR: Could not validate with device %s because a passcode is set. Please enter the passcode on the device and retry.\n", udid); break; - case 'd': - idevice_set_debug_level(1); + case LOCKDOWN_E_INVALID_CONF: + case LOCKDOWN_E_INVALID_HOST_ID: + printf("ERROR: Device %s is not paired with this host\n", udid); + break; + case LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING: + printf("ERROR: Please accept the trust dialog on the screen of device %s, then attempt to pair again.\n", udid); + break; + case LOCKDOWN_E_USER_DENIED_PAIRING: + printf("ERROR: Device %s said that the user denied the trust dialog.\n", udid); + break; + case LOCKDOWN_E_PAIRING_FAILED: + printf("ERROR: Pairing with device %s failed.\n", udid); + break; + case LOCKDOWN_E_GET_PROHIBITED: + case LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION: + printf("ERROR: Pairing is not possible over this connection.\n"); +#ifdef HAVE_WIRELESS_PAIRING + printf("To perform a wireless pairing use the -w command line switch. See usage or man page for details.\n"); +#endif break; default: - print_usage(argc, argv); - exit(EXIT_SUCCESS); - } + printf("ERROR: Device %s returned unhandled error code %d\n", udid, err); + break; } } +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Manage host pairings with devices and usbmuxd.\n" + "\n" + "Where COMMAND is one of:\n" + " systembuid print the system buid of the usbmuxd host\n" + " hostid print the host id for target device\n" + " pair pair device with this host\n" + " validate validate if device is paired with this host\n" + " unpair unpair device with this host\n" + " list list devices paired with this host\n" + "\n" + "The following OPTIONS are accepted:\n" + " -u, --udid UDID target specific device by UDID\n" + ); +#ifdef HAVE_WIRELESS_PAIRING + fprintf(is_error ? stderr : stdout, + " -w, --wireless perform wireless pairing (see NOTE)\n" + " -n, --network connect to network device (see NOTE)\n" + ); +#endif + fprintf(is_error ? stderr : stdout, + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + ); +#ifdef HAVE_WIRELESS_PAIRING + fprintf(is_error ? stderr : stdout, + "\n" + "NOTE: Pairing over network (wireless pairing) is only supported by Apple TV\n" + "devices. To perform a wireless pairing, you need to use the -w command line\n" + "switch. Make sure to put the device into pairing mode first by opening\n" + "Settings > Remotes and Devices > Remote App and Devices.\n" + ); +#endif + fprintf(is_error ? stderr : stdout, + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + int main(int argc, char **argv) { + int c = 0; + static struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, +#ifdef HAVE_WIRELESS_PAIRING + { "wireless", no_argument, NULL, 'w' }, + { "network", no_argument, NULL, 'n' }, + { "hostinfo", required_argument, NULL, 1 }, +#endif + { "debug", no_argument, NULL, 'd' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; +#ifdef HAVE_WIRELESS_PAIRING +#define SHORT_OPTIONS "hu:wndv" +#else +#define SHORT_OPTIONS "hu:dv" +#endif lockdownd_client_t client = NULL; - idevice_t phone = NULL; + idevice_t device = NULL; idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; lockdownd_error_t lerr; int result; char *type = NULL; + int use_network = 0; + int wireless_pairing = 0; +#ifdef HAVE_WIRELESS_PAIRING + plist_t host_info_plist = NULL; +#endif char *cmd; typedef enum { - OP_NONE = 0, OP_PAIR, OP_VALIDATE, OP_UNPAIR, OP_LIST, OP_HOSTID + OP_NONE = 0, OP_PAIR, OP_VALIDATE, OP_UNPAIR, OP_LIST, OP_HOSTID, OP_SYSTEMBUID } op_t; op_t op = OP_NONE; - parse_opts(argc, argv); + while ((c = getopt_long(argc, argv, SHORT_OPTIONS, longopts, NULL)) != -1) { + switch (c) { + case 'h': + print_usage(argc, argv, 0); + exit(EXIT_SUCCESS); + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + result = EXIT_FAILURE; + goto leave; + } + free(udid); + udid = strdup(optarg); + break; +#ifdef HAVE_WIRELESS_PAIRING + case 'w': + wireless_pairing = 1; + break; + case 'n': + use_network = 1; + break; + case 1: + if (!*optarg) { + fprintf(stderr, "ERROR: --hostinfo argument must not be empty!\n"); + result = EXIT_FAILURE; + goto leave; + } + if (*optarg == '@') { + plist_read_from_file(optarg+1, &host_info_plist, NULL); + if (!host_info_plist) { + fprintf(stderr, "ERROR: Could not read from file '%s'\n", optarg+1); + result = EXIT_FAILURE; + goto leave; + } + } +#ifdef HAVE_PLIST_JSON + else if (*optarg == '{') { + if (plist_from_json(optarg, strlen(optarg), &host_info_plist) != PLIST_ERR_SUCCESS) { + fprintf(stderr, "ERROR: --hostinfo argument not valid. Make sure it is a JSON dictionary.\n"); + result = EXIT_FAILURE; + goto leave; + } + } +#endif + else { + fprintf(stderr, "ERROR: --hostinfo argument not valid. To specify a path prefix with '@'\n"); + result = EXIT_FAILURE; + goto leave; + } + break; +#endif + case 'd': + idevice_set_debug_level(1); + break; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + result = EXIT_SUCCESS; + goto leave; + default: + print_usage(argc, argv, 1); + result = EXIT_FAILURE; + goto leave; + } + } + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif if ((argc - optind) < 1) { - printf("ERROR: You need to specify a COMMAND!\n"); - print_usage(argc, argv); - exit(EXIT_FAILURE); + fprintf(stderr, "ERROR: You need to specify a COMMAND!\n"); + print_usage(argc, argv, 1); + result = EXIT_FAILURE; + goto leave; + } + + if (wireless_pairing && use_network) { + fprintf(stderr, "ERROR: You cannot use -w and -n together.\n"); + print_usage(argc, argv, 1); + result = EXIT_FAILURE; + goto leave; } cmd = (argv+optind)[0]; @@ -123,60 +323,91 @@ int main(int argc, char **argv) op = OP_LIST; } else if (!strcmp(cmd, "hostid")) { op = OP_HOSTID; + } else if (!strcmp(cmd, "systembuid")) { + op = OP_SYSTEMBUID; } else { - printf("ERROR: Invalid command '%s' specified\n", cmd); - print_usage(argc, argv); - exit(EXIT_FAILURE); + fprintf(stderr, "ERROR: Invalid command '%s' specified\n", cmd); + print_usage(argc, argv, 1); + result = EXIT_FAILURE; + goto leave; } - if (op == OP_HOSTID) { - char *hostid = NULL; - userpref_get_host_id(&hostid); + if (wireless_pairing) { + if (op == OP_VALIDATE || op == OP_UNPAIR) { + fprintf(stderr, "ERROR: Command '%s' is not supported with -w\n", cmd); + print_usage(argc, argv, 1); + result = EXIT_FAILURE; + goto leave; + } + use_network = 1; + } - printf("%s\n", hostid); + if (op == OP_SYSTEMBUID) { + char *systembuid = NULL; + userpref_read_system_buid(&systembuid); - if (hostid) - free(hostid); + printf("%s\n", systembuid); - return EXIT_SUCCESS; + free(systembuid); + + result = EXIT_SUCCESS; + goto leave; } if (op == OP_LIST) { unsigned int i; - char **uuids = NULL; + char **udids = NULL; unsigned int count = 0; - userpref_get_paired_uuids(&uuids, &count); + userpref_get_paired_udids(&udids, &count); for (i = 0; i < count; i++) { - printf("%s\n", uuids[i]); + printf("%s\n", udids[i]); + free(udids[i]); } - if (uuids) - g_strfreev(uuids); - if (uuid) - free(uuid); - return EXIT_SUCCESS; + free(udids); + result = EXIT_SUCCESS; + goto leave; } - if (uuid) { - ret = idevice_new(&phone, uuid); - free(uuid); - uuid = NULL; - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found with uuid %s, is it plugged in?\n", uuid); - return EXIT_FAILURE; + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); } - } else { - ret = idevice_new(&phone, NULL); + result = EXIT_FAILURE; + goto leave; + } + if (!udid) { + ret = idevice_get_udid(device, &udid); if (ret != IDEVICE_E_SUCCESS) { - printf("No device found, is it plugged in?\n"); - return EXIT_FAILURE; + printf("ERROR: Could not get device udid, error code %d\n", ret); + result = EXIT_FAILURE; + goto leave; } } - lerr = lockdownd_client_new(phone, &client, "idevicepair"); + if (op == OP_HOSTID) { + plist_t pair_record = NULL; + char *hostid = NULL; + + userpref_read_pair_record(udid, &pair_record); + pair_record_get_host_id(pair_record, &hostid); + + printf("%s\n", hostid); + + free(hostid); + plist_free(pair_record); + + result = EXIT_SUCCESS; + goto leave; + } + + lerr = lockdownd_client_new(device, &client, TOOL_NAME); if (lerr != LOCKDOWN_E_SUCCESS) { - idevice_free(phone); - printf("ERROR: lockdownd_client_new failed with error code %d\n", lerr); - return EXIT_FAILURE; + printf("ERROR: Could not connect to lockdownd, error code %d\n", lerr); + result = EXIT_FAILURE; + goto leave; } result = EXIT_SUCCESS; @@ -187,76 +418,62 @@ int main(int argc, char **argv) result = EXIT_FAILURE; goto leave; } else { - if (strcmp("com.apple.mobile.lockdown", type)) { + if (strcmp("com.apple.mobile.lockdown", type) != 0) { printf("WARNING: QueryType request returned '%s'\n", type); } - if (type) { - free(type); - } - } - - ret = idevice_get_uuid(phone, &uuid); - if (ret != IDEVICE_E_SUCCESS) { - printf("ERROR: Could not get device uuid, error code %d\n", ret); - result = EXIT_FAILURE; - goto leave; + free(type); } switch(op) { default: case OP_PAIR: - lerr = lockdownd_pair(client, NULL); +#ifdef HAVE_WIRELESS_PAIRING + if (wireless_pairing) { + lerr = lockdownd_cu_pairing_create(client, pairing_cb, NULL, host_info_plist, NULL); + if (lerr == LOCKDOWN_E_SUCCESS) { + lerr = lockdownd_pair_cu(client); + } + } else +#endif + { + lerr = lockdownd_pair(client, NULL); + } if (lerr == LOCKDOWN_E_SUCCESS) { - printf("SUCCESS: Paired with device %s\n", uuid); + printf("SUCCESS: Paired with device %s\n", udid); } else { result = EXIT_FAILURE; - if (lerr == LOCKDOWN_E_PASSWORD_PROTECTED) { - printf("ERROR: Could not pair with the device because a passcode is set. Please enter the passcode on the device and retry.\n"); - } else { - printf("ERROR: Pairing with device %s failed with unhandled error code %d\n", uuid, lerr); - } + print_error_message(lerr); } break; case OP_VALIDATE: - lerr = lockdownd_validate_pair(client, NULL); + lockdownd_client_free(client); + client = NULL; + lerr = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME); if (lerr == LOCKDOWN_E_SUCCESS) { - printf("SUCCESS: Validated pairing with device %s\n", uuid); + printf("SUCCESS: Validated pairing with device %s\n", udid); } else { result = EXIT_FAILURE; - if (lerr == LOCKDOWN_E_PASSWORD_PROTECTED) { - printf("ERROR: Could not validate with the device because a passcode is set. Please enter the passcode on the device and retry.\n"); - } else if (lerr == LOCKDOWN_E_INVALID_HOST_ID) { - printf("ERROR: Device %s is not paired with this host\n", uuid); - } else { - printf("ERROR: Pairing failed with unhandled error code %d\n", lerr); - } + print_error_message(lerr); } break; case OP_UNPAIR: lerr = lockdownd_unpair(client, NULL); if (lerr == LOCKDOWN_E_SUCCESS) { - /* also remove local device public key */ - userpref_remove_device_public_key(uuid); - printf("SUCCESS: Unpaired with device %s\n", uuid); + printf("SUCCESS: Unpaired with device %s\n", udid); } else { result = EXIT_FAILURE; - if (lerr == LOCKDOWN_E_INVALID_HOST_ID) { - printf("ERROR: Device %s is not paired with this host\n", uuid); - } else { - printf("ERROR: Unpairing with device %s failed with unhandled error code %d\n", uuid, lerr); - } + print_error_message(lerr); } break; } leave: lockdownd_client_free(client); - idevice_free(phone); - if (uuid) { - free(uuid); - } + idevice_free(device); + free(udid); + return result; } diff --git a/tools/ideviceprovision.c b/tools/ideviceprovision.c new file mode 100644 index 0000000..4080a28 --- /dev/null +++ b/tools/ideviceprovision.c @@ -0,0 +1,689 @@ +/* + * ideviceprovision.c + * Simple utility to install, get, or remove provisioning profiles + * to/from idevices + * + * Copyright (c) 2012-2016 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "ideviceprovision" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <sys/stat.h> +#include <errno.h> +#ifndef WIN32 +#include <signal.h> +#endif + +#ifdef WIN32 +#include <windows.h> +#else +#include <arpa/inet.h> +#endif + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/misagent.h> +#include <plist/plist.h> + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Manage provisioning profiles on a device.\n" + "\n" + "Where COMMAND is one of:\n" + " install FILE Installs the provisioning profile specified by FILE.\n" + " A valid .mobileprovision file is expected.\n" + " list Get a list of all provisioning profiles on the device.\n" + " copy PATH Retrieves all provisioning profiles from the device and\n" + " stores them into the existing directory specified by PATH.\n" + " The files will be stored as UUID.mobileprovision\n" + " copy UUID PATH Retrieves the provisioning profile identified by UUID\n" + " from the device and stores it into the existing directory\n" + " specified by PATH. The file will be stored as UUID.mobileprovision.\n" + " remove UUID Removes the provisioning profile identified by UUID.\n" + " remove-all Removes all installed provisioning profiles.\n" + " dump FILE Prints detailed information about the provisioning profile\n" + " specified by FILE.\n" + "\n" + "The following OPTIONS are accepted:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -x, --xml print XML output when using the 'dump' command\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +enum { + OP_INSTALL, + OP_LIST, + OP_COPY, + OP_REMOVE, + OP_DUMP, + NUM_OPS +}; + +#define ASN1_SEQUENCE 0x30 +#define ASN1_CONTAINER 0xA0 +#define ASN1_OBJECT_IDENTIFIER 0x06 +#define ASN1_OCTET_STRING 0x04 + +static void asn1_next_item(unsigned char** p) +{ + char bsize = *(*p+1); + if (bsize & 0x80) { + *p += 2 + (bsize & 0xF); + } else { + *p += 3; + } +} + +static size_t asn1_item_get_size(const unsigned char* p) +{ + size_t res = 0; + char bsize = *(p+1); + if (bsize & 0x80) { + uint16_t ws = 0; + uint32_t ds = 0; + switch (bsize & 0xF) { + case 2: + ws = *(uint16_t*)(p+2); + res = ntohs(ws); + break; + case 3: + ds = *(uint32_t*)(p+2); + res = ntohl(ds) >> 8; + break; + case 4: + ds = *(uint32_t*)(p+2); + res = ntohl(ds); + break; + default: + fprintf(stderr, "ERROR: Invalid or unimplemented byte size %d\n", bsize & 0xF); + break; + } + } else { + res = (int)bsize; + } + return res; +} + +static void asn1_skip_item(unsigned char** p) +{ + size_t sz = asn1_item_get_size(*p); + *p += 2; + *p += sz; +} + +static plist_t profile_get_embedded_plist(plist_t profile) +{ + if (plist_get_node_type(profile) != PLIST_DATA) { + fprintf(stderr, "%s: unexpected plist node type for profile (PLIST_DATA expected)\n", __func__); + return NULL; + } + char* bbuf = NULL; + uint64_t blen = 0; + plist_get_data_val(profile, &bbuf, &blen); + if (!bbuf) { + fprintf(stderr, "%s: could not get data value from plist node\n", __func__); + return NULL; + } + + unsigned char* pp = (unsigned char*)bbuf; + + if (*pp != ASN1_SEQUENCE) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (0)\n", __func__); + return NULL; + } + size_t slen = asn1_item_get_size(pp); + char bsize = *(pp+1); + if (bsize & 0x80) { + slen += 2 + (bsize & 0xF); + } else { + slen += 3; + } + if (slen != blen) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (1)\n", __func__); + return NULL; + } + asn1_next_item(&pp); + + if (*pp != ASN1_OBJECT_IDENTIFIER) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (2)\n", __func__); + return NULL; + } + asn1_skip_item(&pp); + + if (*pp != ASN1_CONTAINER) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (3)\n", __func__); + return NULL; + } + asn1_next_item(&pp); + + if (*pp != ASN1_SEQUENCE) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (4)\n", __func__); + return NULL; + } + asn1_next_item(&pp); + + int k = 0; + // go to the 3rd element (skip 2) + while (k < 2) { + asn1_skip_item(&pp); + k++; + } + if (*pp != ASN1_SEQUENCE) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (5)\n", __func__); + return NULL; + } + asn1_next_item(&pp); + + if (*pp != ASN1_OBJECT_IDENTIFIER) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (6)\n", __func__); + return NULL; + } + asn1_skip_item(&pp); + + if (*pp != ASN1_CONTAINER) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (7)\n", __func__); + return NULL; + } + asn1_next_item(&pp); + + if (*pp != ASN1_OCTET_STRING) { + free(bbuf); + fprintf(stderr, "%s: unexpected profile data (8)\n", __func__); + return NULL; + } + slen = asn1_item_get_size(pp); + asn1_next_item(&pp); + + plist_t pl = NULL; + plist_from_xml((char*)pp, slen, &pl); + free(bbuf); + + return pl; +} + +static int profile_read_from_file(const char* path, unsigned char **profile_data, unsigned int *profile_size) +{ + FILE* f = fopen(path, "rb"); + if (!f) { + fprintf(stderr, "Could not open file '%s'\n", path); + return -1; + } + fseek(f, 0, SEEK_END); + long int size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (size >= 0x1000000) { + fprintf(stderr, "The file '%s' is too large for processing.\n", path); + fclose(f); + return -1; + } + + unsigned char* buf = malloc(size); + if (!buf) { + fprintf(stderr, "Could not allocate memory...\n"); + fclose(f); + return -1; + } + + long int cur = 0; + while (cur < size) { + ssize_t r = fread(buf+cur, 1, 512, f); + if (r <= 0) { + break; + } + cur += r; + } + fclose(f); + + if (cur != size) { + free(buf); + fprintf(stderr, "Could not read in file '%s' (size %ld read %ld)\n", path, size, cur); + return -1; + } + + *profile_data = buf; + *profile_size = (unsigned int)size; + + return 0; +} + +int main(int argc, char *argv[]) +{ + lockdownd_client_t client = NULL; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; + lockdownd_service_descriptor_t service = NULL; + idevice_t device = NULL; + idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + int res = 0; + int i; + int op = -1; + int output_xml = 0; + const char* udid = NULL; + const char* param = NULL; + const char* param2 = NULL; + int use_network = 0; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { "xml", no_argument, NULL, 'x' }, + { NULL, 0, NULL, 0} + }; + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + /* parse cmdline args */ + while ((c = getopt_long(argc, argv, "dhu:nvx", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + case 'x': + output_xml = 1; + break; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + if (!argv[0]) { + fprintf(stderr, "ERROR: Missing command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + i = 0; + if (!strcmp(argv[i], "install")) { + op = OP_INSTALL; + i++; + if (!argv[i] || !*argv[i]) { + fprintf(stderr, "Missing argument for 'install' command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + param = argv[i]; + } + else if (!strcmp(argv[i], "list")) { + op = OP_LIST; + } + else if (!strcmp(argv[i], "copy")) { + op = OP_COPY; + i++; + if (!argv[i] || !*argv[i]) { + fprintf(stderr, "Missing argument for 'copy' command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + param = argv[i]; + i++; + if (argv[i] && (strlen(argv[i]) > 0)) { + param2 = argv[i]; + } + } + else if (!strcmp(argv[i], "remove")) { + op = OP_REMOVE; + i++; + if (!argv[i] || !*argv[i]) { + fprintf(stderr, "Missing argument for 'remove' command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + param = argv[i]; + } + else if (!strcmp(argv[i], "remove-all")) { + op = OP_REMOVE; + } + else if (!strcmp(argv[i], "dump")) { + op = OP_DUMP; + i++; + if (!argv[i] || !*argv[i]) { + fprintf(stderr, "Missing argument for 'remove' command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + param = argv[i]; + } + if ((op == -1) || (op >= NUM_OPS)) { + fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[i]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (op == OP_DUMP) { + unsigned char* profile_data = NULL; + unsigned int profile_size = 0; + if (profile_read_from_file(param, &profile_data, &profile_size) != 0) { + return -1; + } + plist_t pdata = plist_new_data((char*)profile_data, profile_size); + plist_t pl = profile_get_embedded_plist(pdata); + plist_free(pdata); + free(profile_data); + + if (pl) { + if (output_xml) { + char* xml = NULL; + uint32_t xlen = 0; + plist_to_xml(pl, &xml, &xlen); + if (xml) { + printf("%s\n", xml); + free(xml); + } + } else { + if (pl && (plist_get_node_type(pl) == PLIST_DICT)) { + plist_write_to_stream(pl, stdout, PLIST_FORMAT_LIMD, 0); + } else { + fprintf(stderr, "ERROR: unexpected node type in profile plist (not PLIST_DICT)\n"); + res = -1; + } + } + } else { + fprintf(stderr, "ERROR: could not extract embedded plist from profile!\n"); + } + plist_free(pl); + + return res; + } + + if (op == OP_COPY) { + struct stat st; + const char *checkdir = (param2) ? param2 : param; + if ((stat(checkdir, &st) < 0) || !S_ISDIR(st.st_mode)) { + fprintf(stderr, "ERROR: %s does not exist or is not a directory!\n", checkdir); + return -1; + } + } + + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + return -1; + } + + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ldret); + idevice_free(device); + return -1; + } + + plist_t pver = NULL; + char *pver_s = NULL; + lockdownd_get_value(client, NULL, "ProductVersion", &pver); + if (pver && plist_get_node_type(pver) == PLIST_STRING) { + plist_get_string_val(pver, &pver_s); + } + plist_free(pver); + int product_version_major = 0; + int product_version_minor = 0; + int product_version_patch = 0; + if (pver_s) { + sscanf(pver_s, "%d.%d.%d", &product_version_major, &product_version_minor, &product_version_patch); + free(pver_s); + } + if (product_version_major == 0) { + fprintf(stderr, "ERROR: Could not determine the device's ProductVersion\n"); + lockdownd_client_free(client); + idevice_free(device); + return -1; + } + int product_version = ((product_version_major & 0xFF) << 16) | ((product_version_minor & 0xFF) << 8) | (product_version_patch & 0xFF); + + lockdownd_error_t lerr = lockdownd_start_service(client, MISAGENT_SERVICE_NAME, &service); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "Could not start service %s: %s\n", MISAGENT_SERVICE_NAME, lockdownd_strerror(lerr)); + lockdownd_client_free(client); + idevice_free(device); + return -1; + } + lockdownd_client_free(client); + client = NULL; + + misagent_client_t mis = NULL; + if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) { + fprintf(stderr, "Could not connect to %s on device\n", MISAGENT_SERVICE_NAME); + if (service) + lockdownd_service_descriptor_free(service); + lockdownd_client_free(client); + idevice_free(device); + return -1; + } + + if (service) + lockdownd_service_descriptor_free(service); + + switch (op) { + case OP_INSTALL: + { + unsigned char* profile_data = NULL; + unsigned int profile_size = 0; + if (profile_read_from_file(param, &profile_data, &profile_size) != 0) { + break; + } + + uint64_t psize = profile_size; + plist_t pdata = plist_new_data((const char*)profile_data, psize); + free(profile_data); + + if (misagent_install(mis, pdata) == MISAGENT_E_SUCCESS) { + printf("Profile '%s' installed successfully.\n", param); + } else { + int sc = misagent_get_status_code(mis); + fprintf(stderr, "Could not install profile '%s', status code: 0x%x\n", param, sc); + } + } + break; + case OP_LIST: + case OP_COPY: + { + plist_t profiles = NULL; + misagent_error_t merr; + if (product_version < 0x090300) { + merr = misagent_copy(mis, &profiles); + } else { + merr = misagent_copy_all(mis, &profiles); + } + if (merr == MISAGENT_E_SUCCESS) { + int found_match = 0; + uint32_t num_profiles = plist_array_get_size(profiles); + if (op == OP_LIST || !param2) { + printf("Device has %d provisioning %s installed:\n", num_profiles, (num_profiles == 1) ? "profile" : "profiles"); + } + uint32_t j; + for (j = 0; !found_match && j < num_profiles; j++) { + char* p_name = NULL; + char* p_uuid = NULL; + plist_t profile = plist_array_get_item(profiles, j); + plist_t pl = profile_get_embedded_plist(profile); + if (pl && (plist_get_node_type(pl) == PLIST_DICT)) { + plist_t node; + node = plist_dict_get_item(pl, "Name"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + plist_get_string_val(node, &p_name); + } + node = plist_dict_get_item(pl, "UUID"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + plist_get_string_val(node, &p_uuid); + } + } + if (param2) { + if (p_uuid && !strcmp(p_uuid, param)) { + found_match = 1; + } else { + free(p_uuid); + free(p_name); + continue; + } + } + printf("%s - %s\n", (p_uuid) ? p_uuid : "(unknown id)", (p_name) ? p_name : "(no name)"); + if (op == OP_COPY) { + char pfname[512]; + if (p_uuid) { + sprintf(pfname, "%s/%s.mobileprovision", (param2) ? param2 : param, p_uuid); + } else { + sprintf(pfname, "%s/profile%d.mobileprovision", (param2) ? param2 : param, j); + } + FILE* f = fopen(pfname, "wb"); + if (f) { + char* dt = NULL; + uint64_t ds = 0; + plist_get_data_val(profile, &dt, &ds); + fwrite(dt, 1, ds, f); + fclose(f); + printf(" => %s\n", pfname); + } else { + fprintf(stderr, "Could not open '%s' for writing: %s\n", pfname, strerror(errno)); + } + } + free(p_uuid); + free(p_name); + } + if (param2 && !found_match) { + fprintf(stderr, "Profile '%s' was not found on the device.\n", param); + res = -1; + } + } else { + int sc = misagent_get_status_code(mis); + fprintf(stderr, "Could not get installed profiles from device, status code: 0x%x\n", sc); + res = -1; + } + plist_free(profiles); + } + break; + case OP_REMOVE: + if (param) { + /* remove specified provisioning profile */ + if (misagent_remove(mis, param) == MISAGENT_E_SUCCESS) { + printf("Profile '%s' removed.\n", param); + } else { + int sc = misagent_get_status_code(mis); + fprintf(stderr, "Could not remove profile '%s', status code 0x%x\n", param, sc); + } + } else { + /* remove all provisioning profiles */ + plist_t profiles = NULL; + misagent_error_t merr; + if (product_version < 0x090300) { + merr = misagent_copy(mis, &profiles); + } else { + merr = misagent_copy_all(mis, &profiles); + } + if (merr == MISAGENT_E_SUCCESS) { + uint32_t j; + uint32_t num_removed = 0; + for (j = 0; j < plist_array_get_size(profiles); j++) { + char* p_name = NULL; + char* p_uuid = NULL; + plist_t profile = plist_array_get_item(profiles, j); + plist_t pl = profile_get_embedded_plist(profile); + if (pl && (plist_get_node_type(pl) == PLIST_DICT)) { + plist_t node; + node = plist_dict_get_item(pl, "Name"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + plist_get_string_val(node, &p_name); + } + node = plist_dict_get_item(pl, "UUID"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + plist_get_string_val(node, &p_uuid); + } + } + if (p_uuid) { + if (misagent_remove(mis, p_uuid) == MISAGENT_E_SUCCESS) { + printf("OK profile removed: %s - %s\n", p_uuid, (p_name) ? p_name : "(no name)"); + num_removed++; + } else { + int sc = misagent_get_status_code(mis); + printf("FAIL profile not removed: %s - %s (status code 0x%x)\n", p_uuid, (p_name) ? p_name : "(no name)", sc); + } + } + free(p_name); + free(p_uuid); + } + printf("%d profiles removed.\n", num_removed); + } else { + int sc = misagent_get_status_code(mis); + fprintf(stderr, "Could not get installed profiles from device, status code: 0x%x\n", sc); + res = -1; + } + plist_free(profiles); + } + break; + default: + break; + } + + misagent_client_free(mis); + + idevice_free(device); + + return res; +} + diff --git a/tools/idevicescreenshot.c b/tools/idevicescreenshot.c index 8567f77..0e694c7 100644 --- a/tools/idevicescreenshot.c +++ b/tools/idevicescreenshot.c @@ -1,113 +1,227 @@ -/** - * idevicescreenshot -- Gets a screenshot from a connected iPhone/iPod Touch +/* + * idevicescreenshot.c + * Gets a screenshot from a device * * Copyright (C) 2010 Nikias Bassen <nikias@gmx.li> * - * Licensed under the GNU General Public License Version 2 + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, + * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more profile. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - * USA + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicescreenshot" + #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <getopt.h> #include <errno.h> #include <time.h> +#include <unistd.h> +#ifndef WIN32 +#include <signal.h> +#endif #include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/lockdown.h> #include <libimobiledevice/screenshotr.h> -void print_usage(int argc, char **argv); +static void get_image_filename(char *imgdata, char **filename) +{ + // If the provided filename already has an extension, use it as is. + if (*filename) { + char *last_dot = strrchr(*filename, '.'); + if (last_dot && !strchr(last_dot, '/')) { + return; + } + } + + // Find the appropriate file extension for the filename. + const char *fileext = NULL; + if (memcmp(imgdata, "\x89PNG", 4) == 0) { + fileext = ".png"; + } else if (memcmp(imgdata, "MM\x00*", 4) == 0) { + fileext = ".tiff"; + } else { + printf("WARNING: screenshot data has unexpected image format.\n"); + fileext = ".dat"; + } + + // If a filename without an extension is provided, append the extension. + // Otherwise, generate a filename based on the current time. + char *basename = NULL; + if (*filename) { + basename = (char*)malloc(strlen(*filename) + 1); + strcpy(basename, *filename); + free(*filename); + *filename = NULL; + } else { + time_t now = time(NULL); + basename = (char*)malloc(32); + strftime(basename, 31, "screenshot-%Y-%m-%d-%H-%M-%S", gmtime(&now)); + } + + // Ensure the filename is unique on disk. + char *unique_filename = (char*)malloc(strlen(basename) + strlen(fileext) + 7); + sprintf(unique_filename, "%s%s", basename, fileext); + int i; + for (i = 2; i < (1 << 16); i++) { + if (access(unique_filename, F_OK) == -1) { + *filename = unique_filename; + break; + } + sprintf(unique_filename, "%s-%d%s", basename, i, fileext); + } + if (!*filename) { + free(unique_filename); + } + free(basename); +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [FILE]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Gets a screenshot from a connected device.\n" + "\n" + "The image is in PNG format for iOS 9+ and otherwise in TIFF format.\n" + "The screenshot is saved as an image with the given FILE name.\n" + "If FILE has no extension, FILE will be a prefix of the saved filename.\n" + "If FILE is not specified, \"screenshot-DATE\", will be used as a prefix\n" + "of the filename, e.g.:\n" + " ./screenshot-2013-12-31-23-59-59.tiff\n" + "\n" + "NOTE: A mounted developer disk image is required on the device, otherwise\n" + "the screenshotr service is not available.\n" + "\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} int main(int argc, char **argv) { idevice_t device = NULL; lockdownd_client_t lckd = NULL; + lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; screenshotr_client_t shotr = NULL; - uint16_t port = 0; + lockdownd_service_descriptor_t service = NULL; int result = -1; - int i; - char *uuid = NULL; + const char *udid = NULL; + int use_network = 0; + char *filename = NULL; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + + /* parse cmdline arguments */ + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { - i++; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; } - uuid = strdup(argv[i]); - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); return 0; - } - else { - print_usage(argc, argv); + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); return 0; + default: + print_usage(argc, argv, 1); + return 2; } } + argc -= optind; + argv += optind; - if (IDEVICE_E_SUCCESS != idevice_new(&device, uuid)) { - printf("No device found, is it plugged in?\n"); - if (uuid) { - free(uuid); + if (argv[0]) { + filename = strdup(argv[0]); + } + + if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); } return -1; } - if (uuid) { - free(uuid); - } - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lckd, NULL)) { + if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lckd, TOOL_NAME))) { idevice_free(device); - printf("Exiting.\n"); + printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret); return -1; } - lockdownd_start_service(lckd, "com.apple.mobile.screenshotr", &port); + lockdownd_error_t lerr = lockdownd_start_service(lckd, SCREENSHOTR_SERVICE_NAME, &service); lockdownd_client_free(lckd); - if (port > 0) { - if (screenshotr_client_new(device, port, &shotr) != SCREENSHOTR_E_SUCCESS) { + if (lerr == LOCKDOWN_E_SUCCESS) { + if (screenshotr_client_new(device, service, &shotr) != SCREENSHOTR_E_SUCCESS) { printf("Could not connect to screenshotr!\n"); } else { char *imgdata = NULL; - char filename[36]; uint64_t imgsize = 0; - time_t now = time(NULL); - strftime(filename, 36, "screenshot-%Y-%m-%d-%H-%M-%S.tiff", gmtime(&now)); if (screenshotr_take_screenshot(shotr, &imgdata, &imgsize) == SCREENSHOTR_E_SUCCESS) { - FILE *f = fopen(filename, "w"); - if (f) { - if (fwrite(imgdata, 1, (size_t)imgsize, f) == (size_t)imgsize) { - printf("Screenshot saved to %s\n", filename); - result = 0; + get_image_filename(imgdata, &filename); + if (!filename) { + printf("FATAL: Could not find a unique filename!\n"); + } else { + FILE *f = fopen(filename, "wb"); + if (f) { + if (fwrite(imgdata, 1, (size_t)imgsize, f) == (size_t)imgsize) { + printf("Screenshot saved to %s\n", filename); + result = 0; + } else { + printf("Could not save screenshot to file %s!\n", filename); + } + fclose(f); } else { - printf("Could not save screenshot to file %s!\n", filename); + printf("Could not open %s for writing: %s\n", filename, strerror(errno)); } - fclose(f); - } else { - printf("Could not open %s for writing: %s\n", filename, strerror(errno)); } } else { printf("Could not get screenshot!\n"); @@ -115,25 +229,14 @@ int main(int argc, char **argv) screenshotr_client_free(shotr); } } else { - printf("Could not start screenshotr service! Remember that you have to mount the Developer disk image on your device if you want to use the screenshotr service.\n"); + printf("Could not start screenshotr service: %s\nRemember that you have to mount the Developer disk image on your device if you want to use the screenshotr service.\n", lockdownd_strerror(lerr)); } + + if (service) + lockdownd_service_descriptor_free(service); + idevice_free(device); - - return result; -} + free(filename); -void print_usage(int argc, char **argv) -{ - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); - printf("Gets a screenshot from the connected iPhone/iPod Touch.\n"); - printf("The screenshot is saved as a TIFF image in the current directory.\n"); - printf("NOTE: A mounted developer disk image is required on the device, otherwise\n"); - printf("the screenshotr service is not available.\n\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + return result; } diff --git a/tools/idevicesetlocation.c b/tools/idevicesetlocation.c new file mode 100644 index 0000000..69fbaf5 --- /dev/null +++ b/tools/idevicesetlocation.c @@ -0,0 +1,192 @@ +/* + * idevicesetlocation.c + * Simulate location on iOS device with mounted developer disk image + * + * Copyright (c) 2016-2020 Nikias Bassen, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define TOOL_NAME "idevicesetlocation" + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include <libimobiledevice/libimobiledevice.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/service.h> + +#include <endianness.h> + +#define DT_SIMULATELOCATION_SERVICE "com.apple.dt.simulatelocation" + +enum { + SET_LOCATION = 0, + RESET_LOCATION = 1 +}; + +static void print_usage(int argc, char **argv, int is_error) +{ + char *bname = strrchr(argv[0], '/'); + bname = (bname) ? bname + 1 : argv[0]; + + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] -- <LAT> <LONG>\n", bname); + fprintf(is_error ? stderr : stdout, " %s [OPTIONS] reset\n", bname); + fprintf(is_error ? stderr : stdout, + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help prints usage information\n" + " -v, --version prints version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +int main(int argc, char **argv) +{ + int c = 0; + const struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "debug", no_argument, NULL, 'd' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + uint32_t mode = 0; + const char *udid = NULL; + int use_network = 0; + + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + + argc -= optind; + argv += optind; + + if ((argc > 2) || (argc < 1)) { + print_usage(argc+optind, argv-optind, 1); + return -1; + } + + if (argc == 2) { + mode = SET_LOCATION; + } else if (argc == 1) { + if (strcmp(argv[0], "reset") == 0) { + mode = RESET_LOCATION; + } else { + print_usage(argc+optind, argv-optind, 1); + return -1; + } + } + + idevice_t device = NULL; + + if (idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) { + if (udid) { + printf("ERROR: Device %s not found!\n", udid); + } else { + printf("ERROR: No device found!\n"); + } + return -1; + } + + lockdownd_client_t lockdown; + lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME); + + lockdownd_service_descriptor_t svc = NULL; + lockdownd_error_t lerr = lockdownd_start_service(lockdown, DT_SIMULATELOCATION_SERVICE, &svc); + if (lerr != LOCKDOWN_E_SUCCESS) { + lockdownd_client_free(lockdown); + idevice_free(device); + printf("ERROR: Could not start the simulatelocation service: %s\nMake sure a developer disk image is mounted!\n", lockdownd_strerror(lerr)); + return -1; + } + lockdownd_client_free(lockdown); + + service_client_t service = NULL; + + service_error_t serr = service_client_new(device, svc, &service); + + lockdownd_service_descriptor_free(svc); + + if (serr != SERVICE_E_SUCCESS) { + lockdownd_client_free(lockdown); + idevice_free(device); + printf("ERROR: Could not connect to simulatelocation service (%d)\n", serr); + return -1; + } + + uint32_t l; + uint32_t s = 0; + + l = htobe32(mode); + service_send(service, (const char*)&l, 4, &s); + if (mode == SET_LOCATION) { + int len = 4 + strlen(argv[0]) + 4 + strlen(argv[1]); + char *buf = malloc(len); + uint32_t latlen; + latlen = strlen(argv[0]); + l = htobe32(latlen); + memcpy(buf, &l, 4); + memcpy(buf+4, argv[0], latlen); + uint32_t longlen = strlen(argv[1]); + l = htobe32(longlen); + memcpy(buf+4+latlen, &l, 4); + memcpy(buf+4+latlen+4, argv[1], longlen); + + s = 0; + service_send(service, buf, len, &s); + } + + idevice_free(device); + + return 0; +} diff --git a/tools/idevicesyslog.c b/tools/idevicesyslog.c index f2b3a2f..a0e641d 100644 --- a/tools/idevicesyslog.c +++ b/tools/idevicesyslog.c @@ -2,169 +2,783 @@ * idevicesyslog.c * Relay the syslog of a device to stdout * + * Copyright (c) 2010-2020 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define TOOL_NAME "idevicesyslog" + #include <stdio.h> #include <string.h> #include <errno.h> #include <signal.h> #include <stdlib.h> -#include <glib.h> +#include <unistd.h> +#include <getopt.h> + +#ifdef WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#endif #include <libimobiledevice/libimobiledevice.h> -#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/syslog_relay.h> +#include <libimobiledevice-glue/termcolors.h> static int quit_flag = 0; +static int exit_on_disconnect = 0; +static int show_device_name = 0; + +static char* udid = NULL; +static char** proc_filters = NULL; +static int num_proc_filters = 0; +static int proc_filter_excluding = 0; + +static int* pid_filters = NULL; +static int num_pid_filters = 0; + +static char** msg_filters = NULL; +static int num_msg_filters = 0; + +static char** trigger_filters = NULL; +static int num_trigger_filters = 0; +static char** untrigger_filters = NULL; +static int num_untrigger_filters = 0; +static int triggered = 0; + +static idevice_t device = NULL; +static syslog_relay_client_t syslog = NULL; + +static const char QUIET_FILTER[] = "CircleJoinRequested|CommCenter|HeuristicInterpreter|MobileMail|PowerUIAgent|ProtectedCloudKeySyncing|SpringBoard|UserEventAgent|WirelessRadioManagerd|accessoryd|accountsd|aggregated|analyticsd|appstored|apsd|assetsd|assistant_service|backboardd|biometrickitd|bluetoothd|calaccessd|callservicesd|cloudd|com.apple.Safari.SafeBrowsing.Service|contextstored|corecaptured|coreduetd|corespeechd|cdpd|dasd|dataaccessd|distnoted|dprivacyd|duetexpertd|findmydeviced|fmfd|fmflocatord|gpsd|healthd|homed|identityservicesd|imagent|itunescloudd|itunesstored|kernel|locationd|maild|mDNSResponder|mediaremoted|mediaserverd|mobileassetd|nanoregistryd|nanotimekitcompaniond|navd|nsurlsessiond|passd|pasted|photoanalysisd|powerd|powerlogHelperd|ptpd|rapportd|remindd|routined|runningboardd|searchd|sharingd|suggestd|symptomsd|timed|thermalmonitord|useractivityd|vmd|wifid|wirelessproxd"; + +static int use_network = 0; + +static char *line = NULL; +static int line_buffer_size = 0; +static int lp = 0; + +static void add_filter(const char* filterstr) +{ + int filter_len = strlen(filterstr); + const char* start = filterstr; + const char* end = filterstr + filter_len; + const char* p = start; + while (p <= end) { + if ((*p == '|') || (*p == '\0')) { + if (p-start > 0) { + char* procn = malloc(p-start+1); + if (!procn) { + fprintf(stderr, "ERROR: malloc() failed\n"); + exit(EXIT_FAILURE); + } + memcpy(procn, start, p-start); + procn[p-start] = '\0'; + char* endp = NULL; + int pid_value = (int)strtol(procn, &endp, 10); + if (!endp || *endp == 0) { + int *new_pid_filters = realloc(pid_filters, sizeof(int) * (num_pid_filters+1)); + if (!new_pid_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + pid_filters = new_pid_filters; + pid_filters[num_pid_filters] = pid_value; + num_pid_filters++; + } else { + char **new_proc_filters = realloc(proc_filters, sizeof(char*) * (num_proc_filters+1)); + if (!new_proc_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + proc_filters = new_proc_filters; + proc_filters[num_proc_filters] = procn; + num_proc_filters++; + } + } + start = p+1; + } + p++; + } +} + +static int find_char(char c, char** p, const char* end) +{ + while ((**p != c) && (*p < end)) { + (*p)++; + } + return (**p == c); +} + +static void stop_logging(void); + +static void syslog_callback(char c, void *user_data) +{ + if (lp >= line_buffer_size-1) { + line_buffer_size+=1024; + char* _line = realloc(line, line_buffer_size); + if (!_line) { + fprintf(stderr, "ERROR: realloc failed\n"); + exit(EXIT_FAILURE); + } + line = _line; + } + line[lp++] = c; + if (c == '\0') { + int shall_print = 0; + int trigger_off = 0; + lp--; + char* linep = &line[0]; + do { + if (lp < 16) { + shall_print = 1; + cprintf(FG_WHITE); + break; + } + + if (line[3] == ' ' && line[6] == ' ' && line[15] == ' ') { + char* end = &line[lp]; + char* p = &line[16]; + + /* device name */ + char* device_name_start = p; + char* device_name_end = p; + if (!find_char(' ', &p, end)) break; + device_name_end = p; + p++; + + /* check if we have any triggers/untriggers */ + if (num_untrigger_filters > 0 && triggered) { + int found = 0; + int i; + for (i = 0; i < num_untrigger_filters; i++) { + if (strstr(device_name_end+1, untrigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 1; + } else { + shall_print = 1; + trigger_off = 1; + } + } else if (num_trigger_filters > 0 && !triggered) { + int found = 0; + int i; + for (i = 0; i < num_trigger_filters; i++) { + if (strstr(device_name_end+1, trigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 0; + break; + } + triggered = 1; + shall_print = 1; + } else if (num_trigger_filters == 0 && num_untrigger_filters > 0 && !triggered) { + shall_print = 0; + quit_flag++; + break; + } + + /* check message filters */ + if (num_msg_filters > 0) { + int found = 0; + int i; + for (i = 0; i < num_msg_filters; i++) { + if (strstr(device_name_end+1, msg_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 0; + break; + } + shall_print = 1; + } + + /* process name */ + char* proc_name_start = p; + char* proc_name_end = p; + if (!find_char('[', &p, end)) break; + char* process_name_start = proc_name_start; + char* process_name_end = p; + char* pid_start = p+1; + char* pp = process_name_start; + if (find_char('(', &pp, p)) { + process_name_end = pp; + } + if (!find_char(']', &p, end)) break; + p++; + if (*p != ' ') break; + proc_name_end = p; + p++; + + int proc_matched = 0; + if (num_pid_filters > 0) { + char* endp = NULL; + int pid_value = (int)strtol(pid_start, &endp, 10); + if (endp && (*endp == ']')) { + int found = proc_filter_excluding; + int i = 0; + for (i = 0; i < num_pid_filters; i++) { + if (pid_value == pid_filters[i]) { + found = !proc_filter_excluding; + break; + } + } + if (found) { + proc_matched = 1; + } + } + } + if (num_proc_filters > 0 && !proc_matched) { + int found = proc_filter_excluding; + int i = 0; + for (i = 0; i < num_proc_filters; i++) { + if (!proc_filters[i]) continue; + if (strncmp(proc_filters[i], process_name_start, process_name_end-process_name_start) == 0) { + found = !proc_filter_excluding; + break; + } + } + if (found) { + proc_matched = 1; + } + } + if (proc_matched) { + shall_print = 1; + } else { + if (num_pid_filters > 0 || num_proc_filters > 0) { + shall_print = 0; + break; + } + } + + /* log level */ + char* level_start = p; + char* level_end = p; + const char* level_color = NULL; + if (!strncmp(p, "<Notice>:", 9)) { + level_end += 9; + level_color = FG_GREEN; + } else if (!strncmp(p, "<Error>:", 8)) { + level_end += 8; + level_color = FG_RED; + } else if (!strncmp(p, "<Warning>:", 10)) { + level_end += 10; + level_color = FG_YELLOW; + } else if (!strncmp(p, "<Debug>:", 8)) { + level_end += 8; + level_color = FG_MAGENTA; + } else { + level_color = FG_WHITE; + } + + /* write date and time */ + cprintf(FG_LIGHT_GRAY); + fwrite(line, 1, 16, stdout); + + if (show_device_name) { + /* write device name */ + cprintf(FG_DARK_YELLOW); + fwrite(device_name_start, 1, device_name_end-device_name_start+1, stdout); + cprintf(COLOR_RESET); + } + + /* write process name */ + cprintf(FG_BRIGHT_CYAN); + fwrite(process_name_start, 1, process_name_end-process_name_start, stdout); + cprintf(FG_CYAN); + fwrite(process_name_end, 1, proc_name_end-process_name_end+1, stdout); + + /* write log level */ + cprintf(level_color); + if (level_end > level_start) { + fwrite(level_start, 1, level_end-level_start, stdout); + p = level_end; + } + + lp -= p - linep; + linep = p; + + cprintf(FG_WHITE); + + } else { + shall_print = 1; + cprintf(FG_WHITE); + } + } while (0); + + if ((num_msg_filters == 0 && num_proc_filters == 0 && num_pid_filters == 0 && num_trigger_filters == 0 && num_untrigger_filters == 0) || shall_print) { + fwrite(linep, 1, lp, stdout); + cprintf(COLOR_RESET); + fflush(stdout); + if (trigger_off) { + triggered = 0; + } + } + line[0] = '\0'; + lp = 0; + return; + } +} + +static int start_logging(void) +{ + idevice_error_t ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + fprintf(stderr, "Device with udid %s not found!?\n", udid); + return -1; + } + + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not connect to lockdownd: %d\n", lerr); + idevice_free(device); + device = NULL; + return -1; + } + + /* start syslog_relay service */ + lockdownd_service_descriptor_t svc = NULL; + lerr = lockdownd_start_service(lockdown, SYSLOG_RELAY_SERVICE_NAME, &svc); + if (lerr == LOCKDOWN_E_PASSWORD_PROTECTED) { + fprintf(stderr, "*** Device is passcode protected, enter passcode on the device to continue ***\n"); + while (!quit_flag) { + lerr = lockdownd_start_service(lockdown, SYSLOG_RELAY_SERVICE_NAME, &svc); + if (lerr != LOCKDOWN_E_PASSWORD_PROTECTED) { + break; + } + sleep(1); + } + } + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not connect to lockdownd: %d\n", lerr); + idevice_free(device); + device = NULL; + return -1; + } + lockdownd_client_free(lockdown); + + /* connect to syslog_relay service */ + syslog_relay_error_t serr = SYSLOG_RELAY_E_UNKNOWN_ERROR; + serr = syslog_relay_client_new(device, svc, &syslog); + lockdownd_service_descriptor_free(svc); + if (serr != SYSLOG_RELAY_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not start service com.apple.syslog_relay.\n"); + idevice_free(device); + device = NULL; + return -1; + } + + /* start capturing syslog */ + serr = syslog_relay_start_capture_raw(syslog, syslog_callback, NULL); + if (serr != SYSLOG_RELAY_E_SUCCESS) { + fprintf(stderr, "ERROR: Unable tot start capturing syslog.\n"); + syslog_relay_client_free(syslog); + syslog = NULL; + idevice_free(device); + device = NULL; + return -1; + } + + fprintf(stdout, "[connected:%s]\n", udid); + fflush(stdout); + + return 0; +} + +static void stop_logging(void) +{ + fflush(stdout); -void print_usage(int argc, char **argv); + if (syslog) { + syslog_relay_client_free(syslog); + syslog = NULL; + } + + if (device) { + idevice_free(device); + device = NULL; + } +} + +static void device_event_cb(const idevice_event_t* event, void* userdata) +{ + if (use_network && event->conn_type != CONNECTION_NETWORK) { + return; + } + if (!use_network && event->conn_type != CONNECTION_USBMUXD) { + return; + } + if (event->event == IDEVICE_DEVICE_ADD) { + if (!syslog) { + if (!udid) { + udid = strdup(event->udid); + } + if (strcmp(udid, event->udid) == 0) { + if (start_logging() != 0) { + fprintf(stderr, "Could not start logger for udid %s\n", udid); + } + } + } + } else if (event->event == IDEVICE_DEVICE_REMOVE) { + if (syslog && (strcmp(udid, event->udid) == 0)) { + stop_logging(); + fprintf(stdout, "[disconnected:%s]\n", udid); + if (exit_on_disconnect) { + quit_flag++; + } + } + } +} /** * signal handler function for cleaning up properly */ static void clean_exit(int sig) { - fprintf(stderr, "Exiting...\n"); + fprintf(stderr, "\nExiting...\n"); quit_flag++; } +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Relay syslog of a connected device.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -x, --exit exit when device disconnects\n" + " -h, --help prints usage information\n" + " -d, --debug enable communication debugging\n" + " -v, --version prints version information\n" + " --no-colors disable colored output\n" + " -o, --output FILE write to FILE instead of stdout\n" + " (existing FILE will be overwritten)\n" + " --colors force writing colored output, e.g. for --output\n" + "\n" + "FILTER OPTIONS:\n" + " -m, --match STRING only print messages that contain STRING\n" + " -t, --trigger STRING start logging when matching STRING\n" + " -T, --untrigger STRING stop logging when matching STRING\n" + " -p, --process PROCESS only print messages from matching process(es)\n" + " -e, --exclude PROCESS print all messages except matching process(es)\n" + " PROCESS is a process name or multiple process names\n" + " separated by \"|\".\n" + " -q, --quiet set a filter to exclude common noisy processes\n" + " --quiet-list prints the list of processes for --quiet and exits\n" + " -k, --kernel only print kernel messages\n" + " -K, --no-kernel suppress kernel messages\n" + "\n" + "For filter examples consult idevicesyslog(1) man page.\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + int main(int argc, char *argv[]) { - lockdownd_client_t client = NULL; - idevice_t phone = NULL; - idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; - int i; - char uuid[41]; - uint16_t port = 0; - uuid[0] = 0; + int include_filter = 0; + int exclude_filter = 0; + int include_kernel = 0; + int exclude_kernel = 0; + int force_colors = 0; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "exit", no_argument, NULL, 'x' }, + { "trigger", required_argument, NULL, 't' }, + { "untrigger", required_argument, NULL, 'T' }, + { "match", required_argument, NULL, 'm' }, + { "process", required_argument, NULL, 'p' }, + { "exclude", required_argument, NULL, 'e' }, + { "quiet", no_argument, NULL, 'q' }, + { "kernel", no_argument, NULL, 'k' }, + { "no-kernel", no_argument, NULL, 'K' }, + { "quiet-list", no_argument, NULL, 1 }, + { "no-colors", no_argument, NULL, 2 }, + { "colors", no_argument, NULL, 3 }, + { "output", required_argument, NULL, 'o' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; signal(SIGINT, clean_exit); - signal(SIGQUIT, clean_exit); signal(SIGTERM, clean_exit); +#ifndef WIN32 + signal(SIGQUIT, clean_exit); signal(SIGPIPE, SIG_IGN); +#endif - /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + while ((c = getopt_long(argc, argv, "dhu:nxt:T:m:e:p:qkKo:v", longopts, NULL)) != -1) { + switch (c) { + case 'd': idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { - i++; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; - } - strcpy(uuid, argv[i]); - continue; - } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + free(udid); + udid = strdup(optarg); + break; + case 'n': + use_network = 1; + break; + case 'q': + exclude_filter++; + add_filter(QUIET_FILTER); + break; + case 'p': + case 'e': + if (c == 'p') { + include_filter++; + } else if (c == 'e') { + exclude_filter++; + } + if (!*optarg) { + fprintf(stderr, "ERROR: filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + add_filter(optarg); + break; + case 'm': + if (!*optarg) { + fprintf(stderr, "ERROR: message filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_msg_filters = realloc(msg_filters, sizeof(char*) * (num_msg_filters+1)); + if (!new_msg_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + msg_filters = new_msg_filters; + msg_filters[num_msg_filters] = strdup(optarg); + num_msg_filters++; + } + break; + case 't': + if (!*optarg) { + fprintf(stderr, "ERROR: trigger filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_trigger_filters = realloc(trigger_filters, sizeof(char*) * (num_trigger_filters+1)); + if (!new_trigger_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + trigger_filters = new_trigger_filters; + trigger_filters[num_trigger_filters] = strdup(optarg); + num_trigger_filters++; + } + break; + case 'T': + if (!*optarg) { + fprintf(stderr, "ERROR: untrigger filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_untrigger_filters = realloc(untrigger_filters, sizeof(char*) * (num_untrigger_filters+1)); + if (!new_untrigger_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + untrigger_filters = new_untrigger_filters; + untrigger_filters[num_untrigger_filters] = strdup(optarg); + num_untrigger_filters++; + } + break; + case 'k': + include_kernel++; + break; + case 'K': + exclude_kernel++; + break; + case 'x': + exit_on_disconnect = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 1: { + printf("%s\n", QUIET_FILTER); return 0; } - else { - print_usage(argc, argv); + case 2: + term_colors_set_enabled(0); + break; + case 3: + force_colors = 1; + break; + case 'o': + if (!*optarg) { + fprintf(stderr, "ERROR: --output option requires an argument!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + if (freopen(optarg, "w", stdout) == NULL) { + fprintf(stderr, "ERROR: Failed to open output file '%s' for writing: %s\n", optarg, strerror(errno)); + return 1; + } + term_colors_set_enabled(0); + } + break; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); return 0; + default: + print_usage(argc, argv, 1); + return 2; } } - if (uuid[0] != 0) { - ret = idevice_new(&phone, uuid); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found with uuid %s, is it plugged in?\n", uuid); - return -1; - } + if (force_colors) { + term_colors_set_enabled(1); } - else - { - ret = idevice_new(&phone, NULL); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found, is it plugged in?\n"); - return -1; - } + + if (include_kernel > 0 && exclude_kernel > 0) { + fprintf(stderr, "ERROR: -k and -K cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; } - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicesyslog")) { - idevice_free(phone); - return -1; + if (include_filter > 0 && exclude_filter > 0) { + fprintf(stderr, "ERROR: -p and -e/-q cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; + } + if (include_filter > 0 && exclude_kernel > 0) { + fprintf(stderr, "ERROR: -p and -K cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; } - /* start syslog_relay service and retrieve port */ - ret = lockdownd_start_service(client, "com.apple.syslog_relay", &port); - if ((ret == LOCKDOWN_E_SUCCESS) && port) { - lockdownd_client_free(client); - - /* connect to socket relay messages */ - idevice_connection_t conn = NULL; - if ((idevice_connect(phone, port, &conn) != IDEVICE_E_SUCCESS) || !conn) { - printf("ERROR: Could not open usbmux connection.\n"); - } else { - while (!quit_flag) { - char *receive = NULL; - uint32_t datalen = 0, bytes = 0, recv_bytes = 0; - - ret = idevice_connection_receive(conn, (char *) &datalen, sizeof(datalen), &bytes); - if (ret < 0) { - fprintf(stderr, "Error receiving data. Exiting...\n"); - break; + if (exclude_filter > 0) { + proc_filter_excluding = 1; + if (include_kernel) { + int i = 0; + for (i = 0; i < num_proc_filters; i++) { + if (!strcmp(proc_filters[i], "kernel")) { + free(proc_filters[i]); + proc_filters[i] = NULL; } + } + } else if (exclude_kernel) { + add_filter("kernel"); + } + } else { + if (include_kernel) { + add_filter("kernel"); + } else if (exclude_kernel) { + proc_filter_excluding = 1; + add_filter("kernel"); + } + } - datalen = GUINT32_FROM_BE(datalen); + if (num_untrigger_filters > 0 && num_trigger_filters == 0) { + triggered = 1; + } - if (datalen == 0) - continue; + argc -= optind; + argv += optind; - recv_bytes += bytes; - receive = (char *) malloc(sizeof(char) * datalen); + int num = 0; + idevice_info_t *devices = NULL; + idevice_get_device_list_extended(&devices, &num); + idevice_device_list_extended_free(devices); + if (num == 0) { + if (!udid) { + fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n"); + return -1; + } - while (!quit_flag && (recv_bytes <= datalen)) { - ret = idevice_connection_receive(conn, receive, datalen, &bytes); + fprintf(stderr, "Waiting for device with UDID %s to become available...\n", udid); + } - if (bytes == 0) - break; + line_buffer_size = 1024; + line = malloc(line_buffer_size); - recv_bytes += bytes; + idevice_subscription_context_t context = NULL; + idevice_events_subscribe(&context, device_event_cb, NULL); - fwrite(receive, sizeof(char), bytes, stdout); - } + while (!quit_flag) { + sleep(1); + } + idevice_events_unsubscribe(context); + stop_logging(); - free(receive); - } + if (num_proc_filters > 0) { + int i; + for (i = 0; i < num_proc_filters; i++) { + free(proc_filters[i]); } - idevice_disconnect(conn); - } else { - printf("ERROR: Could not start service com.apple.syslog_relay.\n"); + free(proc_filters); + } + if (num_pid_filters > 0) { + free(pid_filters); + } + if (num_msg_filters > 0) { + int i; + for (i = 0; i < num_msg_filters; i++) { + free(msg_filters[i]); + } + free(msg_filters); + } + if (num_trigger_filters > 0) { + int i; + for (i = 0; i < num_trigger_filters; i++) { + free(trigger_filters[i]); + } + free(trigger_filters); + } + if (num_untrigger_filters > 0) { + int i; + for (i = 0; i < num_untrigger_filters; i++) { + free(untrigger_filters[i]); + } + free(untrigger_filters); } - idevice_free(phone); + free(line); - return 0; -} + free(udid); -void print_usage(int argc, char **argv) -{ - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); - printf("Relay syslog of a connected iPhone/iPod Touch.\n\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + return 0; } - |