pax_global_header00006660000000000000000000000064146222201420014505gustar00rootroot0000000000000052 comment=1431d42b568ee78161a41ed02df0de60dc1439d6 ideviceinstaller-master/000077500000000000000000000000001462222014200156705ustar00rootroot00000000000000ideviceinstaller-master/.github/000077500000000000000000000000001462222014200172305ustar00rootroot00000000000000ideviceinstaller-master/.github/workflows/000077500000000000000000000000001462222014200212655ustar00rootroot00000000000000ideviceinstaller-master/.github/workflows/build.yml000066400000000000000000000240061462222014200231110ustar00rootroot00000000000000name: 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 sudo apt-get install libzip-dev - 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 libimobiledevice uses: dawidd6/action-download-artifact@v3 with: github_token: ${{secrets.GITHUB_TOKEN}} workflow: build.yml name: libimobiledevice-latest_${{env.target_triplet}} repo: libimobiledevice/libimobiledevice - name: install external dependencies run: | mkdir extract for I in *.tar; do tar -C extract -xvf $I done rm -rf extract/lib 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 - 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 ideviceinstaller.tar usr - name: publish artifact uses: actions/upload-artifact@v4 with: name: ideviceinstaller-latest_${{env.target_triplet}} path: ideviceinstaller.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 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 libimobiledevice uses: dawidd6/action-download-artifact@v3 with: github_token: ${{secrets.GITHUB_TOKEN}} workflow: build.yml name: libimobiledevice-latest_macOS repo: libimobiledevice/libimobiledevice - 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: | SDKDIR=`xcrun --sdk macosx --show-sdk-path 2>/dev/null` echo "SDKDIR=$SDKDIR" >> $GITHUB_ENV 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" echo "BUILD_CFLAGS=$CFLAGS" >> $GITHUB_ENV 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 LIBRESSL_VER=2.2.7 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 "LIBRESSL_CFLAGS=-I`pwd`/deps/libressl-$LIBRESSL_VER/include" >> $GITHUB_ENV echo "LIBRESSL_LIBS=-Xlinker `pwd`/lib/libssl.35.tbd -Xlinker `pwd`/lib/libcrypto.35.tbd" >> $GITHUB_ENV FILENAME="libzip-static.tar.bz2" curl -o $FILENAME.b64 -Ls "https://gist.github.com/nikias/3da15d03120382f87b44029cd8495a02/raw/99cd8138fed99e8f6530b6f179f787342c698e1f/libzip-1.7.1_static_macOS.tar.bz2" base64 -D < $FILENAME.b64 > $FILENAME tar -C deps -xjf $FILENAME echo "LIBZIP_CFLAGS=-I`pwd`/deps/include" >> $GITHUB_ENV echo "LIBZIP_LIBS=`pwd`/deps/lib/libzip.a -Xlinker ${SDKDIR}/usr/lib/libbz2.tbd -Xlinker ${SDKDIR}/usr/lib/liblzma.tbd -lz" >> $GITHUB_ENV - name: autogen run: | export CFLAGS="${{env.BUILD_CFLAGS}} -Wno-nullability-completeness -Wno-expansion-to-defined" echo "Using CFLAGS: $CFLAGS" ./autogen.sh PKG_CONFIG_PATH=/usr/local/lib/pkgconfig \ openssl_CFLAGS="$LIBRESSL_CFLAGS" openssl_LIBS="$LIBRESSL_LIBS" \ libzip_CFLAGS="$LIBZIP_CFLAGS -I${{env.SDKDIR}}/usr/include" libzip_LIBS="$LIBZIP_LIBS -lz" \ libimobiledevice_CFLAGS="-I/usr/local/include ${{env.LIBRESSL_CFLAGS}}" libimobiledevice_LIBS="-L/usr/local/lib -lusbmuxd-2.0 -limobiledevice-glue-1.0 -limobiledevice-1.0 ${{env.LIBRESSL_LIBS}}" - 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 ideviceinstaller.tar usr - name: publish artifact uses: actions/upload-artifact@v4 with: name: ideviceinstaller-latest_macOS path: ideviceinstaller.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 liblzma - name: prepare environment run: | dest=`echo ${{ matrix.msystem }} |tr [:upper:] [:lower:]` echo "dest=$dest" >> $GITHUB_ENV 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_${{ 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 libimobiledevice uses: dawidd6/action-download-artifact@v3 with: github_token: ${{secrets.GITHUB_TOKEN}} workflow: build.yml name: libimobiledevice-latest_${{ matrix.arch }}-${{ env.dest }} repo: libimobiledevice/libimobiledevice - 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: install additional requirements run: | FILENAME="libzip-1.7.1-static.tar.bz2" curl -o $FILENAME.b64 -Ls "https://gist.github.com/nikias/3da15d03120382f87b44029cd8495a02/raw/99cd8138fed99e8f6530b6f179f787342c698e1f/libzip-1.7.1_static_${{matrix.arch}}-${{env.dest}}.tar.bz2" base64 -d < $FILENAME.b64 > $FILENAME mkdir deps tar -C deps -xjf $FILENAME echo "LIBZIP_CFLAGS=-I`pwd`/deps/include" >> $GITHUB_ENV echo "LIBZIP_LIBS=`pwd`/deps/lib/libzip.a /${{env.dest}}/lib/libbz2.a /${{env.dest}}/lib/liblzma.a " >> $GITHUB_ENV - name: autogen run: ./autogen.sh CC=gcc CXX=g++ libzip_CFLAGS="${{env.LIBZIP_CFLAGS}}" libzip_LIBS="${{env.LIBZIP_LIBS}} /${{env.dest}}/lib/libz.a" - 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 ideviceinstaller.tar ${{ env.dest }} - name: publish artifact uses: actions/upload-artifact@v4 with: name: ideviceinstaller-latest_${{ matrix.arch }}-${{ env.dest }} path: ideviceinstaller.tar ideviceinstaller-master/.gitignore000066400000000000000000000007571462222014200176710ustar00rootroot00000000000000# git-ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): *.[oa] *~ *.po *.lo *.la autom4te.cache/* *.in */.deps/* m4/* *.swp aclocal.m4 config.h config.log config.sub config.guess config.status configure depcomp install-sh compile main ltmain.sh missing mkinstalldirs libtool *Makefile stamp-h1 src/.libs src/ideviceinstaller ideviceinstaller-master/AUTHORS000066400000000000000000000000721462222014200167370ustar00rootroot00000000000000Julien Lavergne Keith Gable Martin Szulecki Nikias Bassen ideviceinstaller-master/COPYING000066400000000000000000000431041462222014200167250ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ideviceinstaller-master/Makefile.am000066400000000000000000000004121462222014200177210ustar00rootroot00000000000000AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 SUBDIRS = src man EXTRA_DIST = \ README.md \ git-version-gen dist-hook: @if ! git diff --quiet; then echo "Uncommitted changes present; not releasing"; exit 1; fi echo $(VERSION) > $(distdir)/.tarball-version ideviceinstaller-master/NEWS000066400000000000000000000054471462222014200164010ustar00rootroot00000000000000Version 1.1.1 ~~~~~~~~~~~~~ * Changes: - Bump autoconf requirement to 2.64 - Bump libzip dependency to 0.10 - Ignore .DS_Store and hidden files when parsing ZIP files - Fix device removal detection triggering on any device unplug - Improve excessive progress output - Return non-zero exit status on errors - Remove length check on UDID argument to support newer devices - Fix win32 build - Add "-n" option to make waiting on install/uninstall notification optional - Ignore SIGPIPE signal - Add "--network" and "--version" options to ideviceactivation tool - Bump libimobiledevice dependency to 1.3.0 - Bump libplist dependency to 2.2.0 - Improve README.md with project description, installation, contributing and usage sections Version 1.1.0 ~~~~~~~~~~~~~ * Changes: - Fix installation of archives which are missing app directory zip file entry - Return non-zero exit status (128) when device error occurs - Add error checking for readlink use - Add support for installing from directories which contain symlinks - Plug a few memory leaks - Print AFC error code if writing fails - Fix possible buffer overflow with filename argument - Increase transfer buffer size for faster file uploads - Fix building with older libzip versions - Swap "-u" and "-U" arguments and print deprecation warning if still used - Improve error reporting on wrong usage - Replace "iPhone" wording with more general term "iOS device" - Remove waiting timeouts as they appear unreliable with large archives - Don't wait for a notification during uninstall as there is none sometimes - Improve command line output for more clarity and unification - Detect device removal and abort operation in that case - Turn some errors messages into warnings to not confuse users - Support iOS 7 correctly by passing "CFBundleIdentifier" option - Allow installation of developer apps by passing a ".app" directory - Add compatibility for libimobiledevice >= 1.1.5 - Fix file operations for WIN32 - Fix wrong usage description for upgrade command - Use CFBundleExecutable instead of CFBundleName to construct executable path - Rename "uuid" to correct "udid" abbreviation as used in other tools - Fix various issues with ZIP index and locating files in the archive - Improve detection of Info.plist in application archives - Fix compiler warnings - Allow creating app archives with just the documents/user data - Add support for CarrierBundle installation (.ipcc files) Version 1.0.1 ~~~~~~~~~~~~~ * Changes: - Fix minor manpage typos - Fix build on OS X - Fix build warnings and make compilers happy Version 1.0.0 ~~~~~~~~~~~~~ * First official public tarbal release * Features: - Install, upgrade, uninstall, archive, restore, and enumerate installed or archived apps on an iDevice ideviceinstaller-master/README.md000066400000000000000000000072661462222014200171620ustar00rootroot00000000000000# ideviceinstaller *A command-line application to manage apps and app archives on iOS devices.* ![](https://github.com/libimobiledevice/ideviceinstaller/actions/workflows/build.yml/badge.svg) ## Features The ideviceinstaller application allows interacting with the app installation service of an iOS device. It makes use of the fabulous [libimobiledevice library](https://github.com/libimobiledevice/libimobiledevice) that allows communication with iOS devices. Some key features are: - **Status:** Install, upgrade, uninstall, and enumerate apps - **Browse**: Allows to retrieve a list of installed apps with filter options - **Install**: Supports app package, carrier bundle and developer .app directory - **Format**: Allows command output in plist, XML, or JSON format - **Compatibility**: Supports latest device firmware releases - **Cross-Platform:** Tested on Linux, macOS, Windows and Android platforms ## 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 \ libimobiledevice-dev \ libzip-dev \ usbmuxd ``` Continue with cloning the actual project repository: ```shell git clone https://github.com/libimobiledevice/ideviceinstaller.git cd ideviceinstaller ``` Now you can build and install it: ```shell ./autogen.sh make sudo make install ``` ## Usage First of all attach your device to your machine. Then simply run: ```shell ideviceinstaller list ``` This will print a list of `` identifiers (bundle identifiers) for use with other commands (see further below). To install an app from a package file use: ```shell ideviceinstaller install ``` To uninstall an app with the `` from the device use: ```shell ideviceinstaller uninstall ``` Please consult the usage information or manual page for a full documentation of available command line options: ```shell ideviceinstaller --help man ideviceinstaller ``` ## 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/ideviceinstaller.git * Repository (Mirror): https://github.com/libimobiledevice/ideviceinstaller.git * Issue Tracker: https://github.com/libimobiledevice/ideviceinstaller/issues * Mailing List: https://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel * Twitter: https://twitter.com/libimobiledev ## License This software is licensed under the [GNU General Public License v2.0](https://www.gnu.org/licenses/gpl-2.0.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. ideviceinstaller is an independent software application and has not been authorized, sponsored or otherwise approved by Apple Inc. README Updated on: 2023-07-20 ideviceinstaller-master/autogen.sh000077500000000000000000000005551462222014200176760ustar00rootroot00000000000000#!/bin/sh 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 $srcdir/configure "$@" fi ideviceinstaller-master/configure.ac000066400000000000000000000043771462222014200201710ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.68]) AC_INIT([ideviceinstaller], [m4_esyscmd(./git-version-gen $RELEASE_VERSION)], [https://github.com/libimobiledevice/ideviceinstaller/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]) AC_CONFIG_MACRO_DIR([m4]) # 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 # Checks for programs. AC_PROG_CC AM_PROG_CC_C_O LT_INIT # Checks for libraries. PKG_CHECK_MODULES(libimobiledevice, libimobiledevice-1.0 >= 1.3.0) PKG_CHECK_MODULES(libplist, libplist-2.0 >= 2.3.0) PKG_CHECK_MODULES(libzip, libzip >= 0.10) # Checks for header files. AC_CHECK_HEADERS([stdint.h stdlib.h string.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT8_T # Checks for library functions. AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([strdup strerror asprintf vasprintf]) # Check for lstat AC_MSG_CHECKING([whether lstat is available]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #if defined(HAVE_UNISTD_H) #include #endif ]],[[ struct stat st; lstat("/tmp", &st); ]])], [have_lstat="yes"], [have_lstat="no"]) AC_MSG_RESULT([${have_lstat}]) if test "x${have_lstat}" = "xyes" ; then AC_DEFINE([HAVE_LSTAT], 1, [Define if lstat syscall is supported]) fi AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wmissing-declarations -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Werror -g") AC_SUBST(GLOBAL_CFLAGS) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) AC_CONFIG_FILES([ Makefile src/Makefile man/Makefile ]) AC_OUTPUT echo " Configuration for $PACKAGE $VERSION: ------------------------------------------- Install prefix: .........: $prefix Now type 'make' to build $PACKAGE $VERSION, and then 'make install' for installation. " ideviceinstaller-master/git-version-gen000077500000000000000000000011031462222014200206260ustar00rootroot00000000000000#!/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" ideviceinstaller-master/m4/000077500000000000000000000000001462222014200162105ustar00rootroot00000000000000ideviceinstaller-master/m4/as-compiler-flag.m4000066400000000000000000000030071462222014200215740ustar00rootroot00000000000000dnl as-compiler-flag.m4 0.1.0 dnl autostars m4 macro for detection of compiler flags dnl David Schleef dnl $Id: as-compiler-flag.m4,v 1.1 2005/12/15 23:35:19 ds Exp $ dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED]) dnl Tries to compile with the given CFLAGS. dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags, dnl and ACTION-IF-NOT-ACCEPTED otherwise. AC_DEFUN([AS_COMPILER_FLAG], [ AC_MSG_CHECKING([to see if compiler understands $1]) save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $1" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) CFLAGS="$save_CFLAGS" if test "X$flag_ok" = Xyes ; then m4_ifvaln([$2],[$2]) true else m4_ifvaln([$3],[$3]) true fi AC_MSG_RESULT([$flag_ok]) ]) dnl AS_COMPILER_FLAGS(VAR, FLAGS) dnl Tries to compile with the given CFLAGS. AC_DEFUN([AS_COMPILER_FLAGS], [ list=$2 flags_supported="" flags_unsupported="" AC_MSG_CHECKING([for supported compiler flags]) for each in $list do save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $each" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) CFLAGS="$save_CFLAGS" if test "X$flag_ok" = Xyes ; then flags_supported="$flags_supported $each" else flags_unsupported="$flags_unsupported $each" fi done AC_MSG_RESULT([$flags_supported]) if test "X$flags_unsupported" != X ; then AC_MSG_WARN([unsupported compiler flags: $flags_unsupported]) fi $1="$$1 $flags_supported" ]) ideviceinstaller-master/man/000077500000000000000000000000001462222014200164435ustar00rootroot00000000000000ideviceinstaller-master/man/Makefile.am000066400000000000000000000000671462222014200205020ustar00rootroot00000000000000man_MANS = ideviceinstaller.1 EXTRA_DIST = $(man_MANS) ideviceinstaller-master/man/ideviceinstaller.1000066400000000000000000000061411462222014200220550ustar00rootroot00000000000000.TH "ideviceinstaller" 1 .SH NAME ideviceinstaller \- Manage apps on iOS devices. .SH SYNOPSIS .B ideviceinstaller [OPTIONS] .SH DESCRIPTION Allows to enumerate, install, upgrade, and uninstall apps on iOS devices. .SH COMMANDS .TP .B list List installed apps on the device. Options: .RS .TP .B \-\-user List user apps only (apps installed by the user). .B This is the default. .TP .B \-\-system List system apps only (apps available from the system firmware). .TP .B \-\-all List all types of apps. .TP .B \-\-xml Print output as XML Property List. .TP .B \-a, \-\-attribute ATTR Specify attribute to return. This argument can be passed multiple times. If omitted and \f[B]\-\-xml\f[] is *not* specified, the default attributes \f[B]CFBundleIdentifier\f[], \f[B]CFBundleShortVersionString\f[], and \f[B]CFBundleDisplayName\f[] will be used. The attributes can be found in the app's Info.plist, but also some extra attributes exist. Some examples: .RS .TP \f[B]StaticDiskUsage\f[] disk usage of installed app .TP \f[B]DynamicDiskUsage\f[] app user data disk usage .TP \f[B]Path\f[] app installation location .TP \f[B]SignerIdentity\f[] code signing identity .TP NOTE: It is suggested to always add CFBundleIdentifier to allow unique identification of the apps. .RE .TP .B \-b, \-\-bundle\-identifier BUNDLEID Only query given bundle identifier. This argument can be passed multiple times. .RE .TP .B install PATH Install app from a package file specified by PATH. PATH can also be a .ipcc file for carrier bundle installation or a .app directory for developer app installation. .RS .TP .B \-s, \-\-sinf PATH Pass an external SINF file located at PATH. .TP .B \-m, \-\-metadata PATH Pass an external iTunesMetadata file located at PATH. .RE .TP .B uninstall BUNDLEID Uninstall app specified by BUNDLEID. .TP .B upgrade PATH Upgrade app from a package file specified by PATH. .SH LEGACY COMMANDS The following commands are non-functional with iOS 7 or later. .TP .B archive BUNDLEID Archive app specified by BUNDLEID. Options: .RS .TP .B \-\-uninstall Uninstall the package after making an archive .TP .B \-\-app_only Archive application data only .TP .B \-\-docs_only Archive documents (user data) only .TP .B \-\-copy=PATH Copy the app archive to directory PATH when done .TP .B \-\-remove Only valid when copy=PATH is used: remove after copy .RE .TP .B restore BUNDLEID Restore archived app specified by BUNDLEID. .TP .B list-archives List archived apps on the device. Options: .RS .TP .B \-\-xml Print output as XML Property List. .RE .TP .B remove-archive BUNDLEID Remove app archive specified by BUNDLEID. .SH OPTIONS .TP .B \-u, \-\-udid UDID Target specific device by UDID. .TP .B \-n, \-\-network Connect to network device. .TP .B \-w, \-\-notify-wait Wait for app installed/uninstalled notification before reporting success of operation. .TP .B \-h, \-\-help Print usage information. .TP .B \-d, \-\-debug Enable communication debugging. .TP .B \-v, \-\-version Print version information. .SH AUTHORS Nikias Bassen Martin Szulecki .SH ON THE WEB https://libimobiledevice.org https://github.com/libimobiledevice/ideviceinstaller ideviceinstaller-master/src/000077500000000000000000000000001462222014200164575ustar00rootroot00000000000000ideviceinstaller-master/src/Makefile.am000066400000000000000000000006071462222014200205160ustar00rootroot00000000000000AM_CFLAGS = \ $(GLOBAL_CFLAGS) \ $(libimobiledevice_CFLAGS) \ $(libglib2_CFLAGS) \ $(libplist_CFLAGS) \ $(libzip_CFLAGS) AM_LDFLAGS = \ $(libimobiledevice_LIBS) \ $(libglib2_LIBS) \ $(libplist_LIBS) \ $(libzip_LIBS) bin_PROGRAMS = ideviceinstaller ideviceinstaller_SOURCES = ideviceinstaller.c ideviceinstaller_CFLAGS = $(AM_CFLAGS) ideviceinstaller_LDFLAGS = $(AM_LDFLAGS) ideviceinstaller-master/src/ideviceinstaller.c000066400000000000000000001270511462222014200221570ustar00rootroot00000000000000/* * ideviceinstaller - Manage apps on iOS devices. * * Copyright (C) 2010-2023 Nikias Bassen * Copyright (C) 2010-2015 Martin Szulecki * * Licensed under the GNU General Public License Version 2 * * 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 profile. * * 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 #endif #include #define _GNU_SOURCE 1 #define __USE_GNU 1 #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifndef WIN32 #include #endif #include #include #include #include #include #include #include #ifdef WIN32 #include #define wait_ms(x) Sleep(x) #else #define wait_ms(x) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = x * 1000000; nanosleep(&ts, NULL); } #endif #ifndef HAVE_VASPRINTF static 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 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 #define ITUNES_METADATA_PLIST_FILENAME "iTunesMetadata.plist" const char PKG_PATH[] = "PublicStaging"; const char APPARCH_PATH[] = "ApplicationArchives"; char *udid = NULL; char *cmdarg = NULL; char *extsinf = NULL; char *extmeta = NULL; enum cmd_mode { CMD_NONE = 0, CMD_LIST_APPS, CMD_INSTALL, CMD_UNINSTALL, CMD_UPGRADE, CMD_LIST_ARCHIVES, CMD_ARCHIVE, CMD_RESTORE, CMD_REMOVE_ARCHIVE }; int cmd = CMD_NONE; char *last_status = NULL; int wait_for_command_complete = 0; int use_network = 0; int use_notifier = 0; int notification_expected = 0; int is_device_connected = 0; int command_completed = 0; int ignore_events = 0; int err_occurred = 0; int notified = 0; plist_t bundle_ids = NULL; plist_t return_attrs = NULL; #define FORMAT_XML 1 #define FORMAT_JSON 2 int output_format = 0; int opt_list_user = 0; int opt_list_system = 0; char *copy_path = NULL; int remove_after_copy = 0; int skip_uninstall = 1; int app_only = 0; int docs_only = 0; static void print_apps_header() { if (!return_attrs) { return; } uint32_t i = 0; for (i = 0; i < plist_array_get_size(return_attrs); i++) { plist_t node = plist_array_get_item(return_attrs, i); if (i > 0) { printf(", "); } printf("%s", plist_get_string_ptr(node, NULL)); } printf("\n"); } static void print_apps(plist_t apps) { if (!return_attrs) { return; } uint32_t i = 0; for (i = 0; i < plist_array_get_size(apps); i++) { plist_t app = plist_array_get_item(apps, i); uint32_t j = 0; for (j = 0; j < plist_array_get_size(return_attrs); j++) { plist_t node = plist_array_get_item(return_attrs, j); if (j > 0) { printf(", "); } const char* key = plist_get_string_ptr(node, NULL); node = plist_dict_get_item(app, key); if (node) { if (!strcmp(key, "CFBundleIdentifier")) { printf("%s", plist_get_string_ptr(node, NULL)); } else { uint64_t uval = 0; switch (plist_get_node_type(node)) { case PLIST_STRING: printf("\"%s\"", plist_get_string_ptr(node, NULL)); break; case PLIST_INT: plist_get_uint_val(node, &uval); printf("%" PRIu64, uval); break; case PLIST_BOOLEAN: printf("%s", plist_bool_val_is_true(node) ? "true" : "false"); break; case PLIST_ARRAY: printf("(array)"); break; case PLIST_DICT: printf("(dict)"); break; default: break; } } } } printf("\n"); } } static void notifier(const char *notification, void *unused) { notified = 1; } static void status_cb(plist_t command, plist_t status, void *unused) { if (command && status) { char* command_name = NULL; instproxy_command_get_name(command, &command_name); /* get status */ char *status_name = NULL; instproxy_status_get_name(status, &status_name); if (status_name) { if (!strcmp(status_name, "Complete")) { command_completed = 1; } } /* get error if any */ char* error_name = NULL; char* error_description = NULL; uint64_t error_code = 0; instproxy_status_get_error(status, &error_name, &error_description, &error_code); /* output/handling */ if (!error_name) { if (!strcmp(command_name, "Browse")) { uint64_t total = 0; uint64_t current_index = 0; uint64_t current_amount = 0; plist_t current_list = NULL; instproxy_status_get_current_list(status, &total, ¤t_index, ¤t_amount, ¤t_list); if (current_list) { print_apps(current_list); plist_free(current_list); } } else if (status_name) { /* get progress if any */ int percent = -1; instproxy_status_get_percent_complete(status, &percent); if (last_status && (strcmp(last_status, status_name))) { printf("\n"); } if (percent >= 0) { printf("\r%s: %s (%d%%)", command_name, status_name, percent); } else { printf("\r%s: %s", command_name, status_name); } if (command_completed) { printf("\n"); } } } else { /* report error to the user */ if (error_description) fprintf(stderr, "ERROR: %s failed. Got error \"%s\" with code 0x%08"PRIx64": %s\n", command_name, error_name, error_code, error_description ? error_description: "N/A"); else fprintf(stderr, "ERROR: %s failed. Got error \"%s\".\n", command_name, error_name); err_occurred = 1; } /* clean up */ free(error_name); free(error_description); free(last_status); last_status = status_name; free(command_name); command_name = NULL; } else { fprintf(stderr, "ERROR: %s was called with invalid arguments!\n", __func__); } } static int zip_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len) { struct zip_stat zs; struct zip_file *zfile; int zindex = zip_name_locate(zf, filename, locate_flags); *buffer = NULL; *len = 0; if (zindex < 0) { return -1; } zip_stat_init(&zs); if (zip_stat_index(zf, zindex, 0, &zs) != 0) { fprintf(stderr, "ERROR: zip_stat_index '%s' failed!\n", filename); return -2; } if (zs.size > 10485760) { fprintf(stderr, "ERROR: file '%s' is too large!\n", filename); return -3; } zfile = zip_fopen_index(zf, zindex, 0); if (!zfile) { fprintf(stderr, "ERROR: zip_fopen '%s' failed!\n", filename); return -4; } *buffer = malloc(zs.size); if (zs.size > LLONG_MAX || zip_fread(zfile, *buffer, zs.size) != (zip_int64_t)zs.size) { fprintf(stderr, "ERROR: zip_fread %" PRIu64 " bytes from '%s'\n", (uint64_t)zs.size, filename); free(*buffer); *buffer = NULL; zip_fclose(zfile); return -5; } *len = zs.size; zip_fclose(zfile); return 0; } static int zip_get_app_directory(struct zip* zf, char** path) { zip_int64_t i = 0; zip_int64_t c = (zip_int64_t)zip_get_num_entries(zf, 0); int len = 0; const char* name = NULL; /* look through all filenames in the archive */ do { /* get filename at current index */ name = zip_get_name(zf, i++, 0); if (name != NULL) { /* check if we have a "Payload/.../" name */ len = strlen(name); if (!strncmp(name, "Payload/", 8) && (len > 8)) { /* skip hidden files */ if (name[8] == '.') continue; /* locate the second directory delimiter */ const char* p = name + 8; do { if (*p == '/') { break; } } while(p++ != NULL); /* try next entry if not found */ if (p == NULL) continue; len = p - name + 1; /* make sure app directory endwith .app */ if (len < 12 || strncmp(p - 4, ".app", 4)) { continue; } if (path != NULL) { free(*path); *path = NULL; } /* allocate and copy filename */ *path = (char*)malloc(len + 1); strncpy(*path, name, len); /* add terminating null character */ char* t = *path + len; *t = '\0'; break; } } } while(i < c); if (*path == NULL) { return -1; } return 0; } static void idevice_event_callback(const idevice_event_t* event, void* userdata) { if (ignore_events) { return; } if (event->event == IDEVICE_DEVICE_REMOVE) { if (!strcmp(udid, event->udid)) { fprintf(stderr, "ideviceinstaller: Device removed\n"); is_device_connected = 0; } } } static void idevice_wait_for_command_to_complete() { is_device_connected = 1; ignore_events = 0; /* subscribe to make sure to exit on device removal */ idevice_event_subscribe(idevice_event_callback, NULL); /* wait for command to complete */ while (wait_for_command_complete && !command_completed && !err_occurred && is_device_connected) { wait_ms(50); } /* wait some time if a notification is expected */ while (use_notifier && notification_expected && !notified && !err_occurred && is_device_connected) { wait_ms(50); } ignore_events = 1; idevice_event_unsubscribe(); } 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" "Manage apps on iOS devices.\n" "\n" "COMMANDS:\n" " list List installed apps. Options:\n" " --user List user apps only (this is the default)\n" " --system List system apps only\n" " --all List all types of apps\n" " --xml Print output as XML Property List\n" " -a, --attribute ATTR Specify attribute to return - see man page\n" " (can be passed multiple times)\n" " -b, --bundle-identifier BUNDLEID Only query given bundle identifier\n" " (can be passed multiple times)\n" " install PATH Install app from package file specified by PATH.\n" " PATH can also be a .ipcc file for carrier bundles.\n" " -s, --sinf PATH Pass an external SINF file\n" " -m, --metadata PATH Pass an external iTunesMetadata file\n" " uninstall BUNDLEID Uninstall app specified by BUNDLEID.\n" " upgrade PATH Upgrade app from package file specified by PATH.\n" "\n" "LEGACY COMMANDS (non-functional with iOS 7 or later):\n" " archive BUNDLEID Archive app specified by BUNDLEID. Options:\n" " --uninstall Uninstall the package after making an archive\n" " --app-only Archive application data only\n" " --docs-only Archive documents (user data) only\n" " --copy=PATH Copy the app archive to directory PATH when done\n" " --remove Only valid when copy=PATH is used: remove after copy\n" " restore BUNDLEID Restore archived app specified by BUNDLEID\n" " list-archives List archived apps. Options:\n" " --xml Print output as XML Property List\n" " remove-archive BUNDLEID Remove app archive specified by BUNDLEID\n" "\n" "OPTIONS:\n" " -u, --udid UDID Target specific device by UDID\n" " -n, --network Connect to network device\n" " -w, --notify-wait Wait for app installed/uninstalled notification\n" " before reporting success of operation\n" " -h, --help Print usage information\n" " -d, --debug Enable communication debugging\n" " -v, --version Print version information\n" "\n" "Homepage: <" PACKAGE_URL ">\n" "Bug Reports: <" PACKAGE_BUGREPORT ">\n" ); } enum numerical_opts { LIST_USER = 1, LIST_SYSTEM, LIST_ALL, ARCHIVE_UNINSTALL, ARCHIVE_APP_ONLY, ARCHIVE_DOCS_ONLY, ARCHIVE_COPY_PATH, ARCHIVE_COPY_REMOVE, OUTPUT_XML, OUTPUT_JSON }; static void parse_opts(int argc, char **argv) { static struct option longopts[] = { { "help", no_argument, NULL, 'h' }, { "udid", required_argument, NULL, 'u' }, { "network", no_argument, NULL, 'n' }, { "notify-wait", no_argument, NULL, 'w' }, { "debug", no_argument, NULL, 'd' }, { "version", no_argument, NULL, 'v' }, { "bundle-identifier", required_argument, NULL, 'b' }, { "attribute", required_argument, NULL, 'a' }, { "user", no_argument, NULL, LIST_USER }, { "system", no_argument, NULL, LIST_SYSTEM }, { "all", no_argument, NULL, LIST_ALL }, { "xml", no_argument, NULL, OUTPUT_XML }, { "json", no_argument, NULL, OUTPUT_JSON }, { "sinf", required_argument, NULL, 's' }, { "metadata", required_argument, NULL, 'm' }, { "uninstall", no_argument, NULL, ARCHIVE_UNINSTALL }, { "app-only", no_argument, NULL, ARCHIVE_APP_ONLY }, { "docs-only", no_argument, NULL, ARCHIVE_DOCS_ONLY }, { "copy", required_argument, NULL, ARCHIVE_COPY_PATH }, { "remove", no_argument, NULL, ARCHIVE_COPY_REMOVE }, { NULL, 0, NULL, 0 } }; int c; while (1) { c = getopt_long(argc, argv, "hu:nwdvb:a:s:m:", longopts, (int*)0); if (c == -1) { break; } switch (c) { case 'h': print_usage(argc, argv, 0); exit(0); case 'u': if (!*optarg) { printf("ERROR: UDID must not be empty!\n"); print_usage(argc, argv, 1); exit(2); } udid = strdup(optarg); break; case 'n': use_network = 1; break; case 'a': if (!*optarg) { printf("ERROR: attribute must not be empty!\n"); print_usage(argc, argv, 1); exit(2); } if (return_attrs == NULL) { return_attrs = plist_new_array(); } plist_array_append_item(return_attrs, plist_new_string(optarg)); break; case 'b': if (!*optarg) { printf("ERROR: bundle identifier must not be empty!\n"); print_usage(argc, argv, 1); exit(2); } if (bundle_ids == NULL) { bundle_ids = plist_new_array(); } plist_array_append_item(bundle_ids, plist_new_string(optarg)); break; case 's': if (!*optarg) { printf("ERROR: path for --sinf must not be empty!\n"); print_usage(argc, argv, 1); exit(2); } extsinf = strdup(optarg); break; case 'm': if (!*optarg) { printf("ERROR: path for --metadata must not be empty!\n"); print_usage(argc, argv, 1); exit(2); } extmeta = strdup(optarg); break; case 'w': use_notifier = 1; break; case 'd': idevice_set_debug_level(1); break; case 'v': printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); exit(0); case LIST_USER: opt_list_user = 1; break; case LIST_SYSTEM: opt_list_system = 1; break; case LIST_ALL: opt_list_user = 1; opt_list_system = 1; break; case OUTPUT_XML: output_format = FORMAT_XML; break; case OUTPUT_JSON: output_format = FORMAT_JSON; break; case ARCHIVE_UNINSTALL: skip_uninstall = 0; break; case ARCHIVE_APP_ONLY: app_only = 1; docs_only = 0; break; case ARCHIVE_DOCS_ONLY: docs_only = 1; app_only = 0; break; case ARCHIVE_COPY_PATH: copy_path = strdup(optarg); break; case ARCHIVE_COPY_REMOVE: remove_after_copy = 1; break; default: print_usage(argc, argv, 1); exit(2); } } argv += optind; argc -= optind; if (argc == 0) { fprintf(stderr, "ERROR: Missing command.\n\n"); print_usage(argc+optind, argv-optind, 1); exit(2); } char *cmdstr = argv[0]; if (!strcmp(cmdstr, "list")) { cmd = CMD_LIST_APPS; } else if (!strcmp(cmdstr, "install")) { cmd = CMD_INSTALL; } else if (!strcmp(cmdstr, "upgrade")) { cmd = CMD_UPGRADE; } else if (!strcmp(cmdstr, "uninstall") || !strcmp(cmdstr, "remove")) { cmd = CMD_UNINSTALL; } else if (!strcmp(cmdstr, "archives") || !strcmp(cmdstr, "list-archives")) { cmd = CMD_LIST_ARCHIVES; } else if (!strcmp(cmdstr, "archive")) { cmd = CMD_ARCHIVE; } else if (!strcmp(cmdstr, "restore")) { cmd = CMD_RESTORE; } else if (!strcmp(cmdstr, "remove-archive")) { cmd = CMD_REMOVE_ARCHIVE; } switch (cmd) { case CMD_LIST_APPS: case CMD_LIST_ARCHIVES: break; case CMD_INSTALL: case CMD_UPGRADE: if (argc < 2) { fprintf(stderr, "ERROR: Missing filename for '%s' command.\n\n", cmdstr); print_usage(argc+optind, argv-optind, 1); exit(2); } cmdarg = argv[1]; break; case CMD_UNINSTALL: case CMD_ARCHIVE: case CMD_RESTORE: case CMD_REMOVE_ARCHIVE: if (argc < 2) { fprintf(stderr, "ERROR: Missing bundle ID for '%s' command.\n\n", cmdstr); print_usage(argc+optind, argv-optind, 1); exit(2); } cmdarg = argv[1]; break; default: fprintf(stderr, "ERROR: Invalid command '%s'.\n\n", cmdstr); print_usage(argc+optind, argv-optind, 1); exit(2); } } static int afc_upload_file(afc_client_t afc, const char* filename, const char* dstfn) { FILE *f = NULL; uint64_t af = 0; char buf[1048576]; f = fopen(filename, "rb"); if (!f) { fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno)); return -1; } if ((afc_file_open(afc, dstfn, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || !af) { fclose(f); fprintf(stderr, "afc_file_open on '%s' failed!\n", dstfn); return -1; } 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; afc_error_t aerr = afc_file_write(afc, af, buf, amount, &written); if (aerr != AFC_E_SUCCESS) { fprintf(stderr, "AFC Write error: %d\n", aerr); break; } total += written; } if (total != amount) { fprintf(stderr, "Error: wrote only %u of %u\n", total, (uint32_t)amount); afc_file_close(afc, af); fclose(f); return -1; } } } while (amount > 0); afc_file_close(afc, af); fclose(f); return 0; } static void afc_upload_dir(afc_client_t afc, const char* path, const char* afcpath) { afc_make_directory(afc, afcpath); DIR *dir = opendir(path); if (dir) { struct dirent* ep; while ((ep = readdir(dir))) { if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { continue; } char *fpath = (char*)malloc(strlen(path)+1+strlen(ep->d_name)+1); char *apath = (char*)malloc(strlen(afcpath)+1+strlen(ep->d_name)+1); struct stat st; strcpy(fpath, path); strcat(fpath, "/"); strcat(fpath, ep->d_name); strcpy(apath, afcpath); strcat(apath, "/"); strcat(apath, ep->d_name); #ifdef HAVE_LSTAT if ((lstat(fpath, &st) == 0) && S_ISLNK(st.st_mode)) { char *target = (char *)malloc(st.st_size+1); if (readlink(fpath, target, st.st_size+1) < 0) { fprintf(stderr, "ERROR: readlink: %s (%d)\n", strerror(errno), errno); } else { target[st.st_size] = '\0'; afc_make_link(afc, AFC_SYMLINK, target, fpath); } free(target); } else #endif if ((stat(fpath, &st) == 0) && S_ISDIR(st.st_mode)) { afc_upload_dir(afc, fpath, apath); } else { afc_upload_file(afc, fpath, apath); } free(fpath); free(apath); } closedir(dir); } } static char *buf_from_file(const char *filename, size_t *size) { struct stat st; FILE *fp = NULL; if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) { return NULL; } size_t filesize = st.st_size; if (filesize == 0) { return NULL; } char *ibuf = malloc(filesize * sizeof(char)); if (ibuf == NULL) { return NULL; } size_t amount = fread(ibuf, 1, filesize, fp); if (amount != filesize) { fprintf(stderr, "ERROR: could not read %" PRIu64 " bytes from %s\n", (uint64_t)filesize, filename); free(ibuf); return NULL; } fclose(fp); if (size) { *size = filesize; } return ibuf; } int main(int argc, char **argv) { idevice_t device = NULL; lockdownd_client_t client = NULL; instproxy_client_t ipc = NULL; instproxy_error_t err; np_client_t np = NULL; afc_client_t afc = NULL; lockdownd_service_descriptor_t service = NULL; int res = EXIT_FAILURE; char *bundleidentifier = NULL; #ifndef WIN32 signal(SIGPIPE, SIG_IGN); #endif parse_opts(argc, argv); argc -= optind; argv += optind; if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) { if (udid) { fprintf(stderr, "No device found with udid %s.\n", udid); } else { fprintf(stderr, "No device found.\n"); } return EXIT_FAILURE; } if (!udid) { idevice_get_udid(device, &udid); } lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller"); if (lerr != LOCKDOWN_E_SUCCESS) { fprintf(stderr, "Could not connect to lockdownd: %s. Exiting.\n", lockdownd_strerror(lerr)); goto leave_cleanup; } if (use_notifier) { lerr =lockdownd_start_service(client, "com.apple.mobile.notification_proxy", &service); if (lerr != LOCKDOWN_E_SUCCESS) { fprintf(stderr, "Could not start com.apple.mobile.notification_proxy: %s\n", lockdownd_strerror(lerr)); goto leave_cleanup; } np_error_t nperr = np_client_new(device, service, &np); if (service) { lockdownd_service_descriptor_free(service); } service = NULL; if (nperr != NP_E_SUCCESS) { fprintf(stderr, "Could not connect to notification_proxy!\n"); goto leave_cleanup; } np_set_notify_callback(np, notifier, NULL); const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL }; np_observe_notifications(np, noties); } run_again: if (service) { lockdownd_service_descriptor_free(service); } service = NULL; lerr = lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service); if (lerr != LOCKDOWN_E_SUCCESS) { fprintf(stderr, "Could not start com.apple.mobile.installation_proxy: %s\n", lockdownd_strerror(lerr)); goto leave_cleanup; } err = instproxy_client_new(device, service, &ipc); if (service) { lockdownd_service_descriptor_free(service); } service = NULL; if (err != INSTPROXY_E_SUCCESS) { fprintf(stderr, "Could not connect to installation_proxy!\n"); goto leave_cleanup; } setbuf(stdout, NULL); free(last_status); last_status = NULL; notification_expected = 0; if (cmd == CMD_LIST_APPS) { plist_t client_opts = instproxy_client_options_new(); instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL); plist_t apps = NULL; if (opt_list_system && opt_list_user) { plist_dict_remove_item(client_opts, "ApplicationType"); } else if (opt_list_system) { instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL); } else if (opt_list_user) { instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL); } if (bundle_ids) { plist_dict_set_item(client_opts, "BundleIDs", plist_copy(bundle_ids)); } if (!output_format && !return_attrs) { return_attrs = plist_new_array(); plist_array_append_item(return_attrs, plist_new_string("CFBundleIdentifier")); plist_array_append_item(return_attrs, plist_new_string("CFBundleShortVersionString")); plist_array_append_item(return_attrs, plist_new_string("CFBundleDisplayName")); } if (return_attrs) { instproxy_client_options_add(client_opts, "ReturnAttributes", return_attrs, NULL); } if (output_format) { err = instproxy_browse(ipc, client_opts, &apps); if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) { fprintf(stderr, "ERROR: instproxy_browse returnd an invalid plist!\n"); goto leave_cleanup; } char *buf = NULL; uint32_t len = 0; if (output_format == FORMAT_XML) { plist_err_t perr = plist_to_xml(apps, &buf, &len); if (perr != PLIST_ERR_SUCCESS) { fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr); } } else if (output_format == FORMAT_JSON) { /* for JSON, we need to convert some stuff since it doesn't support PLIST_DATA nodes */ plist_array_iter aiter = NULL; plist_array_new_iter(apps, &aiter); plist_t entry = NULL; do { plist_array_next_item(apps, aiter, &entry); if (!entry) break; plist_t items = plist_dict_get_item(entry, "UIApplicationShortcutItems"); plist_array_iter inner = NULL; plist_array_new_iter(items, &inner); plist_t item = NULL; do { plist_array_next_item(items, inner, &item); if (!item) break; plist_t userinfo = plist_dict_get_item(item, "UIApplicationShortcutItemUserInfo"); if (userinfo) { plist_t data_node = plist_dict_get_item(userinfo, "data"); if (data_node) { char *strbuf = NULL; uint32_t buflen = 0; plist_write_to_string(data_node, &strbuf, &buflen, PLIST_FORMAT_LIMD, PLIST_OPT_NO_NEWLINE); plist_set_string_val(data_node, strbuf); free(strbuf); } } } while (item); free(inner); } while (entry); free(aiter); plist_err_t perr = plist_to_json(apps, &buf, &len, 1); if (perr != PLIST_ERR_SUCCESS) { fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr); } } if (buf) { puts(buf); free(buf); } plist_free(apps); res = 0; goto leave_cleanup; } print_apps_header(); err = instproxy_browse_with_callback(ipc, client_opts, status_cb, NULL); if (err == INSTPROXY_E_RECEIVE_TIMEOUT) { fprintf(stderr, "NOTE: timeout waiting for device to browse apps, trying again...\n"); } instproxy_client_options_free(client_opts); if (err != INSTPROXY_E_SUCCESS) { fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err); goto leave_cleanup; } wait_for_command_complete = 1; notification_expected = 0; } else if (cmd == CMD_INSTALL || cmd == CMD_UPGRADE) { plist_t sinf = NULL; plist_t meta = NULL; char *pkgname = NULL; struct stat fst; uint64_t af = 0; char buf[8192]; lockdownd_service_descriptor_free(service); service = NULL; lerr = lockdownd_start_service(client, "com.apple.afc", &service); if (lerr != LOCKDOWN_E_SUCCESS) { fprintf(stderr, "Could not start com.apple.afc: %s\n", lockdownd_strerror(lerr)); goto leave_cleanup; } lockdownd_client_free(client); client = NULL; if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) { fprintf(stderr, "Could not connect to AFC!\n"); goto leave_cleanup; } if (stat(cmdarg, &fst) != 0) { fprintf(stderr, "ERROR: stat: %s: %s\n", cmdarg, strerror(errno)); goto leave_cleanup; } 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); } plist_t client_opts = instproxy_client_options_new(); /* open install package */ int errp = 0; struct zip *zf = NULL; if ((strlen(cmdarg) > 5) && (strcmp(&cmdarg[strlen(cmdarg)-5], ".ipcc") == 0)) { zf = zip_open(cmdarg, 0, &errp); if (!zf) { fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp); goto leave_cleanup; } char* ipcc = strdup(cmdarg); if ((asprintf(&pkgname, "%s/%s", PKG_PATH, basename(ipcc)) > 0) && pkgname) { afc_make_directory(afc, pkgname); } printf("Uploading %s package contents... ", basename(ipcc)); /* extract the contents of the .ipcc file to PublicStaging/.ipcc directory */ zip_int64_t numzf = (zip_int64_t)zip_get_num_entries(zf, 0); zip_int64_t i = 0; for (i = 0; numzf > 0 && i < numzf; i++) { const char* zname = zip_get_name(zf, i, 0); char* dstpath = NULL; if (!zname) continue; if (zname[strlen(zname)-1] == '/') { // directory if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) > 0) && dstpath) { afc_make_directory(afc, dstpath); } free(dstpath); dstpath = NULL; } else { // file struct zip_file* zfile = zip_fopen_index(zf, i, 0); if (!zfile) continue; if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) <= 0) || !dstpath || (afc_file_open(afc, dstpath, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS)) { fprintf(stderr, "ERROR: can't open afc://%s for writing\n", dstpath); free(dstpath); dstpath = NULL; zip_fclose(zfile); continue; } struct zip_stat zs; zip_stat_init(&zs); if (zip_stat_index(zf, i, 0, &zs) != 0) { fprintf(stderr, "ERROR: zip_stat_index %" PRIu64 " failed!\n", i); free(dstpath); dstpath = NULL; zip_fclose(zfile); continue; } free(dstpath); dstpath = NULL; zip_uint64_t zfsize = 0; while (zfsize < zs.size) { zip_int64_t amount = zip_fread(zfile, buf, sizeof(buf)); if (amount == 0) { break; } 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; } total += written; } if (total != amount) { fprintf(stderr, "Error: wrote only %d of %" PRIi64 "\n", total, amount); afc_file_close(afc, af); zip_fclose(zfile); free(dstpath); goto leave_cleanup; } } zfsize += amount; } afc_file_close(afc, af); af = 0; zip_fclose(zfile); } } free(ipcc); printf("DONE.\n"); instproxy_client_options_add(client_opts, "PackageType", "CarrierBundle", NULL); } else if (S_ISDIR(fst.st_mode)) { /* upload developer app directory */ instproxy_client_options_add(client_opts, "PackageType", "Developer", NULL); if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(cmdarg)) < 0) { fprintf(stderr, "ERROR: Out of memory allocating pkgname!?\n"); goto leave_cleanup; } printf("Uploading %s package contents... ", basename(cmdarg)); afc_upload_dir(afc, cmdarg, pkgname); printf("DONE.\n"); /* extract the CFBundleIdentifier from the package */ /* construct full filename to Info.plist */ char *filename = (char*)malloc(strlen(cmdarg)+11+1); strcpy(filename, cmdarg); strcat(filename, "/Info.plist"); struct stat st; FILE *fp = NULL; if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) { fprintf(stderr, "ERROR: could not locate %s in app!\n", filename); free(filename); goto leave_cleanup; } size_t filesize = st.st_size; char *ibuf = malloc(filesize * sizeof(char)); size_t amount = fread(ibuf, 1, filesize, fp); if (amount != filesize) { fprintf(stderr, "ERROR: could not read %u bytes from %s\n", (uint32_t)filesize, filename); free(filename); goto leave_cleanup; } fclose(fp); free(filename); plist_t info = NULL; plist_from_memory(ibuf, filesize, &info, NULL); free(ibuf); if (!info) { fprintf(stderr, "ERROR: could not parse Info.plist!\n"); goto leave_cleanup; } plist_t bname = plist_dict_get_item(info, "CFBundleIdentifier"); if (bname) { plist_get_string_val(bname, &bundleidentifier); } plist_free(info); info = NULL; } else { zf = zip_open(cmdarg, 0, &errp); if (!zf) { fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp); goto leave_cleanup; } char *zbuf = NULL; uint32_t len = 0; plist_t meta_dict = NULL; if (extmeta) { size_t flen = 0; zbuf = buf_from_file(extmeta, &flen); if (zbuf && flen) { meta = plist_new_data(zbuf, flen); plist_from_memory(zbuf, flen, &meta_dict, NULL); free(zbuf); } if (!meta_dict) { plist_free(meta); meta = NULL; fprintf(stderr, "WARNING: could not load external iTunesMetadata %s!\n", extmeta); } zbuf = NULL; } if (!meta && !meta_dict) { /* extract iTunesMetadata.plist from package */ if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) == 0) { meta = plist_new_data(zbuf, len); plist_from_memory(zbuf, len, &meta_dict, NULL); } if (!meta_dict) { plist_free(meta); meta = NULL; fprintf(stderr, "WARNING: could not locate %s in archive!\n", ITUNES_METADATA_PLIST_FILENAME); } free(zbuf); } /* determine .app directory in archive */ zbuf = NULL; len = 0; plist_t info = NULL; char* filename = NULL; char* app_directory_name = NULL; if (zip_get_app_directory(zf, &app_directory_name)) { fprintf(stderr, "ERROR: Unable to locate .app directory in archive. Make sure it is inside a 'Payload' directory.\n"); goto leave_cleanup; } /* construct full filename to Info.plist */ filename = (char*)malloc(strlen(app_directory_name)+10+1); strcpy(filename, app_directory_name); free(app_directory_name); app_directory_name = NULL; strcat(filename, "Info.plist"); if (zip_get_contents(zf, filename, 0, &zbuf, &len) < 0) { fprintf(stderr, "WARNING: could not locate %s in archive!\n", filename); free(filename); zip_unchange_all(zf); zip_close(zf); goto leave_cleanup; } free(filename); plist_from_memory(zbuf, len, &info, NULL); free(zbuf); if (!info) { fprintf(stderr, "Could not parse Info.plist!\n"); zip_unchange_all(zf); zip_close(zf); goto leave_cleanup; } char *bundleexecutable = NULL; plist_t bname = plist_dict_get_item(info, "CFBundleExecutable"); if (bname) { plist_get_string_val(bname, &bundleexecutable); } bname = plist_dict_get_item(info, "CFBundleIdentifier"); if (bname) { plist_get_string_val(bname, &bundleidentifier); } plist_free(info); info = NULL; if (!bundleexecutable) { fprintf(stderr, "Could not determine value for CFBundleExecutable!\n"); zip_unchange_all(zf); zip_close(zf); goto leave_cleanup; } if (extsinf) { size_t flen = 0; zbuf = buf_from_file(extsinf, &flen); if (zbuf && flen) { sinf = plist_new_data(zbuf, flen); free(zbuf); } else { fprintf(stderr, "WARNING: could not load external SINF %s!\n", extsinf); } zbuf = NULL; } if (!sinf) { char *sinfname = NULL; if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable, bundleexecutable) < 0) { fprintf(stderr, "Out of memory!?\n"); goto leave_cleanup; } free(bundleexecutable); /* extract .sinf from package */ zbuf = NULL; len = 0; if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) { sinf = plist_new_data(zbuf, len); } else { fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname); } free(sinfname); free(zbuf); } /* copy archive to device */ pkgname = NULL; if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) { fprintf(stderr, "Out of memory!?\n"); goto leave_cleanup; } printf("Copying '%s' to device... ", cmdarg); if (afc_upload_file(afc, cmdarg, pkgname) < 0) { printf("FAILED\n"); free(pkgname); goto leave_cleanup; } printf("DONE.\n"); if (bundleidentifier) { instproxy_client_options_add(client_opts, "CFBundleIdentifier", bundleidentifier, NULL); } if (sinf) { instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL); } if (meta) { instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL); } } if (zf) { zip_unchange_all(zf); zip_close(zf); } /* perform installation or upgrade */ if (cmd == CMD_INSTALL) { printf("Installing '%s'\n", bundleidentifier); instproxy_install(ipc, pkgname, client_opts, status_cb, NULL); } else { printf("Upgrading '%s'\n", bundleidentifier); instproxy_upgrade(ipc, pkgname, client_opts, status_cb, NULL); } instproxy_client_options_free(client_opts); free(pkgname); wait_for_command_complete = 1; notification_expected = 1; } else if (cmd == CMD_UNINSTALL) { printf("Uninstalling '%s'\n", cmdarg); instproxy_uninstall(ipc, cmdarg, NULL, status_cb, NULL); wait_for_command_complete = 1; notification_expected = 0; } else if (cmd == CMD_LIST_ARCHIVES) { plist_t dict = NULL; err = instproxy_lookup_archives(ipc, NULL, &dict); if (err != INSTPROXY_E_SUCCESS) { fprintf(stderr, "ERROR: lookup_archives returned %d\n", err); goto leave_cleanup; } if (!dict) { fprintf(stderr, "ERROR: lookup_archives did not return a plist!?\n"); goto leave_cleanup; } if (output_format) { char *buf = NULL; uint32_t len = 0; if (output_format == FORMAT_XML) { plist_err_t perr = plist_to_xml(dict, &buf, &len); if (perr != PLIST_ERR_SUCCESS) { fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr); } } else if (output_format == FORMAT_JSON) { plist_err_t perr = plist_to_json(dict, &buf, &len, 1); if (perr != PLIST_ERR_SUCCESS) { fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr); } } if (buf) { puts(buf); free(buf); } plist_free(dict); goto leave_cleanup; } plist_dict_iter iter = NULL; plist_t node = NULL; char *key = NULL; printf("Total: %d archived apps\n", plist_dict_get_size(dict)); plist_dict_new_iter(dict, &iter); if (!iter) { plist_free(dict); fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n"); goto leave_cleanup; } do { key = NULL; node = NULL; plist_dict_next_item(dict, iter, &key, &node); if (key && (plist_get_node_type(node) == PLIST_DICT)) { char *s_dispName = NULL; char *s_version = NULL; plist_t dispName = plist_dict_get_item(node, "CFBundleDisplayName"); plist_t version = plist_dict_get_item(node, "CFBundleShortVersionString"); if (dispName) { plist_get_string_val(dispName, &s_dispName); } if (version) { plist_get_string_val(version, &s_version); } if (!s_dispName) { s_dispName = strdup(key); } if (s_version) { printf("%s - %s %s\n", key, s_dispName, s_version); free(s_version); } else { printf("%s - %s\n", key, s_dispName); } free(s_dispName); free(key); } } while (node); plist_free(dict); } else if (cmd == CMD_ARCHIVE) { plist_t client_opts = NULL; if (skip_uninstall || app_only || docs_only) { client_opts = instproxy_client_options_new(); if (skip_uninstall) { instproxy_client_options_add(client_opts, "SkipUninstall", 1, NULL); } if (app_only) { instproxy_client_options_add(client_opts, "ArchiveType", "ApplicationOnly", NULL); } else if (docs_only) { instproxy_client_options_add(client_opts, "ArchiveType", "DocumentsOnly", NULL); } } if (copy_path) { struct stat fst; if (stat(copy_path, &fst) != 0) { fprintf(stderr, "ERROR: stat: %s: %s\n", copy_path, strerror(errno)); goto leave_cleanup; } if (!S_ISDIR(fst.st_mode)) { fprintf(stderr, "ERROR: '%s' is not a directory as expected.\n", copy_path); goto leave_cleanup; } if (service) { lockdownd_service_descriptor_free(service); } service = NULL; if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || !service) { fprintf(stderr, "Could not start com.apple.afc!\n"); goto leave_cleanup; } lockdownd_client_free(client); client = NULL; if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) { fprintf(stderr, "Could not connect to AFC!\n"); goto leave_cleanup; } } instproxy_archive(ipc, cmdarg, client_opts, status_cb, NULL); instproxy_client_options_free(client_opts); wait_for_command_complete = 1; if (skip_uninstall) { notification_expected = 0; } else { notification_expected = 1; } idevice_wait_for_command_to_complete(); if (copy_path) { if (err_occurred) { afc_client_free(afc); afc = NULL; goto leave_cleanup; } FILE *f = NULL; uint64_t af = 0; /* local filename */ char *localfile = NULL; if (asprintf(&localfile, "%s/%s.ipa", copy_path, cmdarg) < 0) { fprintf(stderr, "Out of memory!?\n"); goto leave_cleanup; } f = fopen(localfile, "wb"); if (!f) { fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno)); free(localfile); goto leave_cleanup; } /* remote filename */ char *remotefile = NULL; if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, cmdarg) < 0) { fprintf(stderr, "Out of memory!?\n"); goto leave_cleanup; } uint32_t fsize = 0; char **fileinfo = NULL; if ((afc_get_file_info(afc, remotefile, &fileinfo) != AFC_E_SUCCESS) || !fileinfo) { fprintf(stderr, "ERROR getting AFC file info for '%s' on device!\n", remotefile); fclose(f); free(remotefile); free(localfile); goto leave_cleanup; } int i; for (i = 0; fileinfo[i]; i+=2) { if (!strcmp(fileinfo[i], "st_size")) { fsize = atoi(fileinfo[i+1]); break; } } i = 0; while (fileinfo[i]) { free(fileinfo[i]); i++; } free(fileinfo); if (fsize == 0) { fprintf(stderr, "Hm... remote file length could not be determined. Cannot copy.\n"); fclose(f); free(remotefile); free(localfile); goto leave_cleanup; } if ((afc_file_open(afc, remotefile, AFC_FOPEN_RDONLY, &af) != AFC_E_SUCCESS) || !af) { fclose(f); fprintf(stderr, "ERROR: could not open '%s' on device for reading!\n", remotefile); free(remotefile); free(localfile); goto leave_cleanup; } /* copy file over */ printf("Copying '%s' --> '%s'... ", remotefile, localfile); free(remotefile); free(localfile); uint32_t amount = 0; uint32_t total = 0; char buf[8192]; do { if (afc_file_read(afc, af, buf, sizeof(buf), &amount) != AFC_E_SUCCESS) { fprintf(stderr, "AFC Read error!\n"); break; } if (amount > 0) { size_t written = fwrite(buf, 1, amount, f); if (written != amount) { fprintf(stderr, "Error when writing %d bytes to local file!\n", amount); break; } total += written; } } while (amount > 0); afc_file_close(afc, af); fclose(f); printf("DONE.\n"); if (total != fsize) { fprintf(stderr, "WARNING: remote and local file sizes don't match (%d != %d)\n", fsize, total); if (remove_after_copy) { fprintf(stderr, "NOTE: archive file will NOT be removed from device\n"); remove_after_copy = 0; } } if (remove_after_copy) { /* remove archive if requested */ printf("Removing '%s'\n", cmdarg); cmd = CMD_REMOVE_ARCHIVE; if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller")) { fprintf(stderr, "Could not connect to lockdownd. Exiting.\n"); goto leave_cleanup; } goto run_again; } } goto leave_cleanup; } else if (cmd == CMD_RESTORE) { instproxy_restore(ipc, cmdarg, NULL, status_cb, NULL); wait_for_command_complete = 1; notification_expected = 1; } else if (cmd == CMD_REMOVE_ARCHIVE) { instproxy_remove_archive(ipc, cmdarg, NULL, status_cb, NULL); wait_for_command_complete = 1; } else { printf("ERROR: no command selected?! This should not be reached!\n"); res = 2; goto leave_cleanup; } /* not needed anymore */ lockdownd_client_free(client); client = NULL; idevice_wait_for_command_to_complete(); res = 0; leave_cleanup: np_client_free(np); instproxy_client_free(ipc); afc_client_free(afc); lockdownd_client_free(client); idevice_free(device); free(udid); free(copy_path); free(extsinf); free(extmeta); free(bundleidentifier); plist_free(bundle_ids); plist_free(return_attrs); if (err_occurred && !res) { res = 128; } return res; }