├── .github └── workflows │ └── build.yml ├── .gitignore ├── AUTHORS ├── COPYING ├── Makefile.am ├── NEWS ├── README.md ├── autogen.sh ├── configure.ac ├── git-version-gen ├── m4 └── as-compiler-flag.m4 ├── man ├── Makefile.am └── ideviceinstaller.1 └── src ├── Makefile.am └── ideviceinstaller.c /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '0 0 1 * *' 7 | 8 | jobs: 9 | build-linux-ubuntu: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: install dependencies 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install libzip-dev 16 | - name: prepare environment 17 | run: | 18 | echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV 19 | - name: fetch libplist 20 | uses: dawidd6/action-download-artifact@v6 21 | with: 22 | github_token: ${{secrets.GITHUB_TOKEN}} 23 | workflow: build.yml 24 | name: libplist-latest_${{env.target_triplet}} 25 | repo: libimobiledevice/libplist 26 | - name: fetch libusbmuxd 27 | uses: dawidd6/action-download-artifact@v6 28 | with: 29 | github_token: ${{secrets.GITHUB_TOKEN}} 30 | workflow: build.yml 31 | name: libusbmuxd-latest_${{env.target_triplet}} 32 | repo: libimobiledevice/libusbmuxd 33 | - name: fetch libimobiledevice-glue 34 | uses: dawidd6/action-download-artifact@v6 35 | with: 36 | github_token: ${{secrets.GITHUB_TOKEN}} 37 | workflow: build.yml 38 | name: libimobiledevice-glue-latest_${{env.target_triplet}} 39 | repo: libimobiledevice/libimobiledevice-glue 40 | - name: fetch libimobiledevice 41 | uses: dawidd6/action-download-artifact@v6 42 | with: 43 | github_token: ${{secrets.GITHUB_TOKEN}} 44 | workflow: build.yml 45 | name: libimobiledevice-latest_${{env.target_triplet}} 46 | repo: libimobiledevice/libimobiledevice 47 | - name: install external dependencies 48 | run: | 49 | mkdir extract 50 | for I in *.tar; do 51 | tar -C extract -xvf $I 52 | done 53 | rm -rf extract/lib 54 | sudo cp -r extract/* / 55 | sudo ldconfig 56 | - uses: actions/checkout@v4 57 | with: 58 | fetch-depth: 0 59 | - name: autogen 60 | run: ./autogen.sh PKG_CONFIG_PATH=/usr/local/lib/pkgconfig 61 | - name: make 62 | run: make 63 | - name: make install 64 | run: sudo make install 65 | - name: prepare artifact 66 | run: | 67 | mkdir -p dest 68 | DESTDIR=`pwd`/dest make install 69 | tar -C dest -cf ideviceinstaller.tar usr 70 | - name: publish artifact 71 | uses: actions/upload-artifact@v4 72 | with: 73 | name: ideviceinstaller-latest_${{env.target_triplet}} 74 | path: ideviceinstaller.tar 75 | build-macOS: 76 | runs-on: macOS-latest 77 | steps: 78 | - name: install dependencies 79 | run: | 80 | if test -x "`which port`"; then 81 | sudo port install libtool autoconf automake pkgconfig 82 | else 83 | brew install libtool autoconf automake pkgconfig 84 | fi 85 | shell: bash 86 | - name: fetch libplist 87 | uses: dawidd6/action-download-artifact@v6 88 | with: 89 | github_token: ${{secrets.GITHUB_TOKEN}} 90 | workflow: build.yml 91 | name: libplist-latest_macOS 92 | repo: libimobiledevice/libplist 93 | - name: fetch libusbmuxd 94 | uses: dawidd6/action-download-artifact@v6 95 | with: 96 | github_token: ${{secrets.GITHUB_TOKEN}} 97 | workflow: build.yml 98 | name: libusbmuxd-latest_macOS 99 | repo: libimobiledevice/libusbmuxd 100 | - name: fetch libimobiledevice-glue 101 | uses: dawidd6/action-download-artifact@v6 102 | with: 103 | github_token: ${{secrets.GITHUB_TOKEN}} 104 | workflow: build.yml 105 | name: libimobiledevice-glue-latest_macOS 106 | repo: libimobiledevice/libimobiledevice-glue 107 | - name: fetch libimobiledevice 108 | uses: dawidd6/action-download-artifact@v6 109 | with: 110 | github_token: ${{secrets.GITHUB_TOKEN}} 111 | workflow: build.yml 112 | name: libimobiledevice-latest_macOS 113 | repo: libimobiledevice/libimobiledevice 114 | - name: install external dependencies 115 | run: | 116 | mkdir extract 117 | for I in *.tar; do 118 | tar -C extract -xvf $I 119 | done 120 | sudo cp -r extract/* / 121 | - uses: actions/checkout@v4 122 | - name: install additional requirements 123 | run: | 124 | SDKDIR=`xcrun --sdk macosx --show-sdk-path 2>/dev/null` 125 | echo "SDKDIR=$SDKDIR" >> $GITHUB_ENV 126 | TESTARCHS="arm64 x86_64" 127 | USEARCHS= 128 | for ARCH in $TESTARCHS; do 129 | if echo "int main(int argc, char **argv) { return 0; }" |clang -arch $ARCH -o /dev/null -isysroot $SDKDIR -x c - 2>/dev/null; then 130 | USEARCHS="$USEARCHS -arch $ARCH" 131 | fi 132 | done 133 | export CFLAGS="$USEARCHS -isysroot $SDKDIR" 134 | echo "Using CFLAGS: $CFLAGS" 135 | echo "BUILD_CFLAGS=$CFLAGS" >> $GITHUB_ENV 136 | mkdir -p lib 137 | curl -o lib/libcrypto.35.tbd -Ls \ 138 | https://gist.github.com/nikias/94c99fd145a75a5104415e5117b0cafa/raw/5209dfbff5a871a14272afe4794e76eb4cf6f062/libcrypto.35.tbd 139 | curl -o lib/libssl.35.tbd -Ls \ 140 | https://gist.github.com/nikias/94c99fd145a75a5104415e5117b0cafa/raw/5209dfbff5a871a14272afe4794e76eb4cf6f062/libssl.35.tbd 141 | LIBRESSL_VER=2.2.7 142 | FILENAME="libressl-$LIBRESSL_VER.tar.gz" 143 | curl -o $FILENAME -Ls "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/$FILENAME" 144 | mkdir -p deps 145 | tar -C deps -xzf $FILENAME 146 | echo "LIBRESSL_CFLAGS=-I`pwd`/deps/libressl-$LIBRESSL_VER/include" >> $GITHUB_ENV 147 | echo "LIBRESSL_LIBS=-Xlinker `pwd`/lib/libssl.35.tbd -Xlinker `pwd`/lib/libcrypto.35.tbd" >> $GITHUB_ENV 148 | FILENAME="libzip-static.tar.bz2" 149 | curl -o $FILENAME.b64 -Ls "https://gist.github.com/nikias/3da15d03120382f87b44029cd8495a02/raw/99cd8138fed99e8f6530b6f179f787342c698e1f/libzip-1.7.1_static_macOS.tar.bz2" 150 | base64 -D < $FILENAME.b64 > $FILENAME 151 | tar -C deps -xjf $FILENAME 152 | echo "LIBZIP_CFLAGS=-I`pwd`/deps/include" >> $GITHUB_ENV 153 | echo "LIBZIP_LIBS=`pwd`/deps/lib/libzip.a -Xlinker ${SDKDIR}/usr/lib/libbz2.tbd -Xlinker ${SDKDIR}/usr/lib/liblzma.tbd -lz" >> $GITHUB_ENV 154 | - name: autogen 155 | run: | 156 | export CFLAGS="${{env.BUILD_CFLAGS}} -Wno-nullability-completeness -Wno-expansion-to-defined" 157 | echo "Using CFLAGS: $CFLAGS" 158 | ./autogen.sh PKG_CONFIG_PATH=/usr/local/lib/pkgconfig \ 159 | openssl_CFLAGS="$LIBRESSL_CFLAGS" openssl_LIBS="$LIBRESSL_LIBS" \ 160 | libzip_CFLAGS="$LIBZIP_CFLAGS -I${{env.SDKDIR}}/usr/include" libzip_LIBS="$LIBZIP_LIBS -lz" \ 161 | 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}}" 162 | - name: make 163 | run: make 164 | - name: make install 165 | run: sudo make install 166 | - name: prepare artifact 167 | run: | 168 | mkdir -p dest 169 | DESTDIR=`pwd`/dest make install 170 | tar -C dest -cf ideviceinstaller.tar usr 171 | - name: publish artifact 172 | uses: actions/upload-artifact@v4 173 | with: 174 | name: ideviceinstaller-latest_macOS 175 | path: ideviceinstaller.tar 176 | build-windows: 177 | runs-on: windows-2019 178 | defaults: 179 | run: 180 | shell: msys2 {0} 181 | strategy: 182 | fail-fast: false 183 | matrix: 184 | include: [ 185 | { msystem: MINGW64, arch: x86_64 }, 186 | { msystem: MINGW32, arch: i686 } 187 | ] 188 | steps: 189 | - uses: msys2/setup-msys2@v2 190 | with: 191 | msystem: ${{ matrix.msystem }} 192 | release: false 193 | update: false 194 | install: >- 195 | base-devel 196 | git 197 | mingw-w64-${{ matrix.arch }}-gcc 198 | make 199 | libtool 200 | autoconf 201 | automake-wrapper 202 | liblzma 203 | - name: prepare environment 204 | run: | 205 | dest=`echo ${{ matrix.msystem }} |tr [:upper:] [:lower:]` 206 | echo "dest=$dest" >> $GITHUB_ENV 207 | echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV 208 | - name: fetch libplist 209 | uses: dawidd6/action-download-artifact@v6 210 | with: 211 | github_token: ${{secrets.GITHUB_TOKEN}} 212 | workflow: build.yml 213 | name: libplist-latest_${{ matrix.arch }}-${{ env.dest }} 214 | repo: libimobiledevice/libplist 215 | - name: fetch libusbmuxd 216 | uses: dawidd6/action-download-artifact@v6 217 | with: 218 | github_token: ${{secrets.GITHUB_TOKEN}} 219 | workflow: build.yml 220 | name: libusbmuxd-latest_${{ matrix.arch }}-${{ env.dest }} 221 | repo: libimobiledevice/libusbmuxd 222 | - name: fetch libimobiledevice-glue 223 | uses: dawidd6/action-download-artifact@v6 224 | with: 225 | github_token: ${{secrets.GITHUB_TOKEN}} 226 | workflow: build.yml 227 | name: libimobiledevice-glue-latest_${{ matrix.arch }}-${{ env.dest }} 228 | repo: libimobiledevice/libimobiledevice-glue 229 | - name: fetch libimobiledevice 230 | uses: dawidd6/action-download-artifact@v6 231 | with: 232 | github_token: ${{secrets.GITHUB_TOKEN}} 233 | workflow: build.yml 234 | name: libimobiledevice-latest_${{ matrix.arch }}-${{ env.dest }} 235 | repo: libimobiledevice/libimobiledevice 236 | - name: install external dependencies 237 | run: | 238 | mkdir extract 239 | for I in *.tar; do 240 | tar -C extract -xvf $I 241 | done 242 | cp -r extract/* / 243 | - uses: actions/checkout@v4 244 | - name: install additional requirements 245 | run: | 246 | FILENAME="libzip-1.7.1-static.tar.bz2" 247 | curl -o $FILENAME.b64 -Ls "https://gist.github.com/nikias/3da15d03120382f87b44029cd8495a02/raw/99cd8138fed99e8f6530b6f179f787342c698e1f/libzip-1.7.1_static_${{matrix.arch}}-${{env.dest}}.tar.bz2" 248 | base64 -d < $FILENAME.b64 > $FILENAME 249 | mkdir deps 250 | tar -C deps -xjf $FILENAME 251 | echo "LIBZIP_CFLAGS=-I`pwd`/deps/include" >> $GITHUB_ENV 252 | echo "LIBZIP_LIBS=`pwd`/deps/lib/libzip.a /${{env.dest}}/lib/libbz2.a /${{env.dest}}/lib/liblzma.a " >> $GITHUB_ENV 253 | - name: autogen 254 | run: ./autogen.sh CC=gcc CXX=g++ libzip_CFLAGS="${{env.LIBZIP_CFLAGS}}" libzip_LIBS="${{env.LIBZIP_LIBS}} /${{env.dest}}/lib/libz.a" 255 | - name: make 256 | run: make 257 | - name: make install 258 | run: make install 259 | - name: prepare artifact 260 | run: | 261 | mkdir -p dest 262 | DESTDIR=`pwd`/dest make install 263 | tar -C dest -cf ideviceinstaller.tar ${{ env.dest }} 264 | - name: publish artifact 265 | uses: actions/upload-artifact@v4 266 | with: 267 | name: ideviceinstaller-latest_${{ matrix.arch }}-${{ env.dest }} 268 | path: ideviceinstaller.tar 269 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # git-ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | *.[oa] 6 | *~ 7 | *.po 8 | *.lo 9 | *.la 10 | autom4te.cache/* 11 | *.in 12 | */.deps/* 13 | m4/* 14 | *.swp 15 | aclocal.m4 16 | config.h 17 | config.log 18 | config.sub 19 | config.guess 20 | config.status 21 | configure 22 | depcomp 23 | install-sh 24 | compile 25 | main 26 | ltmain.sh 27 | missing 28 | mkinstalldirs 29 | libtool 30 | *Makefile 31 | stamp-h1 32 | src/.libs 33 | src/ideviceinstaller 34 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Julien Lavergne 2 | Keith Gable 3 | Martin Szulecki 4 | Nikias Bassen 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = foreign 2 | ACLOCAL_AMFLAGS = -I m4 3 | SUBDIRS = src man 4 | 5 | EXTRA_DIST = \ 6 | README.md \ 7 | git-version-gen 8 | 9 | dist-hook: 10 | @if ! git diff --quiet; then echo "Uncommitted changes present; not releasing"; exit 1; fi 11 | echo $(VERSION) > $(distdir)/.tarball-version 12 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Version 1.1.1 2 | ~~~~~~~~~~~~~ 3 | 4 | * Changes: 5 | - Bump autoconf requirement to 2.64 6 | - Bump libzip dependency to 0.10 7 | - Ignore .DS_Store and hidden files when parsing ZIP files 8 | - Fix device removal detection triggering on any device unplug 9 | - Improve excessive progress output 10 | - Return non-zero exit status on errors 11 | - Remove length check on UDID argument to support newer devices 12 | - Fix win32 build 13 | - Add "-n" option to make waiting on install/uninstall notification optional 14 | - Ignore SIGPIPE signal 15 | - Add "--network" and "--version" options to ideviceactivation tool 16 | - Bump libimobiledevice dependency to 1.3.0 17 | - Bump libplist dependency to 2.2.0 18 | - Improve README.md with project description, installation, contributing and 19 | usage sections 20 | 21 | Version 1.1.0 22 | ~~~~~~~~~~~~~ 23 | 24 | * Changes: 25 | - Fix installation of archives which are missing app directory zip file entry 26 | - Return non-zero exit status (128) when device error occurs 27 | - Add error checking for readlink use 28 | - Add support for installing from directories which contain symlinks 29 | - Plug a few memory leaks 30 | - Print AFC error code if writing fails 31 | - Fix possible buffer overflow with filename argument 32 | - Increase transfer buffer size for faster file uploads 33 | - Fix building with older libzip versions 34 | - Swap "-u" and "-U" arguments and print deprecation warning if still used 35 | - Improve error reporting on wrong usage 36 | - Replace "iPhone" wording with more general term "iOS device" 37 | - Remove waiting timeouts as they appear unreliable with large archives 38 | - Don't wait for a notification during uninstall as there is none sometimes 39 | - Improve command line output for more clarity and unification 40 | - Detect device removal and abort operation in that case 41 | - Turn some errors messages into warnings to not confuse users 42 | - Support iOS 7 correctly by passing "CFBundleIdentifier" option 43 | - Allow installation of developer apps by passing a ".app" directory 44 | - Add compatibility for libimobiledevice >= 1.1.5 45 | - Fix file operations for WIN32 46 | - Fix wrong usage description for upgrade command 47 | - Use CFBundleExecutable instead of CFBundleName to construct executable path 48 | - Rename "uuid" to correct "udid" abbreviation as used in other tools 49 | - Fix various issues with ZIP index and locating files in the archive 50 | - Improve detection of Info.plist in application archives 51 | - Fix compiler warnings 52 | - Allow creating app archives with just the documents/user data 53 | - Add support for CarrierBundle installation (.ipcc files) 54 | 55 | Version 1.0.1 56 | ~~~~~~~~~~~~~ 57 | 58 | * Changes: 59 | - Fix minor manpage typos 60 | - Fix build on OS X 61 | - Fix build warnings and make compilers happy 62 | 63 | Version 1.0.0 64 | ~~~~~~~~~~~~~ 65 | 66 | * First official public tarbal release 67 | * Features: 68 | - Install, upgrade, uninstall, archive, restore, and enumerate installed 69 | or archived apps on an iDevice 70 | 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ideviceinstaller 2 | 3 | *A command-line application to manage apps and app archives on iOS devices.* 4 | 5 | ![](https://github.com/libimobiledevice/ideviceinstaller/actions/workflows/build.yml/badge.svg) 6 | 7 | ## Features 8 | 9 | The ideviceinstaller application allows interacting with the app installation 10 | service of an iOS device. 11 | 12 | It makes use of the fabulous [libimobiledevice library](https://github.com/libimobiledevice/libimobiledevice) that allows 13 | communication with iOS devices. 14 | 15 | Some key features are: 16 | 17 | - **Status:** Install, upgrade, uninstall, and enumerate apps 18 | - **Browse**: Allows to retrieve a list of installed apps with filter options 19 | - **Install**: Supports app package, carrier bundle and developer .app directory 20 | - **Format**: Allows command output in plist, XML, or JSON format 21 | - **Compatibility**: Supports latest device firmware releases 22 | - **Cross-Platform:** Tested on Linux, macOS, Windows and Android platforms 23 | 24 | ## Installation / Getting started 25 | 26 | ### Debian / Ubuntu Linux 27 | 28 | First install all required dependencies and build tools: 29 | ```shell 30 | sudo apt-get install \ 31 | build-essential \ 32 | pkg-config \ 33 | checkinstall \ 34 | git \ 35 | autoconf \ 36 | automake \ 37 | libtool-bin \ 38 | libplist-dev \ 39 | libimobiledevice-dev \ 40 | libzip-dev \ 41 | usbmuxd 42 | ``` 43 | 44 | Continue with cloning the actual project repository: 45 | ```shell 46 | git clone https://github.com/libimobiledevice/ideviceinstaller.git 47 | cd ideviceinstaller 48 | ``` 49 | 50 | Now you can build and install it: 51 | ```shell 52 | ./autogen.sh 53 | make 54 | sudo make install 55 | ``` 56 | 57 | ## Usage 58 | 59 | First of all attach your device to your machine. 60 | 61 | Then simply run: 62 | ```shell 63 | ideviceinstaller list 64 | ``` 65 | 66 | This will print a list of `` identifiers (bundle identifiers) for use 67 | with other commands (see further below). 68 | 69 | To install an app from a package file use: 70 | ```shell 71 | ideviceinstaller install 72 | ``` 73 | 74 | To uninstall an app with the `` from the device use: 75 | ```shell 76 | ideviceinstaller uninstall 77 | ``` 78 | 79 | Please consult the usage information or manual page for a full documentation of 80 | available command line options: 81 | ```shell 82 | ideviceinstaller --help 83 | man ideviceinstaller 84 | ``` 85 | 86 | ## Contributing 87 | 88 | We welcome contributions from anyone and are grateful for every pull request! 89 | 90 | If you'd like to contribute, please fork the `master` branch, change, commit and 91 | send a pull request for review. Once approved it can be merged into the main 92 | code base. 93 | 94 | If you plan to contribute larger changes or a major refactoring, please create a 95 | ticket first to discuss the idea upfront to ensure less effort for everyone. 96 | 97 | Please make sure your contribution adheres to: 98 | * Try to follow the code style of the project 99 | * Commit messages should describe the change well without being too short 100 | * Try to split larger changes into individual commits of a common domain 101 | * Use your real name and a valid email address for your commits 102 | 103 | We are still working on the guidelines so bear with us! 104 | 105 | ## Links 106 | 107 | * Homepage: https://libimobiledevice.org/ 108 | * Repository: https://git.libimobiledevice.org/ideviceinstaller.git 109 | * Repository (Mirror): https://github.com/libimobiledevice/ideviceinstaller.git 110 | * Issue Tracker: https://github.com/libimobiledevice/ideviceinstaller/issues 111 | * Mailing List: https://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel 112 | * Twitter: https://twitter.com/libimobiledev 113 | 114 | ## License 115 | 116 | This software is licensed under the [GNU General Public License v2.0](https://www.gnu.org/licenses/gpl-2.0.en.html), 117 | also included in the repository in the `COPYING` file. 118 | 119 | ## Credits 120 | 121 | Apple, iPhone, iPad, iPod, iPod Touch, Apple TV, Apple Watch, Mac, iOS, 122 | iPadOS, tvOS, watchOS, and macOS are trademarks of Apple Inc. 123 | 124 | ideviceinstaller is an independent software application and has not been 125 | authorized, sponsored or otherwise approved by Apple Inc. 126 | 127 | README Updated on: 2023-07-20 128 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | olddir=`pwd` 4 | srcdir=`dirname $0` 5 | test -z "$srcdir" && srcdir=. 6 | 7 | ( 8 | cd "$srcdir" 9 | 10 | gprefix=`which glibtoolize 2>&1 >/dev/null` 11 | if [ $? -eq 0 ]; then 12 | glibtoolize --force 13 | else 14 | libtoolize --force 15 | fi 16 | aclocal -I m4 17 | autoheader 18 | automake --add-missing 19 | autoconf 20 | 21 | cd "$olddir" 22 | ) 23 | 24 | if [ -z "$NOCONFIGURE" ]; then 25 | $srcdir/configure "$@" 26 | fi 27 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ([2.68]) 5 | AC_INIT([ideviceinstaller], [m4_esyscmd(./git-version-gen $RELEASE_VERSION)], [https://github.com/libimobiledevice/ideviceinstaller/issues], [], [https://libimobiledevice.org]) 6 | AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip check-news]) 7 | m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES]) 8 | AC_CONFIG_SRCDIR([src/]) 9 | AC_CONFIG_HEADERS([config.h]) 10 | AC_CONFIG_MACRO_DIR([m4]) 11 | 12 | # Check if we have a version defined 13 | if test -z $PACKAGE_VERSION; then 14 | 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.]) 15 | fi 16 | 17 | # Checks for programs. 18 | AC_PROG_CC 19 | AM_PROG_CC_C_O 20 | LT_INIT 21 | 22 | # Checks for libraries. 23 | PKG_CHECK_MODULES(libimobiledevice, libimobiledevice-1.0 >= 1.3.0) 24 | PKG_CHECK_MODULES(libplist, libplist-2.0 >= 2.3.0) 25 | PKG_CHECK_MODULES(libzip, libzip >= 0.10) 26 | 27 | # Checks for header files. 28 | AC_CHECK_HEADERS([stdint.h stdlib.h string.h]) 29 | 30 | # Checks for typedefs, structures, and compiler characteristics. 31 | AC_C_CONST 32 | AC_TYPE_SIZE_T 33 | AC_TYPE_SSIZE_T 34 | AC_TYPE_UINT16_T 35 | AC_TYPE_UINT32_T 36 | AC_TYPE_UINT8_T 37 | 38 | # Checks for library functions. 39 | AC_FUNC_MALLOC 40 | AC_FUNC_REALLOC 41 | AC_CHECK_FUNCS([strdup strerror asprintf vasprintf]) 42 | 43 | # Check for lstat 44 | 45 | AC_MSG_CHECKING([whether lstat is available]) 46 | AC_LINK_IFELSE([AC_LANG_PROGRAM([[ 47 | #include 48 | #include 49 | #if defined(HAVE_UNISTD_H) 50 | #include 51 | #endif 52 | ]],[[ 53 | struct stat st; 54 | lstat("/tmp", &st); 55 | ]])], [have_lstat="yes"], [have_lstat="no"]) 56 | AC_MSG_RESULT([${have_lstat}]) 57 | 58 | if test "x${have_lstat}" = "xyes" ; then 59 | AC_DEFINE([HAVE_LSTAT], 1, [Define if lstat syscall is supported]) 60 | fi 61 | 62 | AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wmissing-declarations -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Werror -g") 63 | AC_SUBST(GLOBAL_CFLAGS) 64 | 65 | m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) 66 | 67 | AC_CONFIG_FILES([ 68 | Makefile 69 | src/Makefile 70 | man/Makefile 71 | ]) 72 | AC_OUTPUT 73 | 74 | echo " 75 | Configuration for $PACKAGE $VERSION: 76 | ------------------------------------------- 77 | 78 | Install prefix: .........: $prefix 79 | 80 | Now type 'make' to build $PACKAGE $VERSION, 81 | and then 'make install' for installation. 82 | " 83 | -------------------------------------------------------------------------------- /git-version-gen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SRCDIR=`dirname $0` 3 | if test -n "$1"; then 4 | VER=$1 5 | else 6 | if test -r "${SRCDIR}/.git" && test -x "`which git`" ; then 7 | git update-index -q --refresh 8 | if ! VER=`git describe --tags --dirty 2>/dev/null`; then 9 | COMMIT=`git rev-parse --short HEAD` 10 | DIRTY=`git diff --quiet HEAD || echo "-dirty"` 11 | VER=`sed -n '1,/RE/s/Version \(.*\)/\1/p' ${SRCDIR}/NEWS`-git-${COMMIT}${DIRTY} 12 | fi 13 | else 14 | if test -f "${SRCDIR}/.tarball-version"; then 15 | VER=`cat "${SRCDIR}/.tarball-version"` 16 | fi 17 | fi 18 | fi 19 | VER=`printf %s "$VER" | head -n1` 20 | printf %s "$VER" 21 | -------------------------------------------------------------------------------- /m4/as-compiler-flag.m4: -------------------------------------------------------------------------------- 1 | dnl as-compiler-flag.m4 0.1.0 2 | 3 | dnl autostars m4 macro for detection of compiler flags 4 | 5 | dnl David Schleef 6 | 7 | dnl $Id: as-compiler-flag.m4,v 1.1 2005/12/15 23:35:19 ds Exp $ 8 | 9 | dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED]) 10 | dnl Tries to compile with the given CFLAGS. 11 | dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags, 12 | dnl and ACTION-IF-NOT-ACCEPTED otherwise. 13 | 14 | AC_DEFUN([AS_COMPILER_FLAG], 15 | [ 16 | AC_MSG_CHECKING([to see if compiler understands $1]) 17 | 18 | save_CFLAGS="$CFLAGS" 19 | CFLAGS="$CFLAGS $1" 20 | 21 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) 22 | CFLAGS="$save_CFLAGS" 23 | 24 | if test "X$flag_ok" = Xyes ; then 25 | m4_ifvaln([$2],[$2]) 26 | true 27 | else 28 | m4_ifvaln([$3],[$3]) 29 | true 30 | fi 31 | AC_MSG_RESULT([$flag_ok]) 32 | ]) 33 | 34 | dnl AS_COMPILER_FLAGS(VAR, FLAGS) 35 | dnl Tries to compile with the given CFLAGS. 36 | 37 | AC_DEFUN([AS_COMPILER_FLAGS], 38 | [ 39 | list=$2 40 | flags_supported="" 41 | flags_unsupported="" 42 | AC_MSG_CHECKING([for supported compiler flags]) 43 | for each in $list 44 | do 45 | save_CFLAGS="$CFLAGS" 46 | CFLAGS="$CFLAGS $each" 47 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) 48 | CFLAGS="$save_CFLAGS" 49 | 50 | if test "X$flag_ok" = Xyes ; then 51 | flags_supported="$flags_supported $each" 52 | else 53 | flags_unsupported="$flags_unsupported $each" 54 | fi 55 | done 56 | AC_MSG_RESULT([$flags_supported]) 57 | if test "X$flags_unsupported" != X ; then 58 | AC_MSG_WARN([unsupported compiler flags: $flags_unsupported]) 59 | fi 60 | $1="$$1 $flags_supported" 61 | ]) 62 | 63 | -------------------------------------------------------------------------------- /man/Makefile.am: -------------------------------------------------------------------------------- 1 | man_MANS = ideviceinstaller.1 2 | EXTRA_DIST = $(man_MANS) 3 | -------------------------------------------------------------------------------- /man/ideviceinstaller.1: -------------------------------------------------------------------------------- 1 | .TH "ideviceinstaller" 1 2 | .SH NAME 3 | ideviceinstaller \- Manage apps on iOS devices. 4 | .SH SYNOPSIS 5 | .B ideviceinstaller 6 | [OPTIONS] 7 | 8 | .SH DESCRIPTION 9 | 10 | Allows to enumerate, install, upgrade, and uninstall apps on iOS devices. 11 | 12 | .SH COMMANDS 13 | .TP 14 | .B list 15 | List installed apps on the device. Options: 16 | .RS 17 | .TP 18 | .B \-\-user 19 | List user apps only (apps installed by the user). 20 | .B This is the default. 21 | .TP 22 | .B \-\-system 23 | List system apps only (apps available from the system firmware). 24 | .TP 25 | .B \-\-all 26 | List all types of apps. 27 | .TP 28 | .B \-\-xml 29 | Print output as XML Property List. 30 | .TP 31 | .B \-a, \-\-attribute ATTR 32 | 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: 33 | .RS 34 | .TP 35 | \f[B]StaticDiskUsage\f[] disk usage of installed app 36 | .TP 37 | \f[B]DynamicDiskUsage\f[] app user data disk usage 38 | .TP 39 | \f[B]Path\f[] app installation location 40 | .TP 41 | \f[B]SignerIdentity\f[] code signing identity 42 | .TP 43 | NOTE: It is suggested to always add CFBundleIdentifier to allow unique identification of the apps. 44 | .RE 45 | .TP 46 | .B \-b, \-\-bundle\-identifier BUNDLEID 47 | Only query given bundle identifier. This argument can be passed multiple times. 48 | .RE 49 | .TP 50 | .B install PATH 51 | Install app from a package file specified by PATH. PATH can also be a .ipcc 52 | file for carrier bundle installation or a .app directory for developer 53 | app installation. 54 | .RS 55 | .TP 56 | .B \-s, \-\-sinf PATH 57 | Pass an external SINF file located at PATH. 58 | .TP 59 | .B \-m, \-\-metadata PATH 60 | Pass an external iTunesMetadata file located at PATH. 61 | .RE 62 | 63 | .TP 64 | .B uninstall BUNDLEID 65 | Uninstall app specified by BUNDLEID. 66 | 67 | .TP 68 | .B upgrade PATH 69 | Upgrade app from a package file specified by PATH. 70 | 71 | .SH LEGACY COMMANDS 72 | The following commands are non-functional with iOS 7 or later. 73 | .TP 74 | .B archive BUNDLEID 75 | Archive app specified by BUNDLEID. Options: 76 | .RS 77 | .TP 78 | .B \-\-uninstall 79 | Uninstall the package after making an archive 80 | .TP 81 | .B \-\-app_only 82 | Archive application data only 83 | .TP 84 | .B \-\-docs_only 85 | Archive documents (user data) only 86 | .TP 87 | .B \-\-copy=PATH 88 | Copy the app archive to directory PATH when done 89 | .TP 90 | .B \-\-remove 91 | Only valid when copy=PATH is used: remove after copy 92 | .RE 93 | 94 | .TP 95 | .B restore BUNDLEID 96 | Restore archived app specified by BUNDLEID. 97 | 98 | .TP 99 | .B list-archives 100 | List archived apps on the device. Options: 101 | .RS 102 | .TP 103 | .B \-\-xml 104 | Print output as XML Property List. 105 | .RE 106 | 107 | .TP 108 | .B remove-archive BUNDLEID 109 | Remove app archive specified by BUNDLEID. 110 | 111 | .SH OPTIONS 112 | .TP 113 | .B \-u, \-\-udid UDID 114 | Target specific device by UDID. 115 | .TP 116 | .B \-n, \-\-network 117 | Connect to network device. 118 | .TP 119 | .B \-w, \-\-notify-wait 120 | Wait for app installed/uninstalled notification before reporting success of operation. 121 | .TP 122 | .B \-h, \-\-help 123 | Print usage information. 124 | .TP 125 | .B \-d, \-\-debug 126 | Enable communication debugging. 127 | .TP 128 | .B \-v, \-\-version 129 | Print version information. 130 | 131 | .SH AUTHORS 132 | Nikias Bassen 133 | 134 | Martin Szulecki 135 | 136 | .SH ON THE WEB 137 | https://libimobiledevice.org 138 | 139 | https://github.com/libimobiledevice/ideviceinstaller 140 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS = \ 2 | $(GLOBAL_CFLAGS) \ 3 | $(libimobiledevice_CFLAGS) \ 4 | $(libglib2_CFLAGS) \ 5 | $(libplist_CFLAGS) \ 6 | $(libzip_CFLAGS) 7 | 8 | AM_LDFLAGS = \ 9 | $(libimobiledevice_LIBS) \ 10 | $(libglib2_LIBS) \ 11 | $(libplist_LIBS) \ 12 | $(libzip_LIBS) 13 | 14 | bin_PROGRAMS = ideviceinstaller 15 | 16 | ideviceinstaller_SOURCES = ideviceinstaller.c 17 | ideviceinstaller_CFLAGS = $(AM_CFLAGS) 18 | ideviceinstaller_LDFLAGS = $(AM_LDFLAGS) 19 | 20 | -------------------------------------------------------------------------------- /src/ideviceinstaller.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ideviceinstaller - Manage apps on iOS devices. 3 | * 4 | * Copyright (C) 2010-2023 Nikias Bassen 5 | * Copyright (C) 2010-2015 Martin Szulecki 6 | * 7 | * Licensed under the GNU General Public License Version 2 8 | * 9 | * This program is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more profile. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 22 | * USA 23 | */ 24 | #ifdef HAVE_CONFIG_H 25 | #include 26 | #endif 27 | #include 28 | #define _GNU_SOURCE 1 29 | #define __USE_GNU 1 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #ifdef HAVE_UNISTD_H 41 | #include 42 | #endif 43 | #ifndef WIN32 44 | #include 45 | #endif 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #include 54 | 55 | #include 56 | 57 | #ifdef WIN32 58 | #include 59 | #define wait_ms(x) Sleep(x) 60 | #else 61 | #define wait_ms(x) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = x * 1000000; nanosleep(&ts, NULL); } 62 | #endif 63 | 64 | #ifndef HAVE_VASPRINTF 65 | static int vasprintf(char **PTR, const char *TEMPLATE, va_list AP) 66 | { 67 | int res; 68 | char buf[16]; 69 | res = vsnprintf(buf, 16, TEMPLATE, AP); 70 | if (res > 0) { 71 | *PTR = (char*)malloc(res+1); 72 | res = vsnprintf(*PTR, res+1, TEMPLATE, AP); 73 | } 74 | return res; 75 | } 76 | #endif 77 | 78 | #ifndef HAVE_ASPRINTF 79 | static int asprintf(char **PTR, const char *TEMPLATE, ...) 80 | { 81 | int res; 82 | va_list AP; 83 | va_start(AP, TEMPLATE); 84 | res = vasprintf(PTR, TEMPLATE, AP); 85 | va_end(AP); 86 | return res; 87 | } 88 | #endif 89 | 90 | #define ITUNES_METADATA_PLIST_FILENAME "iTunesMetadata.plist" 91 | 92 | const char PKG_PATH[] = "PublicStaging"; 93 | const char APPARCH_PATH[] = "ApplicationArchives"; 94 | 95 | char *udid = NULL; 96 | char *cmdarg = NULL; 97 | char *extsinf = NULL; 98 | char *extmeta = NULL; 99 | 100 | enum cmd_mode { 101 | CMD_NONE = 0, 102 | CMD_LIST_APPS, 103 | CMD_INSTALL, 104 | CMD_UNINSTALL, 105 | CMD_UPGRADE, 106 | CMD_LIST_ARCHIVES, 107 | CMD_ARCHIVE, 108 | CMD_RESTORE, 109 | CMD_REMOVE_ARCHIVE 110 | }; 111 | 112 | int cmd = CMD_NONE; 113 | 114 | char *last_status = NULL; 115 | int wait_for_command_complete = 0; 116 | int use_network = 0; 117 | int use_notifier = 0; 118 | int notification_expected = 0; 119 | int is_device_connected = 0; 120 | int command_completed = 0; 121 | int ignore_events = 0; 122 | int err_occurred = 0; 123 | int notified = 0; 124 | plist_t bundle_ids = NULL; 125 | plist_t return_attrs = NULL; 126 | #define FORMAT_XML 1 127 | #define FORMAT_JSON 2 128 | int output_format = 0; 129 | int opt_list_user = 0; 130 | int opt_list_system = 0; 131 | char *copy_path = NULL; 132 | int remove_after_copy = 0; 133 | int skip_uninstall = 1; 134 | int app_only = 0; 135 | int docs_only = 0; 136 | 137 | static void print_apps_header() 138 | { 139 | if (!return_attrs) { 140 | return; 141 | } 142 | uint32_t i = 0; 143 | for (i = 0; i < plist_array_get_size(return_attrs); i++) { 144 | plist_t node = plist_array_get_item(return_attrs, i); 145 | if (i > 0) { 146 | printf(", "); 147 | } 148 | printf("%s", plist_get_string_ptr(node, NULL)); 149 | } 150 | printf("\n"); 151 | } 152 | 153 | static void print_apps(plist_t apps) 154 | { 155 | if (!return_attrs) { 156 | return; 157 | } 158 | uint32_t i = 0; 159 | for (i = 0; i < plist_array_get_size(apps); i++) { 160 | plist_t app = plist_array_get_item(apps, i); 161 | uint32_t j = 0; 162 | for (j = 0; j < plist_array_get_size(return_attrs); j++) { 163 | plist_t node = plist_array_get_item(return_attrs, j); 164 | if (j > 0) { 165 | printf(", "); 166 | } 167 | const char* key = plist_get_string_ptr(node, NULL); 168 | node = plist_dict_get_item(app, key); 169 | if (node) { 170 | if (!strcmp(key, "CFBundleIdentifier")) { 171 | printf("%s", plist_get_string_ptr(node, NULL)); 172 | } else { 173 | uint64_t uval = 0; 174 | switch (plist_get_node_type(node)) { 175 | case PLIST_STRING: 176 | printf("\"%s\"", plist_get_string_ptr(node, NULL)); 177 | break; 178 | case PLIST_INT: 179 | plist_get_uint_val(node, &uval); 180 | printf("%" PRIu64, uval); 181 | break; 182 | case PLIST_BOOLEAN: 183 | printf("%s", plist_bool_val_is_true(node) ? "true" : "false"); 184 | break; 185 | case PLIST_ARRAY: 186 | printf("(array)"); 187 | break; 188 | case PLIST_DICT: 189 | printf("(dict)"); 190 | break; 191 | default: 192 | break; 193 | } 194 | } 195 | } 196 | } 197 | printf("\n"); 198 | } 199 | } 200 | 201 | static void notifier(const char *notification, void *unused) 202 | { 203 | notified = 1; 204 | } 205 | 206 | static void status_cb(plist_t command, plist_t status, void *unused) 207 | { 208 | if (command && status) { 209 | char* command_name = NULL; 210 | instproxy_command_get_name(command, &command_name); 211 | 212 | /* get status */ 213 | char *status_name = NULL; 214 | instproxy_status_get_name(status, &status_name); 215 | 216 | if (status_name) { 217 | if (!strcmp(status_name, "Complete")) { 218 | command_completed = 1; 219 | } 220 | } 221 | 222 | /* get error if any */ 223 | char* error_name = NULL; 224 | char* error_description = NULL; 225 | uint64_t error_code = 0; 226 | instproxy_status_get_error(status, &error_name, &error_description, &error_code); 227 | 228 | /* output/handling */ 229 | if (!error_name) { 230 | if (!strcmp(command_name, "Browse")) { 231 | uint64_t total = 0; 232 | uint64_t current_index = 0; 233 | uint64_t current_amount = 0; 234 | plist_t current_list = NULL; 235 | instproxy_status_get_current_list(status, &total, ¤t_index, ¤t_amount, ¤t_list); 236 | if (current_list) { 237 | print_apps(current_list); 238 | plist_free(current_list); 239 | } 240 | } else if (status_name) { 241 | /* get progress if any */ 242 | int percent = -1; 243 | instproxy_status_get_percent_complete(status, &percent); 244 | 245 | if (last_status && (strcmp(last_status, status_name))) { 246 | printf("\n"); 247 | } 248 | 249 | if (percent >= 0) { 250 | printf("\r%s: %s (%d%%)", command_name, status_name, percent); 251 | } else { 252 | printf("\r%s: %s", command_name, status_name); 253 | } 254 | if (command_completed) { 255 | printf("\n"); 256 | } 257 | } 258 | } else { 259 | /* report error to the user */ 260 | if (error_description) 261 | 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"); 262 | else 263 | fprintf(stderr, "ERROR: %s failed. Got error \"%s\".\n", command_name, error_name); 264 | err_occurred = 1; 265 | } 266 | 267 | /* clean up */ 268 | free(error_name); 269 | free(error_description); 270 | 271 | free(last_status); 272 | last_status = status_name; 273 | 274 | free(command_name); 275 | command_name = NULL; 276 | } else { 277 | fprintf(stderr, "ERROR: %s was called with invalid arguments!\n", __func__); 278 | } 279 | } 280 | 281 | static int zip_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len) 282 | { 283 | struct zip_stat zs; 284 | struct zip_file *zfile; 285 | int zindex = zip_name_locate(zf, filename, locate_flags); 286 | 287 | *buffer = NULL; 288 | *len = 0; 289 | 290 | if (zindex < 0) { 291 | return -1; 292 | } 293 | 294 | zip_stat_init(&zs); 295 | 296 | if (zip_stat_index(zf, zindex, 0, &zs) != 0) { 297 | fprintf(stderr, "ERROR: zip_stat_index '%s' failed!\n", filename); 298 | return -2; 299 | } 300 | 301 | if (zs.size > 10485760) { 302 | fprintf(stderr, "ERROR: file '%s' is too large!\n", filename); 303 | return -3; 304 | } 305 | 306 | zfile = zip_fopen_index(zf, zindex, 0); 307 | if (!zfile) { 308 | fprintf(stderr, "ERROR: zip_fopen '%s' failed!\n", filename); 309 | return -4; 310 | } 311 | 312 | *buffer = malloc(zs.size); 313 | if (zs.size > LLONG_MAX || zip_fread(zfile, *buffer, zs.size) != (zip_int64_t)zs.size) { 314 | fprintf(stderr, "ERROR: zip_fread %" PRIu64 " bytes from '%s'\n", (uint64_t)zs.size, filename); 315 | free(*buffer); 316 | *buffer = NULL; 317 | zip_fclose(zfile); 318 | return -5; 319 | } 320 | *len = zs.size; 321 | zip_fclose(zfile); 322 | return 0; 323 | } 324 | 325 | static int zip_get_app_directory(struct zip* zf, char** path) 326 | { 327 | zip_int64_t i = 0; 328 | zip_int64_t c = (zip_int64_t)zip_get_num_entries(zf, 0); 329 | int len = 0; 330 | const char* name = NULL; 331 | 332 | /* look through all filenames in the archive */ 333 | do { 334 | /* get filename at current index */ 335 | name = zip_get_name(zf, i++, 0); 336 | if (name != NULL) { 337 | /* check if we have a "Payload/.../" name */ 338 | len = strlen(name); 339 | if (!strncmp(name, "Payload/", 8) && (len > 8)) { 340 | /* skip hidden files */ 341 | if (name[8] == '.') 342 | continue; 343 | 344 | /* locate the second directory delimiter */ 345 | const char* p = name + 8; 346 | do { 347 | if (*p == '/') { 348 | break; 349 | } 350 | } while(p++ != NULL); 351 | 352 | /* try next entry if not found */ 353 | if (p == NULL) 354 | continue; 355 | 356 | len = p - name + 1; 357 | 358 | /* make sure app directory endwith .app */ 359 | if (len < 12 || strncmp(p - 4, ".app", 4)) 360 | { 361 | continue; 362 | } 363 | 364 | if (path != NULL) { 365 | free(*path); 366 | *path = NULL; 367 | } 368 | 369 | /* allocate and copy filename */ 370 | *path = (char*)malloc(len + 1); 371 | strncpy(*path, name, len); 372 | 373 | /* add terminating null character */ 374 | char* t = *path + len; 375 | *t = '\0'; 376 | break; 377 | } 378 | } 379 | } while(i < c); 380 | 381 | if (*path == NULL) { 382 | return -1; 383 | } 384 | 385 | return 0; 386 | } 387 | 388 | static void idevice_event_callback(const idevice_event_t* event, void* userdata) 389 | { 390 | if (ignore_events) { 391 | return; 392 | } 393 | if (event->event == IDEVICE_DEVICE_REMOVE) { 394 | if (!strcmp(udid, event->udid)) { 395 | fprintf(stderr, "ideviceinstaller: Device removed\n"); 396 | is_device_connected = 0; 397 | } 398 | } 399 | } 400 | 401 | static void idevice_wait_for_command_to_complete() 402 | { 403 | is_device_connected = 1; 404 | ignore_events = 0; 405 | 406 | /* subscribe to make sure to exit on device removal */ 407 | idevice_event_subscribe(idevice_event_callback, NULL); 408 | 409 | /* wait for command to complete */ 410 | while (wait_for_command_complete && !command_completed && !err_occurred 411 | && is_device_connected) { 412 | wait_ms(50); 413 | } 414 | 415 | /* wait some time if a notification is expected */ 416 | while (use_notifier && notification_expected && !notified && !err_occurred && is_device_connected) { 417 | wait_ms(50); 418 | } 419 | 420 | ignore_events = 1; 421 | idevice_event_unsubscribe(); 422 | } 423 | 424 | static void print_usage(int argc, char **argv, int is_error) 425 | { 426 | char *name = strrchr(argv[0], '/'); 427 | fprintf((is_error) ? stderr : stdout, "Usage: %s OPTIONS\n", (name ? name + 1 : argv[0])); 428 | fprintf((is_error) ? stderr : stdout, 429 | "\n" 430 | "Manage apps on iOS devices.\n" 431 | "\n" 432 | "COMMANDS:\n" 433 | " list List installed apps. Options:\n" 434 | " --user List user apps only (this is the default)\n" 435 | " --system List system apps only\n" 436 | " --all List all types of apps\n" 437 | " --xml Print output as XML Property List\n" 438 | " -a, --attribute ATTR Specify attribute to return - see man page\n" 439 | " (can be passed multiple times)\n" 440 | " -b, --bundle-identifier BUNDLEID Only query given bundle identifier\n" 441 | " (can be passed multiple times)\n" 442 | " install PATH Install app from package file specified by PATH.\n" 443 | " PATH can also be a .ipcc file for carrier bundles.\n" 444 | " -s, --sinf PATH Pass an external SINF file\n" 445 | " -m, --metadata PATH Pass an external iTunesMetadata file\n" 446 | " uninstall BUNDLEID Uninstall app specified by BUNDLEID.\n" 447 | " upgrade PATH Upgrade app from package file specified by PATH.\n" 448 | "\n" 449 | "LEGACY COMMANDS (non-functional with iOS 7 or later):\n" 450 | " archive BUNDLEID Archive app specified by BUNDLEID. Options:\n" 451 | " --uninstall Uninstall the package after making an archive\n" 452 | " --app-only Archive application data only\n" 453 | " --docs-only Archive documents (user data) only\n" 454 | " --copy=PATH Copy the app archive to directory PATH when done\n" 455 | " --remove Only valid when copy=PATH is used: remove after copy\n" 456 | " restore BUNDLEID Restore archived app specified by BUNDLEID\n" 457 | " list-archives List archived apps. Options:\n" 458 | " --xml Print output as XML Property List\n" 459 | " remove-archive BUNDLEID Remove app archive specified by BUNDLEID\n" 460 | "\n" 461 | "OPTIONS:\n" 462 | " -u, --udid UDID Target specific device by UDID\n" 463 | " -n, --network Connect to network device\n" 464 | " -w, --notify-wait Wait for app installed/uninstalled notification\n" 465 | " before reporting success of operation\n" 466 | " -h, --help Print usage information\n" 467 | " -d, --debug Enable communication debugging\n" 468 | " -v, --version Print version information\n" 469 | "\n" 470 | "Homepage: <" PACKAGE_URL ">\n" 471 | "Bug Reports: <" PACKAGE_BUGREPORT ">\n" 472 | ); 473 | } 474 | 475 | enum numerical_opts { 476 | LIST_USER = 1, 477 | LIST_SYSTEM, 478 | LIST_ALL, 479 | ARCHIVE_UNINSTALL, 480 | ARCHIVE_APP_ONLY, 481 | ARCHIVE_DOCS_ONLY, 482 | ARCHIVE_COPY_PATH, 483 | ARCHIVE_COPY_REMOVE, 484 | OUTPUT_XML, 485 | OUTPUT_JSON 486 | }; 487 | 488 | static void parse_opts(int argc, char **argv) 489 | { 490 | static struct option longopts[] = { 491 | { "help", no_argument, NULL, 'h' }, 492 | { "udid", required_argument, NULL, 'u' }, 493 | { "network", no_argument, NULL, 'n' }, 494 | { "notify-wait", no_argument, NULL, 'w' }, 495 | { "debug", no_argument, NULL, 'd' }, 496 | { "version", no_argument, NULL, 'v' }, 497 | { "bundle-identifier", required_argument, NULL, 'b' }, 498 | { "attribute", required_argument, NULL, 'a' }, 499 | { "user", no_argument, NULL, LIST_USER }, 500 | { "system", no_argument, NULL, LIST_SYSTEM }, 501 | { "all", no_argument, NULL, LIST_ALL }, 502 | { "xml", no_argument, NULL, OUTPUT_XML }, 503 | { "json", no_argument, NULL, OUTPUT_JSON }, 504 | { "sinf", required_argument, NULL, 's' }, 505 | { "metadata", required_argument, NULL, 'm' }, 506 | { "uninstall", no_argument, NULL, ARCHIVE_UNINSTALL }, 507 | { "app-only", no_argument, NULL, ARCHIVE_APP_ONLY }, 508 | { "docs-only", no_argument, NULL, ARCHIVE_DOCS_ONLY }, 509 | { "copy", required_argument, NULL, ARCHIVE_COPY_PATH }, 510 | { "remove", no_argument, NULL, ARCHIVE_COPY_REMOVE }, 511 | { NULL, 0, NULL, 0 } 512 | }; 513 | int c; 514 | 515 | while (1) { 516 | c = getopt_long(argc, argv, "hu:nwdvb:a:s:m:", longopts, (int*)0); 517 | if (c == -1) { 518 | break; 519 | } 520 | 521 | switch (c) { 522 | case 'h': 523 | print_usage(argc, argv, 0); 524 | exit(0); 525 | case 'u': 526 | if (!*optarg) { 527 | printf("ERROR: UDID must not be empty!\n"); 528 | print_usage(argc, argv, 1); 529 | exit(2); 530 | } 531 | udid = strdup(optarg); 532 | break; 533 | case 'n': 534 | use_network = 1; 535 | break; 536 | case 'a': 537 | if (!*optarg) { 538 | printf("ERROR: attribute must not be empty!\n"); 539 | print_usage(argc, argv, 1); 540 | exit(2); 541 | } 542 | if (return_attrs == NULL) { 543 | return_attrs = plist_new_array(); 544 | } 545 | plist_array_append_item(return_attrs, plist_new_string(optarg)); 546 | break; 547 | case 'b': 548 | if (!*optarg) { 549 | printf("ERROR: bundle identifier must not be empty!\n"); 550 | print_usage(argc, argv, 1); 551 | exit(2); 552 | } 553 | if (bundle_ids == NULL) { 554 | bundle_ids = plist_new_array(); 555 | } 556 | plist_array_append_item(bundle_ids, plist_new_string(optarg)); 557 | break; 558 | case 's': 559 | if (!*optarg) { 560 | printf("ERROR: path for --sinf must not be empty!\n"); 561 | print_usage(argc, argv, 1); 562 | exit(2); 563 | } 564 | extsinf = strdup(optarg); 565 | break; 566 | case 'm': 567 | if (!*optarg) { 568 | printf("ERROR: path for --metadata must not be empty!\n"); 569 | print_usage(argc, argv, 1); 570 | exit(2); 571 | } 572 | extmeta = strdup(optarg); 573 | break; 574 | case 'w': 575 | use_notifier = 1; 576 | break; 577 | case 'd': 578 | idevice_set_debug_level(1); 579 | break; 580 | case 'v': 581 | printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); 582 | exit(0); 583 | case LIST_USER: 584 | opt_list_user = 1; 585 | break; 586 | case LIST_SYSTEM: 587 | opt_list_system = 1; 588 | break; 589 | case LIST_ALL: 590 | opt_list_user = 1; 591 | opt_list_system = 1; 592 | break; 593 | case OUTPUT_XML: 594 | output_format = FORMAT_XML; 595 | break; 596 | case OUTPUT_JSON: 597 | output_format = FORMAT_JSON; 598 | break; 599 | case ARCHIVE_UNINSTALL: 600 | skip_uninstall = 0; 601 | break; 602 | case ARCHIVE_APP_ONLY: 603 | app_only = 1; 604 | docs_only = 0; 605 | break; 606 | case ARCHIVE_DOCS_ONLY: 607 | docs_only = 1; 608 | app_only = 0; 609 | break; 610 | case ARCHIVE_COPY_PATH: 611 | copy_path = strdup(optarg); 612 | break; 613 | case ARCHIVE_COPY_REMOVE: 614 | remove_after_copy = 1; 615 | break; 616 | default: 617 | print_usage(argc, argv, 1); 618 | exit(2); 619 | } 620 | } 621 | 622 | argv += optind; 623 | argc -= optind; 624 | 625 | if (argc == 0) { 626 | fprintf(stderr, "ERROR: Missing command.\n\n"); 627 | print_usage(argc+optind, argv-optind, 1); 628 | exit(2); 629 | } 630 | 631 | char *cmdstr = argv[0]; 632 | 633 | if (!strcmp(cmdstr, "list")) { 634 | cmd = CMD_LIST_APPS; 635 | } else if (!strcmp(cmdstr, "install")) { 636 | cmd = CMD_INSTALL; 637 | } else if (!strcmp(cmdstr, "upgrade")) { 638 | cmd = CMD_UPGRADE; 639 | } else if (!strcmp(cmdstr, "uninstall") || !strcmp(cmdstr, "remove")) { 640 | cmd = CMD_UNINSTALL; 641 | } else if (!strcmp(cmdstr, "archives") || !strcmp(cmdstr, "list-archives")) { 642 | cmd = CMD_LIST_ARCHIVES; 643 | } else if (!strcmp(cmdstr, "archive")) { 644 | cmd = CMD_ARCHIVE; 645 | } else if (!strcmp(cmdstr, "restore")) { 646 | cmd = CMD_RESTORE; 647 | } else if (!strcmp(cmdstr, "remove-archive")) { 648 | cmd = CMD_REMOVE_ARCHIVE; 649 | } 650 | 651 | switch (cmd) { 652 | case CMD_LIST_APPS: 653 | case CMD_LIST_ARCHIVES: 654 | break; 655 | case CMD_INSTALL: 656 | case CMD_UPGRADE: 657 | if (argc < 2) { 658 | fprintf(stderr, "ERROR: Missing filename for '%s' command.\n\n", cmdstr); 659 | print_usage(argc+optind, argv-optind, 1); 660 | exit(2); 661 | } 662 | cmdarg = argv[1]; 663 | break; 664 | case CMD_UNINSTALL: 665 | case CMD_ARCHIVE: 666 | case CMD_RESTORE: 667 | case CMD_REMOVE_ARCHIVE: 668 | if (argc < 2) { 669 | fprintf(stderr, "ERROR: Missing bundle ID for '%s' command.\n\n", cmdstr); 670 | print_usage(argc+optind, argv-optind, 1); 671 | exit(2); 672 | } 673 | cmdarg = argv[1]; 674 | break; 675 | default: 676 | fprintf(stderr, "ERROR: Invalid command '%s'.\n\n", cmdstr); 677 | print_usage(argc+optind, argv-optind, 1); 678 | exit(2); 679 | } 680 | } 681 | 682 | static int afc_upload_file(afc_client_t afc, const char* filename, const char* dstfn) 683 | { 684 | FILE *f = NULL; 685 | uint64_t af = 0; 686 | char buf[1048576]; 687 | 688 | f = fopen(filename, "rb"); 689 | if (!f) { 690 | fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno)); 691 | return -1; 692 | } 693 | 694 | if ((afc_file_open(afc, dstfn, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || !af) { 695 | fclose(f); 696 | fprintf(stderr, "afc_file_open on '%s' failed!\n", dstfn); 697 | return -1; 698 | } 699 | 700 | size_t amount = 0; 701 | do { 702 | amount = fread(buf, 1, sizeof(buf), f); 703 | if (amount > 0) { 704 | uint32_t written, total = 0; 705 | while (total < amount) { 706 | written = 0; 707 | afc_error_t aerr = afc_file_write(afc, af, buf, amount, &written); 708 | if (aerr != AFC_E_SUCCESS) { 709 | fprintf(stderr, "AFC Write error: %d\n", aerr); 710 | break; 711 | } 712 | total += written; 713 | } 714 | if (total != amount) { 715 | fprintf(stderr, "Error: wrote only %u of %u\n", total, (uint32_t)amount); 716 | afc_file_close(afc, af); 717 | fclose(f); 718 | return -1; 719 | } 720 | } 721 | } while (amount > 0); 722 | 723 | afc_file_close(afc, af); 724 | fclose(f); 725 | 726 | return 0; 727 | } 728 | 729 | static void afc_upload_dir(afc_client_t afc, const char* path, const char* afcpath) 730 | { 731 | afc_make_directory(afc, afcpath); 732 | 733 | DIR *dir = opendir(path); 734 | if (dir) { 735 | struct dirent* ep; 736 | while ((ep = readdir(dir))) { 737 | if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { 738 | continue; 739 | } 740 | char *fpath = (char*)malloc(strlen(path)+1+strlen(ep->d_name)+1); 741 | char *apath = (char*)malloc(strlen(afcpath)+1+strlen(ep->d_name)+1); 742 | 743 | struct stat st; 744 | 745 | strcpy(fpath, path); 746 | strcat(fpath, "/"); 747 | strcat(fpath, ep->d_name); 748 | 749 | strcpy(apath, afcpath); 750 | strcat(apath, "/"); 751 | strcat(apath, ep->d_name); 752 | 753 | #ifdef HAVE_LSTAT 754 | if ((lstat(fpath, &st) == 0) && S_ISLNK(st.st_mode)) { 755 | char *target = (char *)malloc(st.st_size+1); 756 | if (readlink(fpath, target, st.st_size+1) < 0) { 757 | fprintf(stderr, "ERROR: readlink: %s (%d)\n", strerror(errno), errno); 758 | } else { 759 | target[st.st_size] = '\0'; 760 | afc_make_link(afc, AFC_SYMLINK, target, fpath); 761 | } 762 | free(target); 763 | } else 764 | #endif 765 | if ((stat(fpath, &st) == 0) && S_ISDIR(st.st_mode)) { 766 | afc_upload_dir(afc, fpath, apath); 767 | } else { 768 | afc_upload_file(afc, fpath, apath); 769 | } 770 | free(fpath); 771 | free(apath); 772 | } 773 | closedir(dir); 774 | } 775 | } 776 | 777 | static char *buf_from_file(const char *filename, size_t *size) 778 | { 779 | struct stat st; 780 | FILE *fp = NULL; 781 | 782 | if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) { 783 | return NULL; 784 | } 785 | size_t filesize = st.st_size; 786 | if (filesize == 0) { 787 | return NULL; 788 | } 789 | char *ibuf = malloc(filesize * sizeof(char)); 790 | if (ibuf == NULL) { 791 | return NULL; 792 | } 793 | size_t amount = fread(ibuf, 1, filesize, fp); 794 | if (amount != filesize) { 795 | fprintf(stderr, "ERROR: could not read %" PRIu64 " bytes from %s\n", (uint64_t)filesize, filename); 796 | free(ibuf); 797 | return NULL; 798 | } 799 | fclose(fp); 800 | 801 | if (size) { 802 | *size = filesize; 803 | } 804 | 805 | return ibuf; 806 | } 807 | 808 | int main(int argc, char **argv) 809 | { 810 | idevice_t device = NULL; 811 | lockdownd_client_t client = NULL; 812 | instproxy_client_t ipc = NULL; 813 | instproxy_error_t err; 814 | np_client_t np = NULL; 815 | afc_client_t afc = NULL; 816 | lockdownd_service_descriptor_t service = NULL; 817 | int res = EXIT_FAILURE; 818 | char *bundleidentifier = NULL; 819 | 820 | #ifndef WIN32 821 | signal(SIGPIPE, SIG_IGN); 822 | #endif 823 | parse_opts(argc, argv); 824 | 825 | argc -= optind; 826 | argv += optind; 827 | 828 | if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) { 829 | if (udid) { 830 | fprintf(stderr, "No device found with udid %s.\n", udid); 831 | } else { 832 | fprintf(stderr, "No device found.\n"); 833 | } 834 | return EXIT_FAILURE; 835 | } 836 | 837 | if (!udid) { 838 | idevice_get_udid(device, &udid); 839 | } 840 | 841 | lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller"); 842 | if (lerr != LOCKDOWN_E_SUCCESS) { 843 | fprintf(stderr, "Could not connect to lockdownd: %s. Exiting.\n", lockdownd_strerror(lerr)); 844 | goto leave_cleanup; 845 | } 846 | 847 | if (use_notifier) { 848 | lerr =lockdownd_start_service(client, "com.apple.mobile.notification_proxy", &service); 849 | if (lerr != LOCKDOWN_E_SUCCESS) { 850 | fprintf(stderr, "Could not start com.apple.mobile.notification_proxy: %s\n", lockdownd_strerror(lerr)); 851 | goto leave_cleanup; 852 | } 853 | 854 | np_error_t nperr = np_client_new(device, service, &np); 855 | 856 | if (service) { 857 | lockdownd_service_descriptor_free(service); 858 | } 859 | service = NULL; 860 | 861 | if (nperr != NP_E_SUCCESS) { 862 | fprintf(stderr, "Could not connect to notification_proxy!\n"); 863 | goto leave_cleanup; 864 | } 865 | 866 | np_set_notify_callback(np, notifier, NULL); 867 | 868 | const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL }; 869 | 870 | np_observe_notifications(np, noties); 871 | } 872 | 873 | run_again: 874 | if (service) { 875 | lockdownd_service_descriptor_free(service); 876 | } 877 | service = NULL; 878 | 879 | lerr = lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service); 880 | if (lerr != LOCKDOWN_E_SUCCESS) { 881 | fprintf(stderr, "Could not start com.apple.mobile.installation_proxy: %s\n", lockdownd_strerror(lerr)); 882 | goto leave_cleanup; 883 | } 884 | 885 | err = instproxy_client_new(device, service, &ipc); 886 | 887 | if (service) { 888 | lockdownd_service_descriptor_free(service); 889 | } 890 | service = NULL; 891 | 892 | if (err != INSTPROXY_E_SUCCESS) { 893 | fprintf(stderr, "Could not connect to installation_proxy!\n"); 894 | goto leave_cleanup; 895 | } 896 | 897 | setbuf(stdout, NULL); 898 | 899 | free(last_status); 900 | last_status = NULL; 901 | 902 | notification_expected = 0; 903 | 904 | if (cmd == CMD_LIST_APPS) { 905 | plist_t client_opts = instproxy_client_options_new(); 906 | instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL); 907 | plist_t apps = NULL; 908 | 909 | if (opt_list_system && opt_list_user) { 910 | plist_dict_remove_item(client_opts, "ApplicationType"); 911 | } else if (opt_list_system) { 912 | instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL); 913 | } else if (opt_list_user) { 914 | instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL); 915 | } 916 | 917 | if (bundle_ids) { 918 | plist_dict_set_item(client_opts, "BundleIDs", plist_copy(bundle_ids)); 919 | } 920 | 921 | if (!output_format && !return_attrs) { 922 | return_attrs = plist_new_array(); 923 | plist_array_append_item(return_attrs, plist_new_string("CFBundleIdentifier")); 924 | plist_array_append_item(return_attrs, plist_new_string("CFBundleShortVersionString")); 925 | plist_array_append_item(return_attrs, plist_new_string("CFBundleDisplayName")); 926 | } 927 | 928 | if (return_attrs) { 929 | instproxy_client_options_add(client_opts, "ReturnAttributes", return_attrs, NULL); 930 | } 931 | 932 | if (output_format) { 933 | err = instproxy_browse(ipc, client_opts, &apps); 934 | 935 | if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) { 936 | fprintf(stderr, "ERROR: instproxy_browse returnd an invalid plist!\n"); 937 | goto leave_cleanup; 938 | } 939 | char *buf = NULL; 940 | uint32_t len = 0; 941 | if (output_format == FORMAT_XML) { 942 | plist_err_t perr = plist_to_xml(apps, &buf, &len); 943 | if (perr != PLIST_ERR_SUCCESS) { 944 | fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr); 945 | } 946 | } else if (output_format == FORMAT_JSON) { 947 | /* for JSON, we need to convert some stuff since it doesn't support PLIST_DATA nodes */ 948 | plist_array_iter aiter = NULL; 949 | plist_array_new_iter(apps, &aiter); 950 | plist_t entry = NULL; 951 | do { 952 | plist_array_next_item(apps, aiter, &entry); 953 | if (!entry) break; 954 | plist_t items = plist_dict_get_item(entry, "UIApplicationShortcutItems"); 955 | plist_array_iter inner = NULL; 956 | plist_array_new_iter(items, &inner); 957 | plist_t item = NULL; 958 | do { 959 | plist_array_next_item(items, inner, &item); 960 | if (!item) break; 961 | plist_t userinfo = plist_dict_get_item(item, "UIApplicationShortcutItemUserInfo"); 962 | if (userinfo) { 963 | plist_t data_node = plist_dict_get_item(userinfo, "data"); 964 | 965 | if (data_node) { 966 | char *strbuf = NULL; 967 | uint32_t buflen = 0; 968 | plist_write_to_string(data_node, &strbuf, &buflen, PLIST_FORMAT_LIMD, PLIST_OPT_NO_NEWLINE); 969 | plist_set_string_val(data_node, strbuf); 970 | free(strbuf); 971 | } 972 | } 973 | } while (item); 974 | free(inner); 975 | } while (entry); 976 | free(aiter); 977 | plist_err_t perr = plist_to_json(apps, &buf, &len, 1); 978 | if (perr != PLIST_ERR_SUCCESS) { 979 | fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr); 980 | } 981 | } 982 | if (buf) { 983 | puts(buf); 984 | free(buf); 985 | } 986 | plist_free(apps); 987 | res = 0; 988 | goto leave_cleanup; 989 | } 990 | 991 | print_apps_header(); 992 | 993 | err = instproxy_browse_with_callback(ipc, client_opts, status_cb, NULL); 994 | if (err == INSTPROXY_E_RECEIVE_TIMEOUT) { 995 | fprintf(stderr, "NOTE: timeout waiting for device to browse apps, trying again...\n"); 996 | } 997 | 998 | instproxy_client_options_free(client_opts); 999 | if (err != INSTPROXY_E_SUCCESS) { 1000 | fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err); 1001 | goto leave_cleanup; 1002 | } 1003 | 1004 | wait_for_command_complete = 1; 1005 | notification_expected = 0; 1006 | } else if (cmd == CMD_INSTALL || cmd == CMD_UPGRADE) { 1007 | plist_t sinf = NULL; 1008 | plist_t meta = NULL; 1009 | char *pkgname = NULL; 1010 | struct stat fst; 1011 | uint64_t af = 0; 1012 | char buf[8192]; 1013 | 1014 | lockdownd_service_descriptor_free(service); 1015 | service = NULL; 1016 | 1017 | lerr = lockdownd_start_service(client, "com.apple.afc", &service); 1018 | if (lerr != LOCKDOWN_E_SUCCESS) { 1019 | fprintf(stderr, "Could not start com.apple.afc: %s\n", lockdownd_strerror(lerr)); 1020 | goto leave_cleanup; 1021 | } 1022 | 1023 | lockdownd_client_free(client); 1024 | client = NULL; 1025 | 1026 | if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) { 1027 | fprintf(stderr, "Could not connect to AFC!\n"); 1028 | goto leave_cleanup; 1029 | } 1030 | 1031 | if (stat(cmdarg, &fst) != 0) { 1032 | fprintf(stderr, "ERROR: stat: %s: %s\n", cmdarg, strerror(errno)); 1033 | goto leave_cleanup; 1034 | } 1035 | 1036 | char **strs = NULL; 1037 | if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) { 1038 | if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) { 1039 | fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH); 1040 | } 1041 | } 1042 | if (strs) { 1043 | int i = 0; 1044 | while (strs[i]) { 1045 | free(strs[i]); 1046 | i++; 1047 | } 1048 | free(strs); 1049 | } 1050 | 1051 | plist_t client_opts = instproxy_client_options_new(); 1052 | 1053 | /* open install package */ 1054 | int errp = 0; 1055 | struct zip *zf = NULL; 1056 | 1057 | if ((strlen(cmdarg) > 5) && (strcmp(&cmdarg[strlen(cmdarg)-5], ".ipcc") == 0)) { 1058 | zf = zip_open(cmdarg, 0, &errp); 1059 | if (!zf) { 1060 | fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp); 1061 | goto leave_cleanup; 1062 | } 1063 | 1064 | char* ipcc = strdup(cmdarg); 1065 | if ((asprintf(&pkgname, "%s/%s", PKG_PATH, basename(ipcc)) > 0) && pkgname) { 1066 | afc_make_directory(afc, pkgname); 1067 | } 1068 | 1069 | printf("Uploading %s package contents... ", basename(ipcc)); 1070 | 1071 | /* extract the contents of the .ipcc file to PublicStaging/.ipcc directory */ 1072 | zip_int64_t numzf = (zip_int64_t)zip_get_num_entries(zf, 0); 1073 | zip_int64_t i = 0; 1074 | for (i = 0; numzf > 0 && i < numzf; i++) { 1075 | const char* zname = zip_get_name(zf, i, 0); 1076 | char* dstpath = NULL; 1077 | if (!zname) continue; 1078 | if (zname[strlen(zname)-1] == '/') { 1079 | // directory 1080 | if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) > 0) && dstpath) { 1081 | afc_make_directory(afc, dstpath); } 1082 | free(dstpath); 1083 | dstpath = NULL; 1084 | } else { 1085 | // file 1086 | struct zip_file* zfile = zip_fopen_index(zf, i, 0); 1087 | if (!zfile) continue; 1088 | 1089 | 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)) { 1090 | fprintf(stderr, "ERROR: can't open afc://%s for writing\n", dstpath); 1091 | free(dstpath); 1092 | dstpath = NULL; 1093 | zip_fclose(zfile); 1094 | continue; 1095 | } 1096 | 1097 | struct zip_stat zs; 1098 | zip_stat_init(&zs); 1099 | if (zip_stat_index(zf, i, 0, &zs) != 0) { 1100 | fprintf(stderr, "ERROR: zip_stat_index %" PRIu64 " failed!\n", i); 1101 | free(dstpath); 1102 | dstpath = NULL; 1103 | zip_fclose(zfile); 1104 | continue; 1105 | } 1106 | 1107 | free(dstpath); 1108 | dstpath = NULL; 1109 | 1110 | zip_uint64_t zfsize = 0; 1111 | while (zfsize < zs.size) { 1112 | zip_int64_t amount = zip_fread(zfile, buf, sizeof(buf)); 1113 | if (amount == 0) { 1114 | break; 1115 | } 1116 | 1117 | if (amount > 0) { 1118 | uint32_t written, total = 0; 1119 | while (total < amount) { 1120 | written = 0; 1121 | if (afc_file_write(afc, af, buf, amount, &written) != 1122 | AFC_E_SUCCESS) { 1123 | fprintf(stderr, "AFC Write error!\n"); 1124 | break; 1125 | } 1126 | total += written; 1127 | } 1128 | if (total != amount) { 1129 | fprintf(stderr, "Error: wrote only %d of %" PRIi64 "\n", total, amount); 1130 | afc_file_close(afc, af); 1131 | zip_fclose(zfile); 1132 | free(dstpath); 1133 | goto leave_cleanup; 1134 | } 1135 | } 1136 | 1137 | zfsize += amount; 1138 | } 1139 | 1140 | afc_file_close(afc, af); 1141 | af = 0; 1142 | 1143 | zip_fclose(zfile); 1144 | } 1145 | } 1146 | free(ipcc); 1147 | printf("DONE.\n"); 1148 | 1149 | instproxy_client_options_add(client_opts, "PackageType", "CarrierBundle", NULL); 1150 | } else if (S_ISDIR(fst.st_mode)) { 1151 | /* upload developer app directory */ 1152 | instproxy_client_options_add(client_opts, "PackageType", "Developer", NULL); 1153 | 1154 | if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(cmdarg)) < 0) { 1155 | fprintf(stderr, "ERROR: Out of memory allocating pkgname!?\n"); 1156 | goto leave_cleanup; 1157 | } 1158 | 1159 | printf("Uploading %s package contents... ", basename(cmdarg)); 1160 | afc_upload_dir(afc, cmdarg, pkgname); 1161 | printf("DONE.\n"); 1162 | 1163 | /* extract the CFBundleIdentifier from the package */ 1164 | 1165 | /* construct full filename to Info.plist */ 1166 | char *filename = (char*)malloc(strlen(cmdarg)+11+1); 1167 | strcpy(filename, cmdarg); 1168 | strcat(filename, "/Info.plist"); 1169 | 1170 | struct stat st; 1171 | FILE *fp = NULL; 1172 | 1173 | if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) { 1174 | fprintf(stderr, "ERROR: could not locate %s in app!\n", filename); 1175 | free(filename); 1176 | goto leave_cleanup; 1177 | } 1178 | size_t filesize = st.st_size; 1179 | char *ibuf = malloc(filesize * sizeof(char)); 1180 | size_t amount = fread(ibuf, 1, filesize, fp); 1181 | if (amount != filesize) { 1182 | fprintf(stderr, "ERROR: could not read %u bytes from %s\n", (uint32_t)filesize, filename); 1183 | free(filename); 1184 | goto leave_cleanup; 1185 | } 1186 | fclose(fp); 1187 | free(filename); 1188 | 1189 | plist_t info = NULL; 1190 | plist_from_memory(ibuf, filesize, &info, NULL); 1191 | free(ibuf); 1192 | 1193 | if (!info) { 1194 | fprintf(stderr, "ERROR: could not parse Info.plist!\n"); 1195 | goto leave_cleanup; 1196 | } 1197 | 1198 | plist_t bname = plist_dict_get_item(info, "CFBundleIdentifier"); 1199 | if (bname) { 1200 | plist_get_string_val(bname, &bundleidentifier); 1201 | } 1202 | plist_free(info); 1203 | info = NULL; 1204 | } else { 1205 | zf = zip_open(cmdarg, 0, &errp); 1206 | if (!zf) { 1207 | fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp); 1208 | goto leave_cleanup; 1209 | } 1210 | 1211 | char *zbuf = NULL; 1212 | uint32_t len = 0; 1213 | plist_t meta_dict = NULL; 1214 | 1215 | if (extmeta) { 1216 | size_t flen = 0; 1217 | zbuf = buf_from_file(extmeta, &flen); 1218 | if (zbuf && flen) { 1219 | meta = plist_new_data(zbuf, flen); 1220 | plist_from_memory(zbuf, flen, &meta_dict, NULL); 1221 | free(zbuf); 1222 | } 1223 | if (!meta_dict) { 1224 | plist_free(meta); 1225 | meta = NULL; 1226 | fprintf(stderr, "WARNING: could not load external iTunesMetadata %s!\n", extmeta); 1227 | } 1228 | zbuf = NULL; 1229 | } 1230 | 1231 | if (!meta && !meta_dict) { 1232 | /* extract iTunesMetadata.plist from package */ 1233 | if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) == 0) { 1234 | meta = plist_new_data(zbuf, len); 1235 | plist_from_memory(zbuf, len, &meta_dict, NULL); 1236 | } 1237 | if (!meta_dict) { 1238 | plist_free(meta); 1239 | meta = NULL; 1240 | fprintf(stderr, "WARNING: could not locate %s in archive!\n", ITUNES_METADATA_PLIST_FILENAME); 1241 | } 1242 | free(zbuf); 1243 | } 1244 | 1245 | /* determine .app directory in archive */ 1246 | zbuf = NULL; 1247 | len = 0; 1248 | plist_t info = NULL; 1249 | char* filename = NULL; 1250 | char* app_directory_name = NULL; 1251 | 1252 | if (zip_get_app_directory(zf, &app_directory_name)) { 1253 | fprintf(stderr, "ERROR: Unable to locate .app directory in archive. Make sure it is inside a 'Payload' directory.\n"); 1254 | goto leave_cleanup; 1255 | } 1256 | 1257 | /* construct full filename to Info.plist */ 1258 | filename = (char*)malloc(strlen(app_directory_name)+10+1); 1259 | strcpy(filename, app_directory_name); 1260 | free(app_directory_name); 1261 | app_directory_name = NULL; 1262 | strcat(filename, "Info.plist"); 1263 | 1264 | if (zip_get_contents(zf, filename, 0, &zbuf, &len) < 0) { 1265 | fprintf(stderr, "WARNING: could not locate %s in archive!\n", filename); 1266 | free(filename); 1267 | zip_unchange_all(zf); 1268 | zip_close(zf); 1269 | goto leave_cleanup; 1270 | } 1271 | free(filename); 1272 | plist_from_memory(zbuf, len, &info, NULL); 1273 | free(zbuf); 1274 | 1275 | if (!info) { 1276 | fprintf(stderr, "Could not parse Info.plist!\n"); 1277 | zip_unchange_all(zf); 1278 | zip_close(zf); 1279 | goto leave_cleanup; 1280 | } 1281 | 1282 | char *bundleexecutable = NULL; 1283 | 1284 | plist_t bname = plist_dict_get_item(info, "CFBundleExecutable"); 1285 | if (bname) { 1286 | plist_get_string_val(bname, &bundleexecutable); 1287 | } 1288 | 1289 | bname = plist_dict_get_item(info, "CFBundleIdentifier"); 1290 | if (bname) { 1291 | plist_get_string_val(bname, &bundleidentifier); 1292 | } 1293 | plist_free(info); 1294 | info = NULL; 1295 | 1296 | if (!bundleexecutable) { 1297 | fprintf(stderr, "Could not determine value for CFBundleExecutable!\n"); 1298 | zip_unchange_all(zf); 1299 | zip_close(zf); 1300 | goto leave_cleanup; 1301 | } 1302 | 1303 | if (extsinf) { 1304 | size_t flen = 0; 1305 | zbuf = buf_from_file(extsinf, &flen); 1306 | if (zbuf && flen) { 1307 | sinf = plist_new_data(zbuf, flen); 1308 | free(zbuf); 1309 | } else { 1310 | fprintf(stderr, "WARNING: could not load external SINF %s!\n", extsinf); 1311 | } 1312 | zbuf = NULL; 1313 | } 1314 | 1315 | if (!sinf) { 1316 | char *sinfname = NULL; 1317 | if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable, bundleexecutable) < 0) { 1318 | fprintf(stderr, "Out of memory!?\n"); 1319 | goto leave_cleanup; 1320 | } 1321 | free(bundleexecutable); 1322 | 1323 | /* extract .sinf from package */ 1324 | zbuf = NULL; 1325 | len = 0; 1326 | if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) { 1327 | sinf = plist_new_data(zbuf, len); 1328 | } else { 1329 | fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname); 1330 | } 1331 | free(sinfname); 1332 | free(zbuf); 1333 | } 1334 | 1335 | /* copy archive to device */ 1336 | pkgname = NULL; 1337 | if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) { 1338 | fprintf(stderr, "Out of memory!?\n"); 1339 | goto leave_cleanup; 1340 | } 1341 | 1342 | printf("Copying '%s' to device... ", cmdarg); 1343 | 1344 | if (afc_upload_file(afc, cmdarg, pkgname) < 0) { 1345 | printf("FAILED\n"); 1346 | free(pkgname); 1347 | goto leave_cleanup; 1348 | } 1349 | 1350 | printf("DONE.\n"); 1351 | 1352 | if (bundleidentifier) { 1353 | instproxy_client_options_add(client_opts, "CFBundleIdentifier", bundleidentifier, NULL); 1354 | } 1355 | if (sinf) { 1356 | instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL); 1357 | } 1358 | if (meta) { 1359 | instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL); 1360 | } 1361 | } 1362 | if (zf) { 1363 | zip_unchange_all(zf); 1364 | zip_close(zf); 1365 | } 1366 | 1367 | /* perform installation or upgrade */ 1368 | if (cmd == CMD_INSTALL) { 1369 | printf("Installing '%s'\n", bundleidentifier); 1370 | instproxy_install(ipc, pkgname, client_opts, status_cb, NULL); 1371 | } else { 1372 | printf("Upgrading '%s'\n", bundleidentifier); 1373 | instproxy_upgrade(ipc, pkgname, client_opts, status_cb, NULL); 1374 | } 1375 | instproxy_client_options_free(client_opts); 1376 | free(pkgname); 1377 | wait_for_command_complete = 1; 1378 | notification_expected = 1; 1379 | } else if (cmd == CMD_UNINSTALL) { 1380 | printf("Uninstalling '%s'\n", cmdarg); 1381 | instproxy_uninstall(ipc, cmdarg, NULL, status_cb, NULL); 1382 | wait_for_command_complete = 1; 1383 | notification_expected = 0; 1384 | } else if (cmd == CMD_LIST_ARCHIVES) { 1385 | plist_t dict = NULL; 1386 | 1387 | err = instproxy_lookup_archives(ipc, NULL, &dict); 1388 | if (err != INSTPROXY_E_SUCCESS) { 1389 | fprintf(stderr, "ERROR: lookup_archives returned %d\n", err); 1390 | goto leave_cleanup; 1391 | } 1392 | 1393 | if (!dict) { 1394 | fprintf(stderr, "ERROR: lookup_archives did not return a plist!?\n"); 1395 | goto leave_cleanup; 1396 | } 1397 | 1398 | if (output_format) { 1399 | char *buf = NULL; 1400 | uint32_t len = 0; 1401 | if (output_format == FORMAT_XML) { 1402 | plist_err_t perr = plist_to_xml(dict, &buf, &len); 1403 | if (perr != PLIST_ERR_SUCCESS) { 1404 | fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr); 1405 | } 1406 | } else if (output_format == FORMAT_JSON) { 1407 | plist_err_t perr = plist_to_json(dict, &buf, &len, 1); 1408 | if (perr != PLIST_ERR_SUCCESS) { 1409 | fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr); 1410 | } 1411 | } 1412 | if (buf) { 1413 | puts(buf); 1414 | free(buf); 1415 | } 1416 | plist_free(dict); 1417 | goto leave_cleanup; 1418 | } 1419 | plist_dict_iter iter = NULL; 1420 | plist_t node = NULL; 1421 | char *key = NULL; 1422 | 1423 | printf("Total: %d archived apps\n", plist_dict_get_size(dict)); 1424 | plist_dict_new_iter(dict, &iter); 1425 | if (!iter) { 1426 | plist_free(dict); 1427 | fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n"); 1428 | goto leave_cleanup; 1429 | } 1430 | do { 1431 | key = NULL; 1432 | node = NULL; 1433 | plist_dict_next_item(dict, iter, &key, &node); 1434 | if (key && (plist_get_node_type(node) == PLIST_DICT)) { 1435 | char *s_dispName = NULL; 1436 | char *s_version = NULL; 1437 | plist_t dispName = 1438 | plist_dict_get_item(node, "CFBundleDisplayName"); 1439 | plist_t version = 1440 | plist_dict_get_item(node, "CFBundleShortVersionString"); 1441 | if (dispName) { 1442 | plist_get_string_val(dispName, &s_dispName); 1443 | } 1444 | if (version) { 1445 | plist_get_string_val(version, &s_version); 1446 | } 1447 | if (!s_dispName) { 1448 | s_dispName = strdup(key); 1449 | } 1450 | if (s_version) { 1451 | printf("%s - %s %s\n", key, s_dispName, s_version); 1452 | free(s_version); 1453 | } else { 1454 | printf("%s - %s\n", key, s_dispName); 1455 | } 1456 | free(s_dispName); 1457 | free(key); 1458 | } 1459 | } 1460 | while (node); 1461 | plist_free(dict); 1462 | } else if (cmd == CMD_ARCHIVE) { 1463 | plist_t client_opts = NULL; 1464 | 1465 | if (skip_uninstall || app_only || docs_only) { 1466 | client_opts = instproxy_client_options_new(); 1467 | if (skip_uninstall) { 1468 | instproxy_client_options_add(client_opts, "SkipUninstall", 1, NULL); 1469 | } 1470 | if (app_only) { 1471 | instproxy_client_options_add(client_opts, "ArchiveType", "ApplicationOnly", NULL); 1472 | } else if (docs_only) { 1473 | instproxy_client_options_add(client_opts, "ArchiveType", "DocumentsOnly", NULL); 1474 | } 1475 | } 1476 | 1477 | if (copy_path) { 1478 | struct stat fst; 1479 | if (stat(copy_path, &fst) != 0) { 1480 | fprintf(stderr, "ERROR: stat: %s: %s\n", copy_path, strerror(errno)); 1481 | goto leave_cleanup; 1482 | } 1483 | 1484 | if (!S_ISDIR(fst.st_mode)) { 1485 | fprintf(stderr, "ERROR: '%s' is not a directory as expected.\n", copy_path); 1486 | goto leave_cleanup; 1487 | } 1488 | 1489 | if (service) { 1490 | lockdownd_service_descriptor_free(service); 1491 | } 1492 | service = NULL; 1493 | 1494 | if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || !service) { 1495 | fprintf(stderr, "Could not start com.apple.afc!\n"); 1496 | goto leave_cleanup; 1497 | } 1498 | 1499 | lockdownd_client_free(client); 1500 | client = NULL; 1501 | 1502 | if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) { 1503 | fprintf(stderr, "Could not connect to AFC!\n"); 1504 | goto leave_cleanup; 1505 | } 1506 | } 1507 | 1508 | instproxy_archive(ipc, cmdarg, client_opts, status_cb, NULL); 1509 | 1510 | instproxy_client_options_free(client_opts); 1511 | wait_for_command_complete = 1; 1512 | if (skip_uninstall) { 1513 | notification_expected = 0; 1514 | } else { 1515 | notification_expected = 1; 1516 | } 1517 | 1518 | idevice_wait_for_command_to_complete(); 1519 | 1520 | if (copy_path) { 1521 | if (err_occurred) { 1522 | afc_client_free(afc); 1523 | afc = NULL; 1524 | goto leave_cleanup; 1525 | } 1526 | FILE *f = NULL; 1527 | uint64_t af = 0; 1528 | /* local filename */ 1529 | char *localfile = NULL; 1530 | if (asprintf(&localfile, "%s/%s.ipa", copy_path, cmdarg) < 0) { 1531 | fprintf(stderr, "Out of memory!?\n"); 1532 | goto leave_cleanup; 1533 | } 1534 | 1535 | f = fopen(localfile, "wb"); 1536 | if (!f) { 1537 | fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno)); 1538 | free(localfile); 1539 | goto leave_cleanup; 1540 | } 1541 | 1542 | /* remote filename */ 1543 | char *remotefile = NULL; 1544 | if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, cmdarg) < 0) { 1545 | fprintf(stderr, "Out of memory!?\n"); 1546 | goto leave_cleanup; 1547 | } 1548 | 1549 | uint32_t fsize = 0; 1550 | char **fileinfo = NULL; 1551 | if ((afc_get_file_info(afc, remotefile, &fileinfo) != AFC_E_SUCCESS) || !fileinfo) { 1552 | fprintf(stderr, "ERROR getting AFC file info for '%s' on device!\n", remotefile); 1553 | fclose(f); 1554 | free(remotefile); 1555 | free(localfile); 1556 | goto leave_cleanup; 1557 | } 1558 | 1559 | int i; 1560 | for (i = 0; fileinfo[i]; i+=2) { 1561 | if (!strcmp(fileinfo[i], "st_size")) { 1562 | fsize = atoi(fileinfo[i+1]); 1563 | break; 1564 | } 1565 | } 1566 | i = 0; 1567 | while (fileinfo[i]) { 1568 | free(fileinfo[i]); 1569 | i++; 1570 | } 1571 | free(fileinfo); 1572 | 1573 | if (fsize == 0) { 1574 | fprintf(stderr, "Hm... remote file length could not be determined. Cannot copy.\n"); 1575 | fclose(f); 1576 | free(remotefile); 1577 | free(localfile); 1578 | goto leave_cleanup; 1579 | } 1580 | 1581 | if ((afc_file_open(afc, remotefile, AFC_FOPEN_RDONLY, &af) != AFC_E_SUCCESS) || !af) { 1582 | fclose(f); 1583 | fprintf(stderr, "ERROR: could not open '%s' on device for reading!\n", remotefile); 1584 | free(remotefile); 1585 | free(localfile); 1586 | goto leave_cleanup; 1587 | } 1588 | 1589 | /* copy file over */ 1590 | printf("Copying '%s' --> '%s'... ", remotefile, localfile); 1591 | free(remotefile); 1592 | free(localfile); 1593 | 1594 | uint32_t amount = 0; 1595 | uint32_t total = 0; 1596 | char buf[8192]; 1597 | 1598 | do { 1599 | if (afc_file_read(afc, af, buf, sizeof(buf), &amount) != AFC_E_SUCCESS) { 1600 | fprintf(stderr, "AFC Read error!\n"); 1601 | break; 1602 | } 1603 | 1604 | if (amount > 0) { 1605 | size_t written = fwrite(buf, 1, amount, f); 1606 | if (written != amount) { 1607 | fprintf(stderr, "Error when writing %d bytes to local file!\n", amount); 1608 | break; 1609 | } 1610 | total += written; 1611 | } 1612 | } while (amount > 0); 1613 | 1614 | afc_file_close(afc, af); 1615 | fclose(f); 1616 | 1617 | printf("DONE.\n"); 1618 | 1619 | if (total != fsize) { 1620 | fprintf(stderr, "WARNING: remote and local file sizes don't match (%d != %d)\n", fsize, total); 1621 | if (remove_after_copy) { 1622 | fprintf(stderr, "NOTE: archive file will NOT be removed from device\n"); 1623 | remove_after_copy = 0; 1624 | } 1625 | } 1626 | 1627 | if (remove_after_copy) { 1628 | /* remove archive if requested */ 1629 | printf("Removing '%s'\n", cmdarg); 1630 | cmd = CMD_REMOVE_ARCHIVE; 1631 | if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller")) { 1632 | fprintf(stderr, "Could not connect to lockdownd. Exiting.\n"); 1633 | goto leave_cleanup; 1634 | } 1635 | goto run_again; 1636 | } 1637 | } 1638 | goto leave_cleanup; 1639 | } else if (cmd == CMD_RESTORE) { 1640 | instproxy_restore(ipc, cmdarg, NULL, status_cb, NULL); 1641 | wait_for_command_complete = 1; 1642 | notification_expected = 1; 1643 | } else if (cmd == CMD_REMOVE_ARCHIVE) { 1644 | instproxy_remove_archive(ipc, cmdarg, NULL, status_cb, NULL); 1645 | wait_for_command_complete = 1; 1646 | } else { 1647 | printf("ERROR: no command selected?! This should not be reached!\n"); 1648 | res = 2; 1649 | goto leave_cleanup; 1650 | } 1651 | 1652 | /* not needed anymore */ 1653 | lockdownd_client_free(client); 1654 | client = NULL; 1655 | 1656 | idevice_wait_for_command_to_complete(); 1657 | res = 0; 1658 | 1659 | leave_cleanup: 1660 | np_client_free(np); 1661 | instproxy_client_free(ipc); 1662 | afc_client_free(afc); 1663 | lockdownd_client_free(client); 1664 | idevice_free(device); 1665 | 1666 | free(udid); 1667 | free(copy_path); 1668 | free(extsinf); 1669 | free(extmeta); 1670 | free(bundleidentifier); 1671 | plist_free(bundle_ids); 1672 | plist_free(return_attrs); 1673 | 1674 | if (err_occurred && !res) { 1675 | res = 128; 1676 | } 1677 | 1678 | return res; 1679 | } 1680 | --------------------------------------------------------------------------------