diff options
-rw-r--r-- | .github/workflows/build.yml | 53 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | 3rd_party/libsrp6a-sha512/t_math.c | 6 | ||||
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | common/Makefile.am | 2 | ||||
-rw-r--r-- | common/userpref.c | 1 | ||||
-rw-r--r-- | configure.ac | 8 | ||||
-rw-r--r-- | cython/mobile_image_mounter.pxi | 43 | ||||
-rw-r--r-- | docs/Makefile.am | 3 | ||||
-rw-r--r-- | docs/afcclient.1 | 8 | ||||
-rw-r--r-- | docs/ideviceimagemounter.1 | 34 | ||||
-rw-r--r-- | include/libimobiledevice/mobile_image_mounter.h | 111 | ||||
-rw-r--r-- | src/mobile_image_mounter.c | 274 | ||||
-rw-r--r-- | tools/Makefile.am | 8 | ||||
-rw-r--r-- | tools/afcclient.c | 642 | ||||
-rw-r--r-- | tools/idevicebackup.c | 129 | ||||
-rw-r--r-- | tools/ideviceimagemounter.c | 399 |
18 files changed, 1358 insertions, 371 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b740952..0298939 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,26 +17,33 @@ jobs: run: | echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV - name: fetch libplist - uses: dawidd6/action-download-artifact@v2 + 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@v2 + 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@v2 + 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 @@ -45,7 +52,7 @@ jobs: done sudo cp -r extract/* / sudo ldconfig - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: autogen @@ -60,7 +67,7 @@ jobs: DESTDIR=`pwd`/dest make install tar -C dest -cf libimobiledevice.tar usr - name: publish artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: libimobiledevice-latest_${{env.target_triplet}} path: libimobiledevice.tar @@ -74,29 +81,36 @@ jobs: else brew install libtool autoconf automake pkgconfig fi - pip3 install cython + pip3 install --break-system-packages cython shell: bash - name: fetch libplist - uses: dawidd6/action-download-artifact@v2 + 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@v2 + 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@v2 + 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 @@ -104,7 +118,7 @@ jobs: tar -C extract -xvf $I done sudo cp -r extract/* / - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: install additional requirements run: | mkdir -p lib @@ -156,7 +170,7 @@ jobs: DESTDIR=`pwd`/dest make install tar -C dest -cf libimobiledevice.tar usr - name: publish artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: libimobiledevice-latest_macOS path: libimobiledevice.tar @@ -193,26 +207,33 @@ jobs: echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV git config --global core.autocrlf false - name: fetch libplist - uses: dawidd6/action-download-artifact@v2 + 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@v2 + 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@v2 + 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 @@ -220,7 +241,7 @@ jobs: tar -C extract -xvf $I done cp -r extract/* / - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: autogen run: ./autogen.sh CC=gcc CXX=g++ --enable-debug - name: make @@ -233,7 +254,7 @@ jobs: DESTDIR=`pwd`/dest make install tar -C dest -cf libimobiledevice.tar ${{ env.dest }} - name: publish artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: libimobiledevice-latest_${{ matrix.arch }}-${{ env.dest }} path: libimobiledevice.tar @@ -37,6 +37,7 @@ docs/html libimobiledevice-1.0.pc tools/.libs/* tools/idevice* +tools/afcclient !tools/idevice*.[ch] cython/.libs/* cython/*.c diff --git a/3rd_party/libsrp6a-sha512/t_math.c b/3rd_party/libsrp6a-sha512/t_math.c index 037650e..dac19ec 100644 --- a/3rd_party/libsrp6a-sha512/t_math.c +++ b/3rd_party/libsrp6a-sha512/t_math.c @@ -720,7 +720,11 @@ BigIntegerModExp(BigInteger r, BigInteger b, BigInteger e, BigInteger m, BigInte else if(a == NULL) { BN_mod_exp(r, b, e, m, c); } -#if OPENSSL_VERSION_NUMBER >= 0x00906000 +/* + * 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); } diff --git a/Makefile.am b/Makefile.am index b11de57..352b28f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,6 +8,7 @@ EXTRA_DIST = \ git-version-gen 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 @@ -66,9 +66,13 @@ sudo apt-get install \ 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 @@ -194,4 +198,4 @@ 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: 2023-12-30 +README Updated on: 2024-06-27 diff --git a/common/Makefile.am b/common/Makefile.am index bd09bad..ba7ed9c 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -7,12 +7,14 @@ AM_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 diff --git a/common/userpref.c b/common/userpref.c index b64c703..48bcfcb 100644 --- a/common/userpref.c +++ b/common/userpref.c @@ -627,7 +627,6 @@ userpref_error_t pair_record_generate_keys_and_certs(plist_t pair_record, key_da } } - X509V3_EXT_cleanup(); X509_free(dev_cert); EVP_PKEY_free(pubkey); diff --git a/configure.ac b/configure.ac index 3c12010..c67e896 100644 --- a/configure.ac +++ b/configure.ac @@ -27,7 +27,8 @@ fi dnl Minimum package versions LIBUSBMUXD_VERSION=2.0.2 LIBPLIST_VERSION=2.3.0 -LIMD_GLUE_VERSION=1.0.0 +LIMD_GLUE_VERSION=1.3.0 +LIBTATSU_VERSION=1.0.3 AC_SUBST(LIBUSBMUXD_VERSION) AC_SUBST(LIBPLIST_VERSION) @@ -43,6 +44,7 @@ LT_INIT 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)])], @@ -132,9 +134,9 @@ AC_ARG_WITH([cython], [build_cython=false], [build_cython=true]) if test "$build_cython" = "true"; then - AC_PROG_CYTHON([0.17.0]) + AC_PROG_CYTHON([3.0.0]) if [test "x$CYTHON" != "xfalse"]; then - AM_PATH_PYTHON([2.3], [ + AM_PATH_PYTHON([3.0], [ CYTHON_PYTHON AS_COMPILER_FLAG([-Wno-cast-function-type -Werror], [ CYTHON_CFLAGS+=" -Wno-cast-function-type" diff --git a/cython/mobile_image_mounter.pxi b/cython/mobile_image_mounter.pxi index a23a59b..d9d40d5 100644 --- a/cython/mobile_image_mounter.pxi +++ b/cython/mobile_image_mounter.pxi @@ -13,7 +13,9 @@ cdef extern from "libimobiledevice/mobile_image_mounter.h": 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(mobile_image_mounter_client_t client, char *image_path, char *image_signature, uint16_t signature_length, 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): @@ -57,11 +59,39 @@ cdef class MobileImageMounterClient(PropertyListService): if c_node != NULL: plist.plist_free(c_node) - cpdef plist.Node mount_image(self, bytes image_path, bytes image_signature, bytes image_type): + 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 - err = mobile_image_mounter_mount_image(self._c_client, image_path, image_signature, len(image_signature), + 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: @@ -72,6 +102,13 @@ cdef class MobileImageMounterClient(PropertyListService): 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) diff --git a/docs/Makefile.am b/docs/Makefile.am index 4a4c56f..8156d4f 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -18,7 +18,8 @@ man_MANS = \ idevicedebug.1 \ idevicedevmodectl.1 \ idevicenotificationproxy.1 \ - idevicesetlocation.1 + idevicesetlocation.1 \ + afcclient.1 EXTRA_DIST = $(man_MANS) diff --git a/docs/afcclient.1 b/docs/afcclient.1 index ca7cb86..a4eeacb 100644 --- a/docs/afcclient.1 +++ b/docs/afcclient.1 @@ -32,16 +32,14 @@ create directory at PATH .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 PATH +.B rm [-rf] PATH remove item at PATH .TP -.B get PATH [LOCALPATH] +.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. -\f[B]WARNING\f[]: Existing files will be overwritten! .TP -.B put LOCALPATH [PATH] +.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. -\f[B]WARNING\f[]: Existing files will be overwritten! .TP .SH OPTIONS diff --git a/docs/ideviceimagemounter.1 b/docs/ideviceimagemounter.1 index 832850a..1fe7e45 100644 --- a/docs/ideviceimagemounter.1 +++ b/docs/ideviceimagemounter.1 @@ -1,13 +1,32 @@ .TH "ideviceimagemounter" 1 .SH NAME -ideviceimagemounter \- Mount disk images on the device. +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 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 @@ -20,9 +39,6 @@ connect to network device. .B \-d, \-\-debug enable communication debugging. .TP -.B \-l, \-\-list -list mount information -.TP .B \-t, \-\-imagetype NAME the image type to use, default is 'Developer' .TP @@ -34,12 +50,6 @@ prints usage information .TP .B \-v, \-\-version prints version information. -.TP -.B IMAGE_FILE -the image filename to mount -.TP -.B IMAGE_SIGNATURE_FILE -corresponding signature file for image filename .SH AUTHOR Nikias Bassen diff --git a/include/libimobiledevice/mobile_image_mounter.h b/include/libimobiledevice/mobile_image_mounter.h index d4fc3f4..76bb61a 100644 --- a/include/libimobiledevice/mobile_image_mounter.h +++ b/include/libimobiledevice/mobile_image_mounter.h @@ -42,6 +42,7 @@ typedef enum { 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; @@ -127,7 +128,7 @@ LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_lookup_im * @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 char *signature, uint16_t signature_size, mobile_image_mounter_upload_cb_t upload_cb, void* userdata); +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. @@ -138,19 +139,50 @@ LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_upload_im * @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. - * 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. */ -LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const char *signature, uint16_t signature_size, const char *image_type, plist_t *result); +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. @@ -165,6 +197,77 @@ LIBIMOBILEDEVICE_API mobile_image_mounter_error_t mobile_image_mounter_mount_ima */ 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 } #endif diff --git a/src/mobile_image_mounter.c b/src/mobile_image_mounter.c index 5df8e86..6df50c4 100644 --- a/src/mobile_image_mounter.c +++ b/src/mobile_image_mounter.c @@ -181,7 +181,7 @@ static mobile_image_mounter_error_t process_result(plist_t result, const char *e 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 char *signature, uint16_t signature_size, mobile_image_mounter_upload_cb_t upload_cb, void* userdata) +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; @@ -192,7 +192,7 @@ mobile_image_mounter_error_t mobile_image_mounter_upload_image(mobile_image_moun 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(signature, signature_size)); + 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)); @@ -241,6 +241,7 @@ mobile_image_mounter_error_t mobile_image_mounter_upload_image(mobile_image_moun 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"); @@ -260,7 +261,7 @@ leave_unlock: } -mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const char *signature, uint16_t signature_size, const char *image_type, plist_t *result) +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_type || !result) { return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; @@ -271,8 +272,11 @@ mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mount 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(signature, signature_size)); + 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); @@ -292,6 +296,56 @@ leave_unlock: return res; } +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) { @@ -324,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/tools/Makefile.am b/tools/Makefile.am index 4cac1fc..b0c2769 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -35,7 +35,7 @@ bin_PROGRAMS = \ afcclient idevicebtlogger_SOURCES = idevicebtlogger.c -iidevicebtlogger_CFLAGS = $(AM_CFLAGS) +idevicebtlogger_CFLAGS = $(AM_CFLAGS) idevicebtlogger_LDFLAGS = $(top_builddir)/common/libinternalcommon.la $(AM_LDFLAGS) idevicebtlogger_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la @@ -66,7 +66,7 @@ idevice_id_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la idevicebackup_SOURCES = idevicebackup.c idevicebackup_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) -idevicebackup_LDFLAGS = $(AM_LDFLAGS) $(ssl_lib_LIBS) $(limd_glue_LIBS) +idevicebackup_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) idevicebackup_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la idevicebackup2_SOURCES = idevicebackup2.c @@ -75,8 +75,8 @@ idevicebackup2_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) idevicebackup2_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la ideviceimagemounter_SOURCES = ideviceimagemounter.c -ideviceimagemounter_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) -ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +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 diff --git a/tools/afcclient.c b/tools/afcclient.c index 9bcd77b..8f49831 100644 --- a/tools/afcclient.c +++ b/tools/afcclient.c @@ -36,6 +36,8 @@ #include <signal.h> #include <ctype.h> #include <unistd.h> +#include <dirent.h> +#include <time.h> #ifdef WIN32 #include <windows.h> @@ -70,6 +72,7 @@ #include <plist/plist.h> #include <libimobiledevice-glue/termcolors.h> +#include <libimobiledevice-glue/utils.h> #undef st_mtime #undef st_birthtime @@ -89,6 +92,26 @@ 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], '/'); @@ -171,9 +194,9 @@ static void handle_help(afc_client_t afc, int argc, char** argv) 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 PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n"); - printf("put LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n"); - printf("\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) @@ -200,7 +223,7 @@ static int timeval_subtract(struct timeval *result, struct timeval *x, struct ti 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; + return x->tv_sec < y->tv_sec; } struct str_item { @@ -674,204 +697,499 @@ static void handle_remove(afc_client_t afc, int argc, char** argv) } } -static void handle_get(afc_client_t afc, int argc, char** argv) +static uint8_t get_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint64_t file_size, uint8_t force_overwrite) { - if (argc < 1 || argc > 2) { - printf("Error: Invalid number of arguments\n"); - return; + 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; } - char *srcpath = NULL; - char* dstpath = NULL; - if (argc == 1) { - srcpath = get_absolute_path(argv[0]); - dstpath = strdup(path_get_basename(argv[0])); - } else { - srcpath = get_absolute_path(argv[0]); - dstpath = strdup(argv[1]); + 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_get_file_info(afc, srcpath, &info); + 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); + 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); } - uint64_t fh = 0; - afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh); - if (err != AFC_E_SUCCESS) { - free(srcpath); - free(dstpath); - printf("Error: Failed to open file '%s': %s (%d)\n", argv[0], afc_strerror(err), err); + 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; } - FILE *f = fopen(dstpath, "wb"); - if (!f && errno == EISDIR) { - const char* basen = path_get_basename(argv[0]); - size_t len = strlen(dstpath) + 1 + strlen(basen) + 1; - char* newdst = (char*)malloc(len); - snprintf(newdst, len, "%s/%s", dstpath, basen); - f = fopen(newdst, "wb"); + 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); } - if (f) { - 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); - } - 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"); - break; +} + +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"); } - chunk += wr; + printf("Error: Failed to read from local file\n"); + succeed = 0; } - 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; + 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) { - printf("\n"); + 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; + } } - if (err != AFC_E_SUCCESS) { - printf("Error: Failed to read from file '%s': %s (%d)\n", argv[0], afc_strerror(err), err); + } + 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; } - free(buf); - fclose(f); } else { - printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno)); + return put_single_file(afc, srcpath, dstpath, force_overwrite); } - afc_file_close(afc, fh); - free(srcpath); + return 1; } -static void handle_put(afc_client_t afc, int argc, char** argv) +static void handle_put(afc_client_t afc, int argc, char **argv) { - if (argc < 1 || argc > 2) { + 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 == 1) { - dstpath = get_absolute_path(path_get_basename(argv[0])); + 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 { - dstpath = get_absolute_path(argv[1]); + printf("Error: Invalid number of arguments\n"); + return; } - - uint64_t fh = 0; - FILE *f = fopen(argv[0], "rb"); - if (f) { - afc_error_t err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh); - if (err == AFC_E_OBJECT_IS_DIR) { - const char* basen = path_get_basename(argv[0]); - size_t len = strlen(dstpath) + 1 + strlen(basen) + 1; - char* newdst = (char*)malloc(len); - snprintf(newdst, len, "%s/%s", dstpath, basen); - free(dstpath); - dstpath = get_absolute_path(newdst); - free(newdst); - err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh); - } - if (err != AFC_E_SUCCESS) { - printf("Error: Failed to open file '%s' on device: %s (%d)\n", argv[1], afc_strerror(err), err); - } else { - 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; - 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"); - } + 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; } - 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"); - 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; - } - } + p++; } - printf("\n"); - free(buf); - afc_file_close(afc, fh); + afc_dictionary_free(info); } - fclose(f); - } else { - printf("Error: Failed to open local file '%s': %s\n", argv[0], strerror(errno)); + // 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); } - free(dstpath); } static void handle_pwd(afc_client_t afc, int argc, char** argv) @@ -975,8 +1293,8 @@ static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline) } else if (*pos == '"' || *pos == '\'') { if (!qpos) { qpos = pos; - } else { - qpos = NULL; + } else { + qpos = NULL; } pos++; } else if (*pos == '\0' || (!qpos && isspace(*pos))) { @@ -1043,7 +1361,7 @@ static int process_args(afc_client_t afc, int argc, char** argv) 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); + handle_list(afc, argc-1, argv+1); } else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) { handle_rename(afc, argc-1, argv+1); @@ -1113,7 +1431,7 @@ static void start_cmdline(afc_client_t afc) break; } } - } + } } static void device_event_cb(const idevice_event_t* event, void* userdata) diff --git a/tools/idevicebackup.c b/tools/idevicebackup.c index 5694c12..c0537b8 100644 --- a/tools/idevicebackup.c +++ b/tools/idevicebackup.c @@ -32,24 +32,6 @@ #include <stdlib.h> #include <signal.h> #include <getopt.h> -#if defined(HAVE_OPENSSL) -#include <openssl/sha.h> -#if OPENSSL_VERSION_NUMBER >= 0x30000000L -#include <openssl/evp.h> -#endif -#elif defined(HAVE_GNUTLS) -#include <gcrypt.h> -#elif defined(HAVE_MBEDTLS) -#include <mbedtls/sha1.h> -#if MBEDTLS_VERSION_NUMBER < 0x03000000 -#define mbedtls_sha1 mbedtls_sha1_ret -#define mbedtls_sha1_starts mbedtls_sha1_starts_ret -#define mbedtls_sha1_update mbedtls_sha1_update_ret -#define mbedtls_sha1_finish mbedtls_sha1_finish_ret -#endif -#else -#error No supported crypto library enabled -#endif #include <unistd.h> #include <ctype.h> #include <time.h> @@ -59,6 +41,7 @@ #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> @@ -91,17 +74,6 @@ enum device_link_file_status_t { DEVICE_LINK_FILE_STATUS_LAST_HUNK }; -static void sha1_of_data(const char *input, uint32_t size, unsigned char *hash_out) -{ -#if defined(HAVE_OPENSSL) - SHA1((const unsigned char*)input, size, hash_out); -#elif defined(HAVE_GNUTLS) - gcry_md_hash_buffer(GCRY_MD_SHA1, hash_out, input, size); -#elif defined(HAVE_MBEDTLS) - mbedtls_sha1((unsigned char*)input, size, hash_out); -#endif -} - static int compare_hash(const unsigned char *hash1, const unsigned char *hash2, int hash_len) { int i; @@ -113,104 +85,49 @@ static int compare_hash(const unsigned char *hash1, const unsigned char *hash2, return 1; } -static void _sha1_update(void* context, const char* data, size_t len) -{ -#if defined(HAVE_OPENSSL) -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_DigestUpdate(context, data, len); -#else - SHA1_Update(context, data, len); -#endif -#elif defined(HAVE_GNUTLS) - gcry_md_write(context, data, len); -#elif defined(HAVE_MBEDTLS) - mbedtls_sha1_update(context, (const unsigned char*)data, len); -#endif -} - 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) { -#if defined(HAVE_OPENSSL) -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_MD_CTX* sha1 = EVP_MD_CTX_new(); - EVP_DigestInit(sha1, EVP_sha1()); - void* psha1 = sha1; -#else - SHA_CTX sha1; - SHA1_Init(&sha1); - void* psha1 = &sha1; -#endif -#elif defined(HAVE_GNUTLS) - 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); - void* psha1 = hd; -#elif defined(HAVE_MBEDTLS) - mbedtls_sha1_context sha1; - mbedtls_sha1_init(&sha1); - mbedtls_sha1_starts(&sha1); - void* psha1 = &sha1; -#endif + 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) { - _sha1_update(psha1, (const char*)buf, len); + sha1_update(&sha1, buf, len); } fclose(f); - _sha1_update(psha1, destpath, strlen(destpath)); - _sha1_update(psha1, ";", 1); + sha1_update(&sha1, destpath, strlen(destpath)); + sha1_update(&sha1, ";", 1); if (greylist == 1) { - _sha1_update(psha1, "true", 4); + sha1_update(&sha1, "true", 4); } else { - _sha1_update(psha1, "false", 5); + sha1_update(&sha1, "false", 5); } - _sha1_update(psha1, ";", 1); + sha1_update(&sha1, ";", 1); if (domain) { - _sha1_update(psha1, domain, strlen(domain)); + sha1_update(&sha1, domain, strlen(domain)); } else { - _sha1_update(psha1, "(null)", 6); + sha1_update(&sha1, "(null)", 6); } - _sha1_update(psha1, ";", 1); + sha1_update(&sha1, ";", 1); if (appid) { - _sha1_update(psha1, appid, strlen(appid)); + sha1_update(&sha1, appid, strlen(appid)); } else { - _sha1_update(psha1, "(null)", 6); + sha1_update(&sha1, "(null)", 6); } - _sha1_update(psha1, ";", 1); + sha1_update(&sha1, ";", 1); if (version) { - _sha1_update(psha1, version, strlen(version)); + sha1_update(&sha1, version, strlen(version)); } else { - _sha1_update(psha1, "(null)", 6); + sha1_update(&sha1, "(null)", 6); } -#if defined(HAVE_OPENSSL) -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_DigestFinal(sha1, hash_out, NULL); - EVP_MD_CTX_destroy(sha1); -#else - SHA1_Final(hash_out, &sha1); -#endif -#elif defined(HAVE_GNUTLS) - unsigned char *newhash = gcry_md_read(hd, GCRY_MD_SHA1); - memcpy(hash_out, newhash, 20); -#elif defined(HAVE_MBEDTLS) - mbedtls_sha1_finish(&sha1, hash_out); -#endif + sha1_final(&sha1, hash_out); } -#if defined(HAVE_GNUTLS) - gcry_md_close(hd); -#elif defined(HAVE_MBEDTLS) - mbedtls_sha1_free(&sha1); -#endif } static void print_hash(const unsigned char *hash, int len) @@ -547,7 +464,7 @@ 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 ) { @@ -1285,14 +1202,14 @@ files_out: } 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; @@ -1309,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 { @@ -1322,7 +1239,7 @@ files_out: } else if (auth_ver) { printf("Unknown AuthVersion '%s', cannot verify AuthSignature\n", auth_ver); } - plist_from_bin(bin, (uint32_t)binsize, &backup_data); + plist_from_bin((char*)bin, (uint32_t)binsize, &backup_data); free(bin); } if (!backup_data) { diff --git a/tools/ideviceimagemounter.c b/tools/ideviceimagemounter.c index f551b6c..511583e 100644 --- a/tools/ideviceimagemounter.c +++ b/tools/ideviceimagemounter.c @@ -45,8 +45,11 @@ #include <libimobiledevice/afc.h> #include <libimobiledevice/notification_proxy.h> #include <libimobiledevice/mobile_image_mounter.h> +#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; @@ -62,18 +65,38 @@ typedef enum { 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 = strrchr(argv[0], '/'); - fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] IMAGE_FILE IMAGE_SIGNATURE_FILE\n", (name ? name + 1: 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" - "Mounts the specified disk image on the device.\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" - " -l, --list List mount information\n" " -t, --imagetype TYPE Image type to use, default is 'Developer'\n" " -x, --xml Use XML output\n" " -d, --debug enable communication debugging\n" @@ -87,11 +110,11 @@ static void print_usage(int argc, char **argv, int is_error) static void parse_opts(int argc, char **argv) { + int debug_level = 0; static struct option longopts[] = { { "help", no_argument, NULL, 'h' }, { "udid", required_argument, NULL, 'u' }, { "network", no_argument, NULL, 'n' }, - { "list", no_argument, NULL, 'l' }, { "imagetype", required_argument, NULL, 't' }, { "xml", no_argument, NULL, 'x' }, { "debug", no_argument, NULL, 'd' }, @@ -101,7 +124,7 @@ static void parse_opts(int argc, char **argv) int c; while (1) { - c = getopt_long(argc, argv, "hu:lt:xdnv", longopts, NULL); + c = getopt_long(argc, argv, "hu:t:xdnv", longopts, NULL); if (c == -1) { break; } @@ -121,9 +144,6 @@ static void parse_opts(int argc, char **argv) case 'n': use_network = 1; break; - case 'l': - list_mode = 1; - break; case 't': imagetype = optarg; break; @@ -131,7 +151,7 @@ static void parse_opts(int argc, char **argv) xml_mode = 1; break; case 'd': - idevice_set_debug_level(1); + debug_level++; break; case 'v': printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); @@ -141,6 +161,8 @@ static void parse_opts(int argc, char **argv) exit(2); } } + idevice_set_debug_level(debug_level); + tss_set_debug_level(debug_level); } static ssize_t mim_upload_cb(void* buf, size_t size, void* userdata) @@ -169,29 +191,75 @@ int main(int argc, char **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; } } + 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; + return 1; } if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lckd, TOOL_NAME))) { @@ -215,7 +283,7 @@ int main(int argc, char **argv) } } - if (product_version_major == 16) { + 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); @@ -246,7 +314,7 @@ int main(int argc, char **argv) service = NULL; } - if (!list_mode) { + if (cmd == CMD_MOUNT) { struct stat fst; if (disk_image_upload_type == DISK_IMAGE_UPLOAD_TYPE_AFC) { if ((lockdownd_start_service(lckd, "com.apple.afc", &service) != @@ -268,7 +336,7 @@ int main(int argc, char **argv) goto leave; } image_size = fst.st_size; - if (stat(image_sig_path, &fst) != 0) { + 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; } @@ -280,10 +348,14 @@ int main(int argc, char **argv) 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 = "Developer"; + if (product_version_major < 17) { + imagetype = "Developer"; + } else { + imagetype = "Personalized"; + } } err = mobile_image_mounter_lookup_image(mim, imagetype, &result); if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { @@ -292,25 +364,217 @@ int main(int argc, char **argv) } 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, "rb"); - 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, "rb"); - 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; @@ -324,11 +588,16 @@ int main(int argc, char **argv) goto leave; } - if (!imagetype) { imagetype = "Developer"; } + if (!mim) { + if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + goto error_out; + } + } + switch(disk_image_upload_type) { case DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE: printf("Uploading %s\n", image_path); @@ -403,7 +672,7 @@ int main(int argc, char **argv) printf("done.\n"); printf("Mounting...\n"); - err = mobile_image_mounter_mount_image(mim, mountname, sig, sig_length, imagetype, &result); + 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"); @@ -435,7 +704,10 @@ int main(int argc, char **argv) printf("unexpected result:\n"); plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0); } - + 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); } @@ -444,6 +716,37 @@ int main(int argc, char **argv) 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) { @@ -466,7 +769,7 @@ leave: idevice_free(device); if (image_path) - free(image_path); + free(image_path); if (image_sig_path) free(image_sig_path); |