diff options
author | Nikias Bassen | 2013-09-17 11:00:31 +0200 |
---|---|---|
committer | Nikias Bassen | 2013-09-17 11:00:31 +0200 |
commit | c45ae1f6b6f53995a5bc99591688102d11ad2148 (patch) | |
tree | 03e36986e4ad61f6345c64b7b2f673eebee33816 | |
download | libusbmuxd-c45ae1f6b6f53995a5bc99591688102d11ad2148.tar.gz libusbmuxd-c45ae1f6b6f53995a5bc99591688102d11ad2148.tar.bz2 |
initial commit of adapted source tree.
-rw-r--r-- | .gitignore | 39 | ||||
-rw-r--r-- | AUTHORS | 5 | ||||
-rw-r--r-- | COPYING.LGPLv2.1 | 502 | ||||
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | README | 214 | ||||
-rw-r--r-- | README.devel | 50 | ||||
-rwxr-xr-x | autogen.sh | 15 | ||||
-rw-r--r-- | configure.ac | 124 | ||||
-rw-r--r-- | include/Makefile.am | 4 | ||||
-rw-r--r-- | include/usbmuxd-proto.h | 97 | ||||
-rw-r--r-- | include/usbmuxd.h | 191 | ||||
-rw-r--r-- | libusbmuxd.pc.in | 11 | ||||
-rw-r--r-- | m4/as-compiler-flag.m4 | 62 | ||||
-rw-r--r-- | python-client/.gitignore | 3 | ||||
-rw-r--r-- | python-client/tcprelay.py | 148 | ||||
-rw-r--r-- | python-client/usbmux.py | 246 | ||||
-rw-r--r-- | src/Makefile.am | 16 | ||||
-rw-r--r-- | src/collection.c | 81 | ||||
-rw-r--r-- | src/collection.h | 48 | ||||
-rw-r--r-- | src/libusbmuxd.c | 980 | ||||
-rw-r--r-- | src/sock_stuff.c | 375 | ||||
-rw-r--r-- | src/sock_stuff.h | 65 | ||||
-rw-r--r-- | tools/Makefile.am | 10 | ||||
-rw-r--r-- | tools/iproxy.c | 281 |
24 files changed, 3573 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c0a8c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# git-ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +*.[oa] +*~ +*.po +*.lo +*.la +autom4te.cache/* +*.in +*/.deps/* +m4/* +swig/* +*.swp +*.patch +aclocal.m4 +config.h +config.log +config.sub +config.guess +config.status +configure +depcomp +install-sh +compile +main +ltmain.sh +missing +mkinstalldirs +libtool +*Makefile +py-compile +stamp-h1 +src/.libs +docs/html +libusbmuxd.pc +tools/.libs/* +tools/iproxy @@ -0,0 +1,5 @@ +Nikias Bassen +Hector Martin +Bastien Nocera +Paul Sladen +Martin Szulecki diff --git a/COPYING.LGPLv2.1 b/COPYING.LGPLv2.1 new file mode 100644 index 0000000..732811e --- /dev/null +++ b/COPYING.LGPLv2.1 @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..4655b8f --- /dev/null +++ b/Makefile.am @@ -0,0 +1,6 @@ +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 +SUBDIRS = src include tools + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libusbmuxd.pc @@ -0,0 +1,214 @@ +Background +========== + +'usbmuxd' stands for "USB multiplexing daemon". This daemon is in charge of +multiplexing connections over USB to an iPhone or iPod touch. To users, it means +you can sync your music, contacts, photos, etc. over USB. To developers, it +means you can connect to any listening localhost socket on the device. usbmuxd +is not used for tethering data transfer, which uses a dedicated USB interface as +a virtual network device. + +Multiple connections to different TCP ports can happen in parallel. An example +(and useful) tool called 'iproxy' is included that allows you to forward +localhost ports to the device---allows SSH over USB on jailbroken devices, or +allowing access the lockdown daemon (and then to all of the file access, sync, +notification and backup services running on the device). + +The higher-level layers are handled by libimobiledevice. 'ifuse' is then able +to sit on top of this and mount your device's AFC filesystem share. + +This package contains the usbmuxd communication interface library 'libusbmuxd'. + +There is also a Python implementation of the client library in the python-client +directory, and an example tcprelay.py which performs a similar function to iproxy. +This implementation supports OSX and Windows and the new iTunes plist-based +usbmuxd protocol, so it is portable and will run on those operating systems with +no modification, using Apple's native usbmuxd. This is useful if you need to +tunnel to your device from another OS in a pinch. Run python tcpclient.py --help +for usage information. + +License +======= + +The contents of this package are licensed under the GNU Lesser General Public +License, version 2.1 or, at your option, any later version (see COPYING.LGPLv2.1). +If a more permissive license is specified at the top of a source file, it takes +precedence over this. + +Legal +===== + +Apple, iPhone, and iPod touch are trademarks of Apple Inc., registered in the +U.S. and other countries. + +Building +======== + + ./autogen.sh + make + sudo make install + +Running (with magic) +==================== + + (Unplug + replug your jailbroken device) + ./iproxy 2222 22 & + ssh -p 2222 root@localhost + +Hopefully you get the normal SSH login prompt. You may still lots of debugging +output for the moment. If this is getting in the way of your ssh login, then +run the 'ssh' command from a different xterminal or virtual console. Of course, +you need to have OpenSSH installed on your jailbroken device for this to work. + +If you have iFuse, you can run "ifuse <mountpoint">. This doesn't require +iproxy and works on all devices, jailbroken or not. + +Running (without magic) +======================= + +If 'udev' is _not_ automatically running on your machine and picking up the new +.rules file, you will need to start usbmuxd by hand first. Check it's running +and that there is only one copy with 'ps aux | grep +usbmuxd'. + + sudo usbmuxd -U -v -v & + ./iproxy 2222 22 & + ssh -p 2222 root@localhost + +Tip: Starting SSH if disabled +============================= + +If your device is rooted, but SSH isn't started and you _cannot_ (for instance, +cracked/broken screen) get to the Services control panel on the device, then you +can start the SSH service over the USB by mounting the (jailbroken) filesystem. + +You will need to mount it using 'ifuse --afc2' (to access the root directory of +the device), and then edit: + + /Library/LaunchDaemons/com.openssh.sshd.plist + +to _remove_ the lines: + + <key>Disabled</key> + <true/> + +Reboot the device and then sshd should be running. + +TODO +==== + +The server currently assumes that the device is well-behaved and does not do a +bunch of checks like looking for the expected SEQ and ACK numbers from it. This +is normally not an issue, but it's annoying for debugging because lost packets +(which shouldn't happen, but can happen if the code is buggy) mean that stuff +gets out of sync and then might crash and burn dozens of packets later. + +The server needs more testing, and some optimizing. + +Someone should probably do some edge-case testing on the TCP stuff. + +The outgoing ACK handling on the server probably needs some thought. Currently, +when there's an outstanding ACK, we send it after a timeout (to avoid sending +a no-payload ACK packet for everything the phone sends us). However, there's +probably a better way of doing this. + +Architecture information +======================== + +The iPhone / iPod Touch basically implements a rather strange USB networking +system that operates at a higher level. It is of course completely proprietary. +Generally speaking, this is what goes on in a typical usage scenario: + +0. iTunes opens a connection to usbmuxd and asks it for device notifications +1. User inserts phone into computer +2. usbmuxd notices the phone and pings it with a version packet +3. phone replies +4. usbmuxd now considers the phone to be connected and tells iTunes +5. iTunes opens another separate connection to usbmuxd and asks it to connect + to, say, the afc port on the device +6. usbmuxd sends a pseudo-TCP SYN packet to the phone +7. the phone's kernel driver receives the SYN packet and itself opens a + TCP connection to localhost on the afc port +8. the phone replies with a pseudo-TCP SYN/ACK indicating that the port is open + and the connection can proceed +7. usbmuxd sends a final ACK to the phone +8. usbmuxd replies to iTunes with a "connection successful" message +9. any data that iTunes writes to the usbmuxd socket from now on is forwarded, + through pseudo-TCP, through USB, back into a more regular TCP connection to + localhost, to the afc daemon on the phone, and vice versa + +The usbmuxd protocol is a relatively simple binary message protocol documented +here: + +http://wikee.iphwn.org/usb:usbmux + +Note that once a connection is established the UNIX socket essentially becomes +a dedicated pipe to the TCP connction and no more high-level control is +possible (closing the socket closes the TCP connection). Ditto for the "listen +for devices" mode - usbmuxd will reject any commands in such mode, and the +socket essentially becomes a dedicated device notification pipe. This means +that you need, at minimum, TWO connections to usbmuxd to do anything useful. + +On Windows, usbmuxd works the same way but a TCP connection to localhost port +27015 replaces the UNIX socket. On OSX, the UNIX socket is /var/run/usbmuxd. The +server and client implemented here default the same /var/run/usbmuxd socket. + +The phone protocol operates over a pair of USB bulk endpoints. There is an outer +layer used for packet size info and a "protocol" (version and TCP are the only +two options), and that header is followed by TCP headers for actual data comms. +However, the protocol isn't actual TCP, just a custom protocol which for some +reason uses a standard TCP header and leaves most fields unused. + +There is no reordering or retransmission. There are no checksums, no URG, no +PSH, no non-ACK, no FIN. What there *is* is the SEQ/ACK/window mechanism used +for flow control, and RST is used as the only connection teardown mechanism (and +also for "connection refused"), and the connection startup is SYN/SYNACK/ACK. + +Windows are constant-scaled by 8 bits. This is legal TCP as long as the +corresponding option is negotiated. Of course, no such negotiation happens on +this protocol. + +Note that, since there are no retransmissions, there is some overlap between ACK +and window for flow control. For example, the server doesn't ever touch its +window size, and just refuses to ACK stuff if its buffers are full and the +client isn't reading data. The phone happily seems to stop sending stuff. + +Also, if the phone RSTs you out of nowhere, look at the packet payload for a +textual error message. Note: if it claims to suffer from amnesia, that probably +means you overflowed its input buffer by ignoring its flow control / window +size. Go figure. Probably a logic bug in the kernel code. + +Note that all normal packets need to have flags set to ACK (and only ACK). There +is no support for, erm, not-acking. Keep the ack number field valid at all +times. + +The usbmuxd CONNECT request port field is byte-swapped (network-endian). This is +even more annoying for the plist based protocol, since it's even true there +(where the field is plain text). So even for the plain text int, you need to +swap the bytes (port 22 becomes <integer>5632</integer>). I have no clue if this +is the case on the new plist protocol on PPC macs (is the newer iTunes available +for those?) + +There are a bunch of gotchas due to the USB framing, and this is even worse +because implementations tend to get it wrong (i.e. libusb, and this is the +reason for the patch). Basically, USB Bulk offers, at the low level, the ability +to transfer packets from 0 to wMaxPacketSize (512 here) bytes, period. There is +no other support for higher level framing of transfers. The way you do those is +by breaking them up into packets, and the final shorter packet marks the end of +the transfer. The critical bit is that, if the transfer happens to be divisible +by 512, you send a zero-length packet (ZLP) to indicate the end of the transfer. +Libusb doesn't set this option by default and the iPhone gets packets stuck to +each other, which it doesn't like. Actually, this framing is sort of redundant +because the usbmux packet header includes a length field, but the phone still +wants the ZLPs or else it breaks. To make matters worse, usbdevfs imposes a max +transfer size of 16k, so libusb breaks transfers into that size. This is okay +for sending as long as the ZLP is only added to the last transfer (the patch +does that), but it can easily cause nasty race conditions on RX due to libusb +doing multiple outstanding reads at the same time and then cancelling the rest +when shorter data arrives (but what if some data got into the other requests +already?), so we only do 16k reads and stick them together ourselves by looking +at the packet size header. We still depend on ZLPs being sent to end transfers +at non-16k boundaries that are multiples of 512, but that seems to work fine. I +guess the ZLPs might cause spurious 0-byte transfers to show up on RX if things +line up right, but we ignore those. By the way, the maximum packet/transfer size +is 65535 bytes due to the 16-bit length header of the usbmux protocol. diff --git a/README.devel b/README.devel new file mode 100644 index 0000000..727e095 --- /dev/null +++ b/README.devel @@ -0,0 +1,50 @@ +Background +========== + +'libusbmuxd' makes it really simple to talk to a running 'usbmuxd' and +hides all the details for you. There are two function calls: + +usbmuxd_scan() +-------------- + +This returns a list of all available iPhone-like devices that are +available for talking to. The returned array contains the USB +product_id, hex formatted serial_number of any iPhones/iTouches and a +non-descript 'handle' for all those devices that are within range (as +of March 2009, that means a device directly plugged into the +computer's USB port). + +Once you have found the device you want to communicate with, take its +'handle' and pass this to usbmuxd_connect(). + +usbmuxd_connect() +----------------- + +This takes a handle, a destination port number and tries to setup +a proxy a connection. It returns a file-descriptor which you should +be able to read(), write() and select() on like any other active network +socket connection. + + +Technical details +================= + +When usbmuxd is running (normally started, or stopped as a result of +'udev' auto-insertion messages), it provides a socket interface in +'/var/run/usbmuxd' that is designed to be compatible with the socket +interface that is provided on MacOSX. + +The structures for communicating over this device are documented +in the 'usbmuxd-proto.h', but you shouldn't need to view them +directly if you are using the libusbmuxd.so library for easy access. + + +Example +======= + +#include <usbmuxd.h> + +... + +gcc -o leetphone leetphone.c -lusbmuxd + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..3292973 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,15 @@ +#!/bin/sh +gprefix=`which glibtoolize 2>&1 >/dev/null` +if [ $? -eq 0 ]; then + glibtoolize --force +else + libtoolize --force +fi +aclocal -I m4 +autoheader +automake --add-missing +autoconf + +if [ -z "$NOCONFIGURE" ]; then + ./configure "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6948d18 --- /dev/null +++ b/configure.ac @@ -0,0 +1,124 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) +AC_INIT(libusbmuxd, 1.0.8, nospam@nowhere.com) +AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES]) +AC_CONFIG_SRCDIR([src/]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +dnl libtool versioning +# +1 : 0 : +1 == adds new functions to the interface +# +1 : 0 : 0 == changes or removes functions (changes include both +# changes to the signature and the semantic) +# ? :+1 : ? == just internal changes +# CURRENT : REVISION : AGE +LIBUSBMUXD_SO_VERSION=2:0:0 + +AC_SUBST(LIBUSBMUXD_SO_VERSION) + +# Checks for programs. +AC_PROG_CC +AC_PROG_CXX +AM_PROG_CC_C_O +AC_PROG_LIBTOOL + +# Checks for libraries. +PKG_CHECK_MODULES(libplist, libplist >= 1.9, have_plist=yes, have_plist=no) +AC_CHECK_HEADERS([sys/inotify.h], have_inotify=yes, have_inotify=no) + +AC_ARG_WITH([protov1], + [AS_HELP_STRING([--without-protov1], + [do not build with protocol v1 support (default is yes)])], + [with_protov1=no], + [with_protov1=yes]) + +if test "x$have_plist" = "xyes"; then + if test "x$with_protov1" != "xyes"; then + have_plist=no + echo "*** Note: Protocol V1 support has been disabled ***" + else + AC_DEFINE(HAVE_PLIST, 1, [Define if you have libplist support]) + AC_SUBST(libplist_CFLAGS) + AC_SUBST(libplist_LIBS) + fi +else + if test "x$with_protov1" == "xyes"; then + AC_MSG_ERROR([protocol V1 support requested but libplist could not be found]) + fi +fi + +AC_ARG_WITH([inotify], + [AS_HELP_STRING([--without-inotify], + [(Linux only) do not build with inotify support (default is yes)])], + [with_inotify=no], + [with_inotify=yes]) + +if test "x$have_inotify" = "xyes"; then + if test "x$with_inotify" != "xyes"; then + have_inotify=no + echo "*** Note: inotify support has been disabled ***" + else + AC_DEFINE(HAVE_INOTIFY, 1, [Define if you have inotify support (linux only)]) + fi +fi + +# Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS([stdint.h stdlib.h string.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([strcasecmp strdup strerror strndup]) + +# Check for operating system +AC_MSG_CHECKING([whether to enable WIN32 build settings]) +case ${host_os} in + *mingw32*|*cygwin*) + win32=true + AC_MSG_RESULT([yes]) + AC_CHECK_TOOL([WINDRES], [windres], AC_MSG_ERROR([windres not found])) + AC_SUBST(WINDRES) + ;; + *) + win32=false + AC_MSG_RESULT([no]) + ;; +esac +AM_CONDITIONAL(WIN32, test x$win32 = xtrue) + +AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wmissing-declarations -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter") +AC_SUBST(GLOBAL_CFLAGS) + +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +AC_OUTPUT([ +Makefile +src/Makefile +include/Makefile +tools/Makefile +libusbmuxd.pc +]) + +echo " +Configuration for $PACKAGE $VERSION: +------------------------------------------- + + Install prefix: .........: $prefix + Protocol v1 support: ....: $have_plist + inotify support (Linux) .: $have_inotify + + Now type 'make' to build $PACKAGE $VERSION, + and then 'make install' for installation. +" diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..f6bc922 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = + +nobase_include_HEADERS = usbmuxd.h \ + usbmuxd-proto.h diff --git a/include/usbmuxd-proto.h b/include/usbmuxd-proto.h new file mode 100644 index 0000000..be9e709 --- /dev/null +++ b/include/usbmuxd-proto.h @@ -0,0 +1,97 @@ +/* + libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009 Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com> + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/* Protocol defintion for usbmuxd proxy protocol */ +#ifndef __USBMUXD_PROTO_H +#define __USBMUXD_PROTO_H + +#include <stdint.h> +#define USBMUXD_PROTOCOL_VERSION 0 + +#if defined(WIN32) || defined(__CYGWIN__) +#define USBMUXD_SOCKET_PORT 27015 +#else +#define USBMUXD_SOCKET_FILE "/var/run/usbmuxd" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum usbmuxd_result { + RESULT_OK = 0, + RESULT_BADCOMMAND = 1, + RESULT_BADDEV = 2, + RESULT_CONNREFUSED = 3, + // ??? + // ??? + RESULT_BADVERSION = 6, +}; + +enum usbmuxd_msgtype { + MESSAGE_RESULT = 1, + MESSAGE_CONNECT = 2, + MESSAGE_LISTEN = 3, + MESSAGE_DEVICE_ADD = 4, + MESSAGE_DEVICE_REMOVE = 5, + //??? + //??? + MESSAGE_PLIST = 8, +}; + +struct usbmuxd_header { + uint32_t length; // length of message, including header + uint32_t version; // protocol version + uint32_t message; // message type + uint32_t tag; // responses to this query will echo back this tag +} __attribute__((__packed__)); + +struct usbmuxd_result_msg { + struct usbmuxd_header header; + uint32_t result; +} __attribute__((__packed__)); + +struct usbmuxd_connect_request { + struct usbmuxd_header header; + uint32_t device_id; + uint16_t port; // TCP port number + uint16_t reserved; // set to zero +} __attribute__((__packed__)); + +struct usbmuxd_listen_request { + struct usbmuxd_header header; +} __attribute__((__packed__)); + +struct usbmuxd_device_record { + uint32_t device_id; + uint16_t product_id; + char serial_number[256]; + uint16_t padding; + uint32_t location; +} __attribute__((__packed__)); + +#ifdef __cplusplus +} +#endif + +#endif /* __USBMUXD_PROTO_H */ diff --git a/include/usbmuxd.h b/include/usbmuxd.h new file mode 100644 index 0000000..4ae71e2 --- /dev/null +++ b/include/usbmuxd.h @@ -0,0 +1,191 @@ +/* + libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009 Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009 Martin Szulecki <opensuse@sukimashita.com> + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef __USBMUXD_H +#define __USBMUXD_H +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Device information structure holding data to identify the device. + * The relevant 'handle' should be passed to 'usbmuxd_connect()', to + * start a proxy connection. The value 'handle' should be considered + * opaque and no presumption made about the meaning of its value. + */ +typedef struct { + uint32_t handle; + int product_id; + char udid[41]; +} usbmuxd_device_info_t; + +/** + * event types for event callback function + */ +enum usbmuxd_event_type { + UE_DEVICE_ADD = 1, + UE_DEVICE_REMOVE +}; + +/** + * Event structure that will be passed to the callback function. + * 'event' will contains the type of the event, and 'device' will contains + * information about the device. + */ +typedef struct { + int event; + usbmuxd_device_info_t device; +} usbmuxd_event_t; + +/** + * Callback function prototype. + */ +typedef void (*usbmuxd_event_cb_t) (const usbmuxd_event_t *event, void *user_data); + +/** + * Subscribe a callback function so that applications get to know about + * device add/remove events. + * + * @param callback A callback function that is executed when an event occurs. + * + * @return 0 on success or negative on error. + */ +int usbmuxd_subscribe(usbmuxd_event_cb_t callback, void *user_data); + +/** + * Unsubscribe callback. + * + * @return only 0 for now. + */ +int usbmuxd_unsubscribe(); + +/** + * Contacts usbmuxd and retrieves a list of connected devices. + * + * @param device_list A pointer to an array of usbmuxd_device_info_t + * that will hold records of the connected devices. The last record + * is a null-terminated record with all fields set to 0/NULL. + * @note The user has to free the list returned. + * + * @return number of attached devices, zero on no devices, or negative + * if an error occured. + */ +int usbmuxd_get_device_list(usbmuxd_device_info_t **device_list); + +/** + * Frees the device list returned by an usbmuxd_get_device_list call + * + * @param device_list A pointer to an array of usbmuxd_device_info_t to free. + * + * @return 0 on success, -1 on error. + */ +int usbmuxd_device_list_free(usbmuxd_device_info_t **device_list); + +/** + * Gets device information for the device specified by udid. + * + * @param udid A device UDID of the device to look for. If udid is NULL, + * This function will return the first device found. + * @param device Pointer to a previously allocated (or static) + * usbmuxd_device_info_t that will be filled with the device info. + * + * @return 0 if no matching device is connected, 1 if the device was found, + * or a negative value on error. + */ +int usbmuxd_get_device_by_udid(const char *udid, usbmuxd_device_info_t *device); + +/** + * Request proxy connect to + * + * @param handle returned by 'usbmuxd_scan()' + * + * @param tcp_port TCP port number on device, in range 0-65535. + * common values are 62078 for lockdown, and 22 for SSH. + * + * @return file descriptor socket of the connection, or -1 on error + */ +int usbmuxd_connect(const int handle, const unsigned short tcp_port); + +/** + * Disconnect. For now, this just closes the socket file descriptor. + * + * @param sfd socker file descriptor returned by usbmuxd_connect() + * + * @return 0 on success, -1 on error. + */ +int usbmuxd_disconnect(int sfd); + +/** + * Send data to the specified socket. + * + * @param sfd socket file descriptor returned by usbmuxd_connect() + * @param data buffer to send + * @param len size of buffer to send + * @param sent_bytes how many bytes sent + * + * @return 0 on success, a negative errno value otherwise. + */ +int usbmuxd_send(int sfd, const char *data, uint32_t len, uint32_t *sent_bytes); + +/** + * Receive data from the specified socket. + * + * @param sfd socket file descriptor returned by usbmuxd_connect() + * @param data buffer to put the data to + * @param len number of bytes to receive + * @param recv_bytes number of bytes received + * @param timeout how many milliseconds to wait for data + * + * @return 0 on success, a negative errno value otherwise. + */ +int usbmuxd_recv_timeout(int sfd, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout); + +/** + * Receive data from the specified socket with a default timeout. + * + * @param sfd socket file descriptor returned by usbmuxd_connect() + * @param data buffer to put the data to + * @param len number of bytes to receive + * @param recv_bytes number of bytes received + * + * @return 0 on success, a negative errno value otherwise. + */ +int usbmuxd_recv(int sfd, char *data, uint32_t len, uint32_t *recv_bytes); + +/** + * Enable or disable the use of inotify extension. Enabled by default. + * Use 0 to disable and 1 to enable inotify support. + * This only has an effect on linux systems if inotify support has been built + * in. Otherwise and on all other platforms this function has no effect. + */ +void libusbmuxd_set_use_inotify(int set); + +void libusbmuxd_set_debug_level(int level); + +#ifdef __cplusplus +} +#endif + +#endif /* __USBMUXD_H */ diff --git a/libusbmuxd.pc.in b/libusbmuxd.pc.in new file mode 100644 index 0000000..551ad44 --- /dev/null +++ b/libusbmuxd.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libusbmuxd +Description: A library to communicate with the usbmux daemon +Version: @VERSION@ +Libs: -L${libdir} -lusbmuxd +Cflags: -I${includedir} + diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4 new file mode 100644 index 0000000..0f660cf --- /dev/null +++ b/m4/as-compiler-flag.m4 @@ -0,0 +1,62 @@ +dnl as-compiler-flag.m4 0.1.0 + +dnl autostars m4 macro for detection of compiler flags + +dnl David Schleef <ds@schleef.org> + +dnl $Id: as-compiler-flag.m4,v 1.1 2005/12/15 23:35:19 ds Exp $ + +dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED]) +dnl Tries to compile with the given CFLAGS. +dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags, +dnl and ACTION-IF-NOT-ACCEPTED otherwise. + +AC_DEFUN([AS_COMPILER_FLAG], +[ + AC_MSG_CHECKING([to see if compiler understands $1]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $1" + + AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + m4_ifvaln([$2],[$2]) + true + else + m4_ifvaln([$3],[$3]) + true + fi + AC_MSG_RESULT([$flag_ok]) +]) + +dnl AS_COMPILER_FLAGS(VAR, FLAGS) +dnl Tries to compile with the given CFLAGS. + +AC_DEFUN([AS_COMPILER_FLAGS], +[ + list=$2 + flags_supported="" + flags_unsupported="" + AC_MSG_CHECKING([for supported compiler flags]) + for each in $list + do + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $each" + AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + flags_supported="$flags_supported $each" + else + flags_unsupported="$flags_unsupported $each" + fi + done + AC_MSG_RESULT([$flags_supported]) + if test "X$flags_unsupported" != X ; then + AC_MSG_WARN([unsupported compiler flags: $flags_unsupported]) + fi + $1="$$1 $flags_supported" +]) + diff --git a/python-client/.gitignore b/python-client/.gitignore new file mode 100644 index 0000000..5da7ef5 --- /dev/null +++ b/python-client/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.pyo + diff --git a/python-client/tcprelay.py b/python-client/tcprelay.py new file mode 100644 index 0000000..add200c --- /dev/null +++ b/python-client/tcprelay.py @@ -0,0 +1,148 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# tcprelay.py - TCP connection relay for usbmuxd +# +# Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import usbmux +import SocketServer +import select +from optparse import OptionParser +import sys +import threading + +class SocketRelay(object): + def __init__(self, a, b, maxbuf=65535): + self.a = a + self.b = b + self.atob = "" + self.btoa = "" + self.maxbuf = maxbuf + def handle(self): + while True: + rlist = [] + wlist = [] + xlist = [self.a, self.b] + if self.atob: + wlist.append(self.b) + if self.btoa: + wlist.append(self.a) + if len(self.atob) < self.maxbuf: + rlist.append(self.a) + if len(self.btoa) < self.maxbuf: + rlist.append(self.b) + rlo, wlo, xlo = select.select(rlist, wlist, xlist) + if xlo: + return + if self.a in wlo: + n = self.a.send(self.btoa) + self.btoa = self.btoa[n:] + if self.b in wlo: + n = self.b.send(self.atob) + self.atob = self.atob[n:] + if self.a in rlo: + s = self.a.recv(self.maxbuf - len(self.atob)) + if not s: + return + self.atob += s + if self.b in rlo: + s = self.b.recv(self.maxbuf - len(self.btoa)) + if not s: + return + self.btoa += s + #print "Relay iter: %8d atob, %8d btoa, lists: %r %r %r"%(len(self.atob), len(self.btoa), rlo, wlo, xlo) + +class TCPRelay(SocketServer.BaseRequestHandler): + def handle(self): + print "Incoming connection to %d"%self.server.server_address[1] + mux = usbmux.USBMux(options.sockpath) + print "Waiting for devices..." + if not mux.devices: + mux.process(1.0) + if not mux.devices: + print "No device found" + self.request.close() + return + dev = mux.devices[0] + print "Connecting to device %s"%str(dev) + dsock = mux.connect(dev, self.server.rport) + lsock = self.request + print "Connection established, relaying data" + try: + fwd = SocketRelay(dsock, lsock, self.server.bufsize * 1024) + fwd.handle() + finally: + dsock.close() + lsock.close() + print "Connection closed" + +class TCPServer(SocketServer.TCPServer): + allow_reuse_address = True + +class ThreadedTCPServer(SocketServer.ThreadingMixIn, TCPServer): + pass + +HOST = "localhost" + +parser = OptionParser(usage="usage: %prog [OPTIONS] RemotePort[:LocalPort] [RemotePort[:LocalPort]]...") +parser.add_option("-t", "--threaded", dest='threaded', action='store_true', default=False, help="use threading to handle multiple connections at once") +parser.add_option("-b", "--bufsize", dest='bufsize', action='store', metavar='KILOBYTES', type='int', default=128, help="specify buffer size for socket forwarding") +parser.add_option("-s", "--socket", dest='sockpath', action='store', metavar='PATH', type='str', default=None, help="specify the path of the usbmuxd socket") + +options, args = parser.parse_args() + +serverclass = TCPServer +if options.threaded: + serverclass = ThreadedTCPServer + +if len(args) == 0: + parser.print_help() + sys.exit(1) + +ports = [] + +for arg in args: + try: + if ':' in arg: + rport, lport = arg.split(":") + rport = int(rport) + lport = int(lport) + ports.append((rport, lport)) + else: + ports.append((int(arg), int(arg))) + except: + parser.print_help() + sys.exit(1) + +servers=[] + +for rport, lport in ports: + print "Forwarding local port %d to remote port %d"%(lport, rport) + server = serverclass((HOST, lport), TCPRelay) + server.rport = rport + server.bufsize = options.bufsize + servers.append(server) + +alive = True + +while alive: + try: + rl, wl, xl = select.select(servers, [], []) + for server in rl: + server.handle_request() + except: + alive = False diff --git a/python-client/usbmux.py b/python-client/usbmux.py new file mode 100644 index 0000000..79ec26a --- /dev/null +++ b/python-client/usbmux.py @@ -0,0 +1,246 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# usbmux.py - usbmux client library for Python +# +# Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import socket, struct, select, sys + +try: + import plistlib + haveplist = True +except: + haveplist = False + +class MuxError(Exception): + pass + +class MuxVersionError(MuxError): + pass + +class SafeStreamSocket: + def __init__(self, address, family): + self.sock = socket.socket(family, socket.SOCK_STREAM) + self.sock.connect(address) + def send(self, msg): + totalsent = 0 + while totalsent < len(msg): + sent = self.sock.send(msg[totalsent:]) + if sent == 0: + raise MuxError("socket connection broken") + totalsent = totalsent + sent + def recv(self, size): + msg = '' + while len(msg) < size: + chunk = self.sock.recv(size-len(msg)) + if chunk == '': + raise MuxError("socket connection broken") + msg = msg + chunk + return msg + +class MuxDevice(object): + def __init__(self, devid, usbprod, serial, location): + self.devid = devid + self.usbprod = usbprod + self.serial = serial + self.location = location + def __str__(self): + return "<MuxDevice: ID %d ProdID 0x%04x Serial '%s' Location 0x%x>"%(self.devid, self.usbprod, self.serial, self.location) + +class BinaryProtocol(object): + TYPE_RESULT = 1 + TYPE_CONNECT = 2 + TYPE_LISTEN = 3 + TYPE_DEVICE_ADD = 4 + TYPE_DEVICE_REMOVE = 5 + VERSION = 0 + def __init__(self, socket): + self.socket = socket + self.connected = False + + def _pack(self, req, payload): + if req == self.TYPE_CONNECT: + return struct.pack("IH", payload['DeviceID'], payload['PortNumber']) + "\x00\x00" + elif req == self.TYPE_LISTEN: + return "" + else: + raise ValueError("Invalid outgoing request type %d"%req) + + def _unpack(self, resp, payload): + if resp == self.TYPE_RESULT: + return {'Number':struct.unpack("I", payload)[0]} + elif resp == self.TYPE_DEVICE_ADD: + devid, usbpid, serial, pad, location = struct.unpack("IH256sHI", payload) + serial = serial.split("\0")[0] + return {'DeviceID': devid, 'Properties': {'LocationID': location, 'SerialNumber': serial, 'ProductID': usbpid}} + elif resp == self.TYPE_DEVICE_REMOVE: + devid = struct.unpack("I", payload)[0] + return {'DeviceID': devid} + else: + raise MuxError("Invalid incoming request type %d"%req) + + def sendpacket(self, req, tag, payload={}): + payload = self._pack(req, payload) + if self.connected: + raise MuxError("Mux is connected, cannot issue control packets") + length = 16 + len(payload) + data = struct.pack("IIII", length, self.VERSION, req, tag) + payload + self.socket.send(data) + def getpacket(self): + if self.connected: + raise MuxError("Mux is connected, cannot issue control packets") + dlen = self.socket.recv(4) + dlen = struct.unpack("I", dlen)[0] + body = self.socket.recv(dlen - 4) + version, resp, tag = struct.unpack("III",body[:0xc]) + if version != self.VERSION: + raise MuxVersionError("Version mismatch: expected %d, got %d"%(self.VERSION,version)) + payload = self._unpack(resp, body[0xc:]) + return (resp, tag, payload) + +class PlistProtocol(BinaryProtocol): + TYPE_RESULT = "Result" + TYPE_CONNECT = "Connect" + TYPE_LISTEN = "Listen" + TYPE_DEVICE_ADD = "Attached" + TYPE_DEVICE_REMOVE = "Detached" #??? + TYPE_PLIST = 8 + VERSION = 1 + def __init__(self, socket): + if not haveplist: + raise Exception("You need the plistlib module") + BinaryProtocol.__init__(self, socket) + + def _pack(self, req, payload): + return payload + + def _unpack(self, resp, payload): + return payload + + def sendpacket(self, req, tag, payload={}): + payload['ClientVersionString'] = 'usbmux.py by marcan' + if isinstance(req, int): + req = [self.TYPE_CONNECT, self.TYPE_LISTEN][req-2] + payload['MessageType'] = req + payload['ProgName'] = 'tcprelay' + BinaryProtocol.sendpacket(self, self.TYPE_PLIST, tag, plistlib.writePlistToString(payload)) + def getpacket(self): + resp, tag, payload = BinaryProtocol.getpacket(self) + if resp != self.TYPE_PLIST: + raise MuxError("Received non-plist type %d"%resp) + payload = plistlib.readPlistFromString(payload) + return payload['MessageType'], tag, payload + +class MuxConnection(object): + def __init__(self, socketpath, protoclass): + self.socketpath = socketpath + if sys.platform in ['win32', 'cygwin']: + family = socket.AF_INET + address = ('127.0.0.1', 27015) + else: + family = socket.AF_UNIX + address = self.socketpath + self.socket = SafeStreamSocket(address, family) + self.proto = protoclass(self.socket) + self.pkttag = 1 + self.devices = [] + + def _getreply(self): + while True: + resp, tag, data = self.proto.getpacket() + if resp == self.proto.TYPE_RESULT: + return tag, data + else: + raise MuxError("Invalid packet type received: %d"%resp) + def _processpacket(self): + resp, tag, data = self.proto.getpacket() + if resp == self.proto.TYPE_DEVICE_ADD: + self.devices.append(MuxDevice(data['DeviceID'], data['Properties']['ProductID'], data['Properties']['SerialNumber'], data['Properties']['LocationID'])) + elif resp == self.proto.TYPE_DEVICE_REMOVE: + for dev in self.devices: + if dev.devid == data['DeviceID']: + self.devices.remove(dev) + elif resp == self.proto.TYPE_RESULT: + raise MuxError("Unexpected result: %d"%resp) + else: + raise MuxError("Invalid packet type received: %d"%resp) + def _exchange(self, req, payload={}): + mytag = self.pkttag + self.pkttag += 1 + self.proto.sendpacket(req, mytag, payload) + recvtag, data = self._getreply() + if recvtag != mytag: + raise MuxError("Reply tag mismatch: expected %d, got %d"%(mytag, recvtag)) + return data['Number'] + + def listen(self): + ret = self._exchange(self.proto.TYPE_LISTEN) + if ret != 0: + raise MuxError("Listen failed: error %d"%ret) + def process(self, timeout=None): + if self.proto.connected: + raise MuxError("Socket is connected, cannot process listener events") + rlo, wlo, xlo = select.select([self.socket.sock], [], [self.socket.sock], timeout) + if xlo: + self.socket.sock.close() + raise MuxError("Exception in listener socket") + if rlo: + self._processpacket() + def connect(self, device, port): + ret = self._exchange(self.proto.TYPE_CONNECT, {'DeviceID':device.devid, 'PortNumber':((port<<8) & 0xFF00) | (port>>8)}) + if ret != 0: + raise MuxError("Connect failed: error %d"%ret) + self.proto.connected = True + return self.socket.sock + def close(self): + self.socket.sock.close() + +class USBMux(object): + def __init__(self, socketpath=None): + if socketpath is None: + if sys.platform == 'darwin': + socketpath = "/var/run/usbmuxd" + else: + socketpath = "/var/run/usbmuxd" + self.socketpath = socketpath + self.listener = MuxConnection(socketpath, BinaryProtocol) + try: + self.listener.listen() + self.version = 0 + self.protoclass = BinaryProtocol + except MuxVersionError: + self.listener = MuxConnection(socketpath, PlistProtocol) + self.listener.listen() + self.protoclass = PlistProtocol + self.version = 1 + self.devices = self.listener.devices + def process(self, timeout=None): + self.listener.process(timeout) + def connect(self, device, port): + connector = MuxConnection(self.socketpath, self.protoclass) + return connector.connect(device, port) + +if __name__ == "__main__": + mux = USBMux() + print "Waiting for devices..." + if not mux.devices: + mux.process(0.1) + while True: + print "Devices:" + for dev in mux.devices: + print dev + mux.process() diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..bf9198f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,16 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +AM_CFLAGS = $(GLOBAL_CFLAGS) $(libplist_CFLAGS) +AM_LDFLAGS = $(GLOBAL_LIBS) $(libplist_LIBS) + +lib_LTLIBRARIES = libusbmuxd.la +libusbmuxd_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBUSBMUXD_SO_VERSION) -no-undefined +libusbmuxd_la_LIBADD = +libusbmuxd_la_SOURCES = \ + collection.c collection.h \ + sock_stuff.c sock_stuff.h \ + libusbmuxd.c + +if WIN32 +libusbmuxd_la_LIBADD += ws2_32 +endif diff --git a/src/collection.c b/src/collection.c new file mode 100644 index 0000000..423cc4e --- /dev/null +++ b/src/collection.c @@ -0,0 +1,81 @@ +/* + libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com> +Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "collection.h" + +void collection_init(struct collection *col) +{ + col->list = malloc(sizeof(void *)); + memset(col->list, 0, sizeof(void *)); + col->capacity = 1; +} + +void collection_free(struct collection *col) +{ + free(col->list); + col->list = NULL; + col->capacity = 0; +} + +void collection_add(struct collection *col, void *element) +{ + int i; + for(i=0; i<col->capacity; i++) { + if(!col->list[i]) { + col->list[i] = element; + return; + } + } + col->list = realloc(col->list, sizeof(void*) * col->capacity * 2); + memset(&col->list[col->capacity], 0, sizeof(void *) * col->capacity); + col->list[col->capacity] = element; + col->capacity *= 2; +} + +void collection_remove(struct collection *col, void *element) +{ + int i; + for(i=0; i<col->capacity; i++) { + if(col->list[i] == element) { + col->list[i] = NULL; + return; + } + } + fprintf(stderr, "%s: WARNING: element %p not present in collection %p (cap %d)", __func__, element, col, col->capacity); +} + +int collection_count(struct collection *col) +{ + int i, cnt = 0; + for(i=0; i<col->capacity; i++) { + if(col->list[i]) + cnt++; + } + return cnt; +} diff --git a/src/collection.h b/src/collection.h new file mode 100644 index 0000000..e9b6403 --- /dev/null +++ b/src/collection.h @@ -0,0 +1,48 @@ +/* + libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com> +Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef __COLLECTION_H__ +#define __COLLECTION_H__ + +struct collection { + void **list; + int capacity; +}; + +void collection_init(struct collection *col); +void collection_add(struct collection *col, void *element); +void collection_remove(struct collection *col, void *element); +int collection_count(struct collection *col); +void collection_free(struct collection *col); + +#define FOREACH(var, col) \ + do { \ + int _iter; \ + for(_iter=0; _iter<(col)->capacity; _iter++) { \ + if(!(col)->list[_iter]) continue; \ + var = (col)->list[_iter]; + +#define ENDFOREACH \ + } \ + } while(0); + +#endif diff --git a/src/libusbmuxd.c b/src/libusbmuxd.c new file mode 100644 index 0000000..64b3725 --- /dev/null +++ b/src/libusbmuxd.c @@ -0,0 +1,980 @@ +/* + libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009-2010 Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009 Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009 Martin Szulecki <opensuse@sukimashita.com> + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include <stdint.h> +#include <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WIN32 +#include <windows.h> +#include <winsock2.h> +#define sleep(x) Sleep(x*1000) +#ifndef EPROTO +#define EPROTO 134 +#endif +#ifndef EBADMSG +#define EBADMSG 104 +#endif +#else +#include <sys/socket.h> +#include <arpa/inet.h> +#include <pthread.h> +#endif + +#ifdef HAVE_INOTIFY +#include <sys/inotify.h> +#define EVENT_SIZE (sizeof (struct inotify_event)) +#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16)) +#define USBMUXD_DIRNAME "/var/run" +#define USBMUXD_SOCKET_NAME "usbmuxd" +#endif /* HAVE_INOTIFY */ + +#include <unistd.h> +#include <signal.h> + +#ifdef HAVE_PLIST +#include <plist/plist.h> +#define PLIST_BUNDLE_ID "com.marcansoft.usbmuxd" +#define PLIST_CLIENT_VERSION_STRING "usbmuxd built for freedom" +#define PLIST_PROGNAME "libusbmuxd" +#endif + +// usbmuxd public interface +#include "usbmuxd.h" +// usbmuxd protocol +#include "usbmuxd-proto.h" +// socket utility functions +#include "sock_stuff.h" +// misc utility functions +#include "collection.h" + +static int libusbmuxd_debug = 2; +#define DEBUG(x, y, ...) if (x <= libusbmuxd_debug) fprintf(stderr, (y), __VA_ARGS__); + +static struct collection devices; +static usbmuxd_event_cb_t event_cb = NULL; +#ifdef WIN32 +HANDLE devmon = NULL; +CRITICAL_SECTION mutex; +static int mutex_initialized = 0; +#define LOCK if (!mutex_initialized) { InitializeCriticalSection(&mutex); mutex_initialized = 1; } EnterCriticalSection(&mutex); +#define UNLOCK LeaveCriticalSection(&mutex); +#else +pthread_t devmon; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +#define LOCK pthread_mutex_lock(&mutex) +#define UNLOCK pthread_mutex_unlock(&mutex) +#endif +static int listenfd = -1; + +static int use_tag = 0; +static int proto_version = 0; + +/** + * Finds a device info record by its handle. + * if the record is not found, NULL is returned. + */ +static usbmuxd_device_info_t *devices_find(uint32_t handle) +{ + FOREACH(usbmuxd_device_info_t *dev, &devices) { + if (dev && dev->handle == handle) { + return dev; + } + } ENDFOREACH + return NULL; +} + +/** + * Creates a socket connection to usbmuxd. + * For Mac/Linux it is a unix domain socket, + * for Windows it is a tcp socket. + */ +static int connect_usbmuxd_socket() +{ +#if defined(WIN32) || defined(__CYGWIN__) + return connect_socket("127.0.0.1", USBMUXD_SOCKET_PORT); +#else + return connect_unix_socket(USBMUXD_SOCKET_FILE); +#endif +} + +static int receive_packet(int sfd, struct usbmuxd_header *header, void **payload, int timeout) +{ + int recv_len; + struct usbmuxd_header hdr; + char *payload_loc = NULL; + + header->length = 0; + header->version = 0; + header->message = 0; + header->tag = 0; + + recv_len = recv_buf_timeout(sfd, &hdr, sizeof(hdr), 0, timeout); + if (recv_len < 0) { + return recv_len; + } else if ((size_t)recv_len < sizeof(hdr)) { + return recv_len; + } + + uint32_t payload_size = hdr.length - sizeof(hdr); + if (payload_size > 0) { + payload_loc = (char*)malloc(payload_size); + if (recv_buf_timeout(sfd, payload_loc, payload_size, 0, 5000) != (int)payload_size) { + DEBUG(1, "%s: Error receiving payload of size %d\n", __func__, payload_size); + free(payload_loc); + return -EBADMSG; + } + } + +#ifdef HAVE_PLIST + if (hdr.message == MESSAGE_PLIST) { + char *message = NULL; + plist_t plist = NULL; + plist_from_xml(payload_loc, payload_size, &plist); + free(payload_loc); + + if (!plist) { + DEBUG(1, "%s: Error getting plist from payload!\n", __func__); + return -EBADMSG; + } + + plist_t node = plist_dict_get_item(plist, "MessageType"); + if (plist_get_node_type(node) != PLIST_STRING) { + DEBUG(1, "%s: Error getting message type from plist!\n", __func__); + free(plist); + return -EBADMSG; + } + + plist_get_string_val(node, &message); + if (message) { + uint64_t val = 0; + if (strcmp(message, "Result") == 0) { + /* result message */ + uint32_t dwval = 0; + plist_t n = plist_dict_get_item(plist, "Number"); + plist_get_uint_val(n, &val); + *payload = malloc(sizeof(uint32_t)); + dwval = val; + memcpy(*payload, &dwval, sizeof(dwval)); + hdr.length = sizeof(hdr) + sizeof(dwval); + hdr.message = MESSAGE_RESULT; + } else if (strcmp(message, "Attached") == 0) { + /* device add message */ + struct usbmuxd_device_record *dev = NULL; + plist_t props = plist_dict_get_item(plist, "Properties"); + if (!props) { + DEBUG(1, "%s: Could not get properties for message '%s' from plist!\n", __func__, message); + free(message); + plist_free(plist); + return -EBADMSG; + } + dev = (struct usbmuxd_device_record*)malloc(sizeof(struct usbmuxd_device_record)); + memset(dev, 0, sizeof(struct usbmuxd_device_record)); + + plist_t n = plist_dict_get_item(props, "DeviceID"); + plist_get_uint_val(n, &val); + dev->device_id = (uint32_t)val; + + n = plist_dict_get_item(props, "ProductID"); + plist_get_uint_val(n, &val); + dev->product_id = (uint32_t)val; + + n = plist_dict_get_item(props, "SerialNumber"); + char *strval = NULL; + plist_get_string_val(n, &strval); + if (strval) { + strncpy(dev->serial_number, strval, 255); + free(strval); + } + n = plist_dict_get_item(props, "LocationID"); + plist_get_uint_val(n, &val); + dev->location = (uint32_t)val; + *payload = (void*)dev; + hdr.length = sizeof(hdr) + sizeof(struct usbmuxd_device_record); + hdr.message = MESSAGE_DEVICE_ADD; + } else if (strcmp(message, "Detached") == 0) { + /* device remove message */ + uint32_t dwval = 0; + plist_t n = plist_dict_get_item(plist, "DeviceID"); + if (n) { + plist_get_uint_val(n, &val); + *payload = malloc(sizeof(uint32_t)); + dwval = val; + memcpy(*payload, &dwval, sizeof(dwval)); + hdr.length = sizeof(hdr) + sizeof(dwval); + hdr.message = MESSAGE_DEVICE_REMOVE; + } + } else { + DEBUG(1, "%s: Unexpected message '%s' in plist!\n", __func__, message); + free(message); + plist_free(plist); + return -EBADMSG; + } + free(message); + } + plist_free(plist); + } else +#endif + { + *payload = payload_loc; + } + + memcpy(header, &hdr, sizeof(hdr)); + + return hdr.length; +} + +/** + * Retrieves the result code to a previously sent request. + */ +static int usbmuxd_get_result(int sfd, uint32_t tag, uint32_t * result) +{ + struct usbmuxd_header hdr; + int recv_len; + uint32_t *res = NULL; + + if (!result) { + return -EINVAL; + } + *result = -1; + + if ((recv_len = receive_packet(sfd, &hdr, (void**)&res, 5000)) < 0) { + DEBUG(1, "%s: Error receiving packet: %d\n", __func__, errno); + if (res) + free(res); + return -errno; + } + if ((size_t)recv_len < sizeof(hdr)) { + DEBUG(1, "%s: Received packet is too small!\n", __func__); + if (res) + free(res); + return -EPROTO; + } + + if (hdr.message == MESSAGE_RESULT) { + int ret = 0; + if (res && (hdr.tag == tag)) { + memcpy(result, res, sizeof(uint32_t)); + ret = 1; + } + if (res) + free(res); + return ret; + } + DEBUG(1, "%s: Unexpected message of type %d received!\n", __func__, hdr.message); + if (res) + free(res); + return -EPROTO; +} + +static int send_packet(int sfd, uint32_t message, uint32_t tag, void *payload, uint32_t payload_size) +{ + struct usbmuxd_header header; + + header.length = sizeof(struct usbmuxd_header); + header.version = proto_version; + header.message = message; + header.tag = tag; + if (payload && (payload_size > 0)) { + header.length += payload_size; + } + int sent = send_buf(sfd, &header, sizeof(header)); + if (sent != sizeof(header)) { + DEBUG(1, "%s: ERROR: could not send packet header\n", __func__); + return -1; + } + if (payload && (payload_size > 0)) { + sent += send_buf(sfd, payload, payload_size); + } + if (sent != (int)header.length) { + DEBUG(1, "%s: ERROR: could not send whole packet\n", __func__); + close_socket(sfd); + return -1; + } + return sent; +} + +static int send_listen_packet(int sfd, uint32_t tag) +{ + int res = 0; +#ifdef HAVE_PLIST + if (proto_version == 1) { + /* plist packet */ + char *payload = NULL; + uint32_t payload_size = 0; + plist_t plist; + + /* construct message plist */ + plist = plist_new_dict(); + plist_dict_insert_item(plist, "BundleID", plist_new_string(PLIST_BUNDLE_ID)); + plist_dict_insert_item(plist, "ClientVersionString", plist_new_string(PLIST_CLIENT_VERSION_STRING)); + plist_dict_insert_item(plist, "MessageType", plist_new_string("Listen")); + plist_dict_insert_item(plist, "ProgName", plist_new_string(PLIST_PROGNAME)); + plist_to_xml(plist, &payload, &payload_size); + plist_free(plist); + + res = send_packet(sfd, MESSAGE_PLIST, tag, payload, payload_size); + free(payload); + } else +#endif + { + /* binary packet */ + res = send_packet(sfd, MESSAGE_LISTEN, tag, NULL, 0); + } + return res; +} + +static int send_connect_packet(int sfd, uint32_t tag, uint32_t device_id, uint16_t port) +{ + int res = 0; +#ifdef HAVE_PLIST + if (proto_version == 1) { + /* plist packet */ + char *payload = NULL; + uint32_t payload_size = 0; + plist_t plist; + + /* construct message plist */ + plist = plist_new_dict(); + plist_dict_insert_item(plist, "BundleID", plist_new_string(PLIST_BUNDLE_ID)); + plist_dict_insert_item(plist, "ClientVersionString", plist_new_string(PLIST_CLIENT_VERSION_STRING)); + plist_dict_insert_item(plist, "MessageType", plist_new_string("Connect")); + plist_dict_insert_item(plist, "DeviceID", plist_new_uint(device_id)); + plist_dict_insert_item(plist, "PortNumber", plist_new_uint(htons(port))); + plist_dict_insert_item(plist, "ProgName", plist_new_string(PLIST_PROGNAME)); + plist_to_xml(plist, &payload, &payload_size); + plist_free(plist); + + res = send_packet(sfd, MESSAGE_PLIST, tag, (void*)payload, payload_size); + free(payload); + } else +#endif + { + /* binary packet */ + struct { + uint32_t device_id; + uint16_t port; + uint16_t reserved; + } conninfo; + + conninfo.device_id = device_id; + conninfo.port = htons(port); + conninfo.reserved = 0; + + res = send_packet(sfd, MESSAGE_CONNECT, tag, &conninfo, sizeof(conninfo)); + } + return res; +} + +/** + * Generates an event, i.e. calls the callback function. + * A reference to a populated usbmuxd_event_t with information about the event + * and the corresponding device will be passed to the callback function. + */ +static void generate_event(usbmuxd_event_cb_t callback, const usbmuxd_device_info_t *dev, enum usbmuxd_event_type event, void *user_data) +{ + usbmuxd_event_t ev; + + if (!callback || !dev) { + return; + } + + ev.event = event; + memcpy(&ev.device, dev, sizeof(usbmuxd_device_info_t)); + + callback(&ev, user_data); +} + +static int usbmuxd_listen_poll() +{ + int sfd; + + sfd = connect_usbmuxd_socket(); + if (sfd < 0) { + while (event_cb) { + if ((sfd = connect_usbmuxd_socket()) > 0) { + break; + } + sleep(1); + } + } + + return sfd; +} + +#ifdef HAVE_INOTIFY +static int use_inotify = 1; + +static int usbmuxd_listen_inotify() +{ + int inot_fd; + int watch_d; + int sfd; + + if (!use_inotify) { + return -2; + } + + sfd = connect_usbmuxd_socket(); + if (sfd >= 0) + return sfd; + + sfd = -1; + inot_fd = inotify_init (); + if (inot_fd < 0) { + DEBUG(1, "%s: Failed to setup inotify\n", __func__); + return -2; + } + + /* inotify is setup, listen for events that concern us */ + watch_d = inotify_add_watch (inot_fd, USBMUXD_DIRNAME, IN_CREATE); + if (watch_d < 0) { + DEBUG(1, "%s: Failed to setup watch descriptor for socket dir\n", __func__); + close (inot_fd); + return -2; + } + + while (1) { + ssize_t len, i; + char buff[EVENT_BUF_LEN] = {0}; + + i = 0; + len = read (inot_fd, buff, EVENT_BUF_LEN -1); + if (len < 0) + goto end; + while (i < len) { + struct inotify_event *pevent = (struct inotify_event *) & buff[i]; + + /* check that it's ours */ + if (pevent->mask & IN_CREATE && + pevent->len && + pevent->name != NULL && + strcmp(pevent->name, USBMUXD_SOCKET_NAME) == 0) { + sfd = connect_usbmuxd_socket (); + goto end; + } + i += EVENT_SIZE + pevent->len; + } + } + +end: + inotify_rm_watch(inot_fd, watch_d); + close(inot_fd); + + return sfd; +} +#endif /* HAVE_INOTIFY */ + +/** + * Tries to connect to usbmuxd and wait if it is not running. + */ +static int usbmuxd_listen() +{ + int sfd; + uint32_t res = -1; + +#ifdef HAVE_PLIST +retry: +#endif + +#ifdef HAVE_INOTIFY + sfd = usbmuxd_listen_inotify(); + if (sfd == -2) + sfd = usbmuxd_listen_poll(); +#else + sfd = usbmuxd_listen_poll(); +#endif + + if (sfd < 0) { + DEBUG(1, "%s: ERROR: usbmuxd was supposed to be running here...\n", __func__); + return sfd; + } + + use_tag++; + LOCK; + if (send_listen_packet(sfd, use_tag) <= 0) { + UNLOCK; + DEBUG(1, "%s: ERROR: could not send listen packet\n", __func__); + close_socket(sfd); + return -1; + } + if (usbmuxd_get_result(sfd, use_tag, &res) && (res != 0)) { + UNLOCK; + close_socket(sfd); +#ifdef HAVE_PLIST + if ((res == RESULT_BADVERSION) && (proto_version != 1)) { + proto_version = 1; + goto retry; + } +#endif + DEBUG(1, "%s: ERROR: did not get OK but %d\n", __func__, res); + return -1; + } + UNLOCK; + + return sfd; +} + +/** + * Waits for an event to occur, i.e. a packet coming from usbmuxd. + * Calls generate_event to pass the event via callback to the client program. + */ +static int get_next_event(int sfd, usbmuxd_event_cb_t callback, void *user_data) +{ + struct usbmuxd_header hdr; + void *payload = NULL; + + /* block until we receive something */ + if (receive_packet(sfd, &hdr, &payload, 0) < 0) { + // when then usbmuxd connection fails, + // generate remove events for every device that + // is still present so applications know about it + FOREACH(usbmuxd_device_info_t *dev, &devices) { + generate_event(callback, dev, UE_DEVICE_REMOVE, user_data); + collection_remove(&devices, dev); + free(dev); + } ENDFOREACH + return -EIO; + } + + if ((hdr.length > sizeof(hdr)) && !payload) { + DEBUG(1, "%s: Invalid packet received, payload is missing!\n", __func__); + return -EBADMSG; + } + + if (hdr.message == MESSAGE_DEVICE_ADD) { + struct usbmuxd_device_record *dev = payload; + usbmuxd_device_info_t *devinfo = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t)); + if (!devinfo) { + DEBUG(1, "%s: Out of memory!\n", __func__); + free(payload); + return -1; + } + + devinfo->handle = dev->device_id; + devinfo->product_id = dev->product_id; + memset(devinfo->udid, '\0', sizeof(devinfo->udid)); + memcpy(devinfo->udid, dev->serial_number, sizeof(devinfo->udid)); + + if (strcasecmp(devinfo->udid, "ffffffffffffffffffffffffffffffffffffffff") == 0) { + sprintf(devinfo->udid + 32, "%08x", devinfo->handle); + } + + collection_add(&devices, devinfo); + generate_event(callback, devinfo, UE_DEVICE_ADD, user_data); + } else if (hdr.message == MESSAGE_DEVICE_REMOVE) { + uint32_t handle; + usbmuxd_device_info_t *devinfo; + + memcpy(&handle, payload, sizeof(uint32_t)); + + devinfo = devices_find(handle); + if (!devinfo) { + DEBUG(1, "%s: WARNING: got device remove message for handle %d, but couldn't find the corresponding handle in the device list. This event will be ignored.\n", __func__, handle); + } else { + generate_event(callback, devinfo, UE_DEVICE_REMOVE, user_data); + collection_remove(&devices, devinfo); + free(devinfo); + } + } else if (hdr.length > 0) { + DEBUG(1, "%s: Unexpected message type %d length %d received!\n", __func__, hdr.message, hdr.length); + } + if (payload) { + free(payload); + } + return 0; +} + +static void device_monitor_cleanup(void* data) +{ + FOREACH(usbmuxd_device_info_t *dev, &devices) { + collection_remove(&devices, dev); + free(dev); + } ENDFOREACH + collection_free(&devices); + + close_socket(listenfd); + listenfd = -1; +} + +/** + * Device Monitor thread function. + * + * This function sets up a connection to usbmuxd + */ +static void *device_monitor(void *data) +{ + collection_init(&devices); + +#ifndef WIN32 + pthread_cleanup_push(device_monitor_cleanup, NULL); +#endif + while (event_cb) { + + listenfd = usbmuxd_listen(); + if (listenfd < 0) { + continue; + } + + while (event_cb) { + int res = get_next_event(listenfd, event_cb, data); + if (res < 0) { + break; + } + } + } + +#ifndef WIN32 + pthread_cleanup_pop(1); +#else + device_monitor_cleanup(NULL); +#endif + return NULL; +} + +int usbmuxd_subscribe(usbmuxd_event_cb_t callback, void *user_data) +{ + int res; + + if (!callback) { + return -EINVAL; + } + event_cb = callback; + +#ifdef WIN32 + res = 0; + devmon = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)device_monitor, user_data, 0, NULL); + if (devmon == NULL) { + res = GetLastError(); + } +#else + res = pthread_create(&devmon, NULL, device_monitor, user_data); +#endif + if (res != 0) { + DEBUG(1, "%s: ERROR: Could not start device watcher thread!\n", __func__); + return res; + } + return 0; +} + +int usbmuxd_unsubscribe() +{ + event_cb = NULL; + + shutdown_socket(listenfd, SHUT_RDWR); + +#ifdef WIN32 + if (devmon != NULL) { + WaitForSingleObject(devmon, INFINITE); + } +#else + if (pthread_kill(devmon, 0) == 0) { + pthread_cancel(devmon); + pthread_join(devmon, NULL); + } +#endif + + return 0; +} + +int usbmuxd_get_device_list(usbmuxd_device_info_t **device_list) +{ + int sfd; + int listen_success = 0; + uint32_t res; + struct collection tmpdevs; + usbmuxd_device_info_t *newlist = NULL; + struct usbmuxd_header hdr; + struct usbmuxd_device_record *dev; + int dev_cnt = 0; + void *payload = NULL; + + *device_list = NULL; + +#ifdef HAVE_PLIST +retry: +#endif + sfd = connect_usbmuxd_socket(); + if (sfd < 0) { + DEBUG(1, "%s: error opening socket!\n", __func__); + return sfd; + } + + use_tag++; + LOCK; + if (send_listen_packet(sfd, use_tag) > 0) { + res = -1; + // get response + if (usbmuxd_get_result(sfd, use_tag, &res) && (res == 0)) { + listen_success = 1; + } else { + UNLOCK; + close_socket(sfd); +#ifdef HAVE_PLIST + if ((res == RESULT_BADVERSION) && (proto_version != 1)) { + proto_version = 1; + goto retry; + } +#endif + DEBUG(1, "%s: Did not get response to scan request (with result=0)...\n", __func__); + return res; + } + } + + if (!listen_success) { + UNLOCK; + DEBUG(1, "%s: Could not send listen request!\n", __func__); + return -1; + } + + collection_init(&tmpdevs); + + // receive device list + while (1) { + if (receive_packet(sfd, &hdr, &payload, 1000) > 0) { + if (hdr.message == MESSAGE_DEVICE_ADD) { + dev = payload; + usbmuxd_device_info_t *devinfo = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t)); + if (!devinfo) { + UNLOCK; + DEBUG(1, "%s: Out of memory!\n", __func__); + free(payload); + return -1; + } + + devinfo->handle = dev->device_id; + devinfo->product_id = dev->product_id; + memset(devinfo->udid, '\0', sizeof(devinfo->udid)); + memcpy(devinfo->udid, dev->serial_number, sizeof(devinfo->udid)); + + if (strcasecmp(devinfo->udid, "ffffffffffffffffffffffffffffffffffffffff") == 0) { + sprintf(devinfo->udid + 32, "%08x", devinfo->handle); + } + + collection_add(&tmpdevs, devinfo); + + } else if (hdr.message == MESSAGE_DEVICE_REMOVE) { + uint32_t handle; + usbmuxd_device_info_t *devinfo = NULL; + + memcpy(&handle, payload, sizeof(uint32_t)); + + FOREACH(usbmuxd_device_info_t *di, &tmpdevs) { + if (di && di->handle == handle) { + devinfo = di; + break; + } + } ENDFOREACH + if (devinfo) { + collection_remove(&tmpdevs, devinfo); + free(devinfo); + } + } else { + DEBUG(1, "%s: Unexpected message %d\n", __func__, hdr.message); + } + if (payload) + free(payload); + } else { + // we _should_ have all of them now. + // or perhaps an error occured. + break; + } + } + UNLOCK; + + // explicitly close connection + close_socket(sfd); + + // create copy of device info entries from collection + newlist = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t) * (collection_count(&tmpdevs) + 1)); + dev_cnt = 0; + FOREACH(usbmuxd_device_info_t *di, &tmpdevs) { + if (di) { + memcpy(&newlist[dev_cnt], di, sizeof(usbmuxd_device_info_t)); + free(di); + dev_cnt++; + } + } ENDFOREACH + collection_free(&tmpdevs); + + memset(&newlist[dev_cnt], 0, sizeof(usbmuxd_device_info_t)); + *device_list = newlist; + + return dev_cnt; +} + +int usbmuxd_device_list_free(usbmuxd_device_info_t **device_list) +{ + if (device_list) { + free(*device_list); + } + return 0; +} + +int usbmuxd_get_device_by_udid(const char *udid, usbmuxd_device_info_t *device) +{ + usbmuxd_device_info_t *dev_list = NULL; + + if (!device) { + return -EINVAL; + } + if (usbmuxd_get_device_list(&dev_list) < 0) { + return -ENODEV; + } + + int i; + int result = 0; + for (i = 0; dev_list[i].handle > 0; i++) { + if (!udid) { + device->handle = dev_list[i].handle; + device->product_id = dev_list[i].product_id; + strcpy(device->udid, dev_list[i].udid); + result = 1; + break; + } + if (!strcmp(udid, dev_list[i].udid)) { + device->handle = dev_list[i].handle; + device->product_id = dev_list[i].product_id; + strcpy(device->udid, dev_list[i].udid); + result = 1; + break; + } + } + + free(dev_list); + + return result; +} + +int usbmuxd_connect(const int handle, const unsigned short port) +{ + int sfd; + int connected = 0; + uint32_t res = -1; + +#ifdef HAVE_PLIST +retry: +#endif + sfd = connect_usbmuxd_socket(); + if (sfd < 0) { + DEBUG(1, "%s: Error: Connection to usbmuxd failed: %s\n", + __func__, strerror(errno)); + return sfd; + } + + use_tag++; + if (send_connect_packet(sfd, use_tag, (uint32_t)handle, (uint16_t)port) <= 0) { + DEBUG(1, "%s: Error sending connect message!\n", __func__); + } else { + // read ACK + DEBUG(2, "%s: Reading connect result...\n", __func__); + if (usbmuxd_get_result(sfd, use_tag, &res)) { + if (res == 0) { + DEBUG(2, "%s: Connect success!\n", __func__); + connected = 1; + } else { +#ifdef HAVE_PLIST + if ((res == RESULT_BADVERSION) && (proto_version == 0)) { + proto_version = 1; + close_socket(sfd); + goto retry; + } +#endif + DEBUG(1, "%s: Connect failed, Error code=%d\n", __func__, res); + } + } + } + + if (connected) { + return sfd; + } + + close_socket(sfd); + + return -1; +} + +int usbmuxd_disconnect(int sfd) +{ + return close_socket(sfd); +} + +int usbmuxd_send(int sfd, const char *data, uint32_t len, uint32_t *sent_bytes) +{ + int num_sent; + + if (sfd < 0) { + return -EINVAL; + } + + num_sent = send(sfd, (void*)data, len, 0); + if (num_sent < 0) { + *sent_bytes = 0; + DEBUG(1, "%s: Error %d when sending: %s\n", __func__, num_sent, strerror(errno)); + return num_sent; + } else if ((uint32_t)num_sent < len) { + DEBUG(1, "%s: Warning: Did not send enough (only %d of %d)\n", __func__, num_sent, len); + } + + *sent_bytes = num_sent; + + return 0; +} + +int usbmuxd_recv_timeout(int sfd, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout) +{ + int num_recv = recv_buf_timeout(sfd, (void*)data, len, 0, timeout); + if (num_recv < 0) { + *recv_bytes = 0; + return num_recv; + } + + *recv_bytes = num_recv; + + return 0; +} + +int usbmuxd_recv(int sfd, char *data, uint32_t len, uint32_t *recv_bytes) +{ + return usbmuxd_recv_timeout(sfd, data, len, recv_bytes, 5000); +} + +void libusbmuxd_set_use_inotify(int set) +{ +#ifdef HAVE_INOTIFY + use_inotify = set; +#endif + return; +} + +void libusbmuxd_set_debug_level(int level) +{ + libusbmuxd_debug = level; + sock_stuff_set_verbose(level); +} diff --git a/src/sock_stuff.c b/src/sock_stuff.c new file mode 100644 index 0000000..609c8ad --- /dev/null +++ b/src/sock_stuff.c @@ -0,0 +1,375 @@ +/* + libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009 Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009 Martin Szulecki <opensuse@sukimashita.com> + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/stat.h> +#ifdef WIN32 +#include <windows.h> +#include <winsock2.h> +static int wsa_init = 0; +#else +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#endif +#include "sock_stuff.h" + +#define RECV_TIMEOUT 20000 + +static int verbose = 0; + +void sock_stuff_set_verbose(int level) +{ + verbose = level; +} + +#ifndef WIN32 +int create_unix_socket(const char *filename) +{ + struct sockaddr_un name; + int sock; + size_t size; + + // remove if still present + unlink(filename); + + /* Create the socket. */ + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return -1; + } + + /* Bind a name to the socket. */ + name.sun_family = AF_LOCAL; + strncpy(name.sun_path, filename, sizeof(name.sun_path)); + name.sun_path[sizeof(name.sun_path) - 1] = '\0'; + + /* The size of the address is + the offset of the start of the filename, + plus its length, + plus one for the terminating null byte. + Alternatively you can just do: + size = SUN_LEN (&name); + */ + size = (offsetof(struct sockaddr_un, sun_path) + + strlen(name.sun_path) + 1); + + if (bind(sock, (struct sockaddr *) &name, size) < 0) { + perror("bind"); + close_socket(sock); + return -1; + } + + if (listen(sock, 10) < 0) { + perror("listen"); + close_socket(sock); + return -1; + } + + return sock; +} + +int connect_unix_socket(const char *filename) +{ + struct sockaddr_un name; + int sfd = -1; + size_t size; + struct stat fst; + + // check if socket file exists... + if (stat(filename, &fst) != 0) { + if (verbose >= 2) + fprintf(stderr, "%s: stat '%s': %s\n", __func__, filename, + strerror(errno)); + return -1; + } + // ... and if it is a unix domain socket + if (!S_ISSOCK(fst.st_mode)) { + if (verbose >= 2) + fprintf(stderr, "%s: File '%s' is not a socket!\n", __func__, + filename); + return -1; + } + // make a new socket + if ((sfd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { + if (verbose >= 2) + fprintf(stderr, "%s: socket: %s\n", __func__, strerror(errno)); + return -1; + } + // and connect to 'filename' + name.sun_family = AF_LOCAL; + strncpy(name.sun_path, filename, sizeof(name.sun_path)); + name.sun_path[sizeof(name.sun_path) - 1] = 0; + + size = (offsetof(struct sockaddr_un, sun_path) + + strlen(name.sun_path) + 1); + + if (connect(sfd, (struct sockaddr *) &name, size) < 0) { + close_socket(sfd); + if (verbose >= 2) + fprintf(stderr, "%s: connect: %s\n", __func__, + strerror(errno)); + return -1; + } + + return sfd; +} +#endif + +int create_socket(uint16_t port) +{ + int sfd = -1; + int yes = 1; +#ifdef WIN32 + WSADATA wsa_data; + if (!wsa_init) { + if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) { + fprintf(stderr, "WSAStartup failed!\n"); + ExitProcess(-1); + } + wsa_init = 1; + } +#endif + struct sockaddr_in saddr; + + if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { + perror("socket()"); + return -1; + } + + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(int)) == -1) { + perror("setsockopt()"); + close_socket(sfd); + return -1; + } + + memset((void *) &saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons(port); + + if (0 > bind(sfd, (struct sockaddr *) &saddr, sizeof(saddr))) { + perror("bind()"); + close_socket(sfd); + return -1; + } + + if (listen(sfd, 1) == -1) { + perror("listen()"); + close_socket(sfd); + return -1; + } + + return sfd; +} + +#if defined(WIN32) || defined(__CYGWIN__) +int connect_socket(const char *addr, uint16_t port) +{ + int sfd = -1; + int yes = 1; + struct hostent *hp; + struct sockaddr_in saddr; +#ifdef WIN32 + WSADATA wsa_data; + if (!wsa_init) { + if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) { + fprintf(stderr, "WSAStartup failed!\n"); + ExitProcess(-1); + } + wsa_init = 1; + } +#endif + + if (!addr) { + errno = EINVAL; + return -1; + } + + if ((hp = gethostbyname(addr)) == NULL) { + if (verbose >= 2) + fprintf(stderr, "%s: unknown host '%s'\n", __func__, addr); + return -1; + } + + if (!hp->h_addr) { + if (verbose >= 2) + fprintf(stderr, "%s: gethostbyname returned NULL address!\n", + __func__); + return -1; + } + + if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { + perror("socket()"); + return -1; + } + + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(int)) == -1) { + perror("setsockopt()"); + close_socket(sfd); + return -1; + } + + memset((void *) &saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = *(uint32_t *) hp->h_addr; + saddr.sin_port = htons(port); + + if (connect(sfd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { + perror("connect"); + close_socket(sfd); + return -2; + } + + return sfd; +} +#endif /* WIN32 || __CYGWIN__ */ + +int check_fd(int fd, fd_mode fdm, unsigned int timeout) +{ + fd_set fds; + int sret; + int eagain; + struct timeval to; + struct timeval *pto; + + if (fd <= 0) { + if (verbose >= 2) + fprintf(stderr, "ERROR: invalid fd in check_fd %d\n", fd); + return -1; + } + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (timeout > 0) { + to.tv_sec = (time_t) (timeout / 1000); + to.tv_usec = (time_t) ((timeout - (to.tv_sec * 1000)) * 1000); + pto = &to; + } else { + pto = NULL; + } + + sret = -1; + + do { + eagain = 0; + switch (fdm) { + case FDM_READ: + sret = select(fd + 1, &fds, NULL, NULL, pto); + break; + case FDM_WRITE: + sret = select(fd + 1, NULL, &fds, NULL, pto); + break; + case FDM_EXCEPT: + sret = select(fd + 1, NULL, NULL, &fds, pto); + break; + default: + return -1; + } + + if (sret < 0) { + switch (errno) { + case EINTR: + // interrupt signal in select + if (verbose >= 2) + fprintf(stderr, "%s: EINTR\n", __func__); + eagain = 1; + break; + case EAGAIN: + if (verbose >= 2) + fprintf(stderr, "%s: EAGAIN\n", __func__); + break; + default: + if (verbose >= 2) + fprintf(stderr, "%s: select failed: %s\n", __func__, + strerror(errno)); + return -1; + } + } + } while (eagain); + + return sret; +} + +int shutdown_socket(int fd, int how) +{ + return shutdown(fd, how); +} + +int close_socket(int fd) { +#ifdef WIN32 + return closesocket(fd); +#else + return close(fd); +#endif +} + +int recv_buf(int fd, void *data, size_t length) +{ + return recv_buf_timeout(fd, data, length, 0, RECV_TIMEOUT); +} + +int peek_buf(int fd, void *data, size_t length) +{ + return recv_buf_timeout(fd, data, length, MSG_PEEK, RECV_TIMEOUT); +} + +int recv_buf_timeout(int fd, void *data, size_t length, int flags, + unsigned int timeout) +{ + int res; + int result; + + // check if data is available + res = check_fd(fd, FDM_READ, timeout); + if (res <= 0) { + return res; + } + // if we get here, there _is_ data available + result = recv(fd, data, length, flags); + if (res > 0 && result == 0) { + // but this is an error condition + if (verbose >= 3) + fprintf(stderr, "%s: fd=%d recv returned 0\n", __func__, fd); + return -EAGAIN; + } + if (result < 0) { + return -errno; + } + return result; +} + +int send_buf(int fd, void *data, size_t length) +{ + return send(fd, data, length, 0); +} diff --git a/src/sock_stuff.h b/src/sock_stuff.h new file mode 100644 index 0000000..5efcd27 --- /dev/null +++ b/src/sock_stuff.h @@ -0,0 +1,65 @@ +/* + libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009 Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009 Martin Szulecki <opensuse@sukimashita.com> + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef __SOCK_STUFF_H +#define __SOCK_STUFF_H + +#include <stdint.h> + +enum fd_mode { + FDM_READ, + FDM_WRITE, + FDM_EXCEPT +}; +typedef enum fd_mode fd_mode; + +#ifdef WIN32 +#include <winsock2.h> +#define SHUT_RD SD_READ +#define SHUT_WR SD_WRITE +#define SHUT_RDWR SD_BOTH +#endif + +#ifndef WIN32 +int create_unix_socket(const char *filename); +int connect_unix_socket(const char *filename); +#endif +int create_socket(uint16_t port); +#if defined(WIN32) || defined(__CYGWIN__) +int connect_socket(const char *addr, uint16_t port); +#endif +int check_fd(int fd, fd_mode fdm, unsigned int timeout); + +int shutdown_socket(int fd, int how); +int close_socket(int fd); + +int recv_buf(int fd, void *data, size_t size); +int peek_buf(int fd, void *data, size_t size); +int recv_buf_timeout(int fd, void *data, size_t size, int flags, + unsigned int timeout); + +int send_buf(int fd, void *data, size_t size); + +void sock_stuff_set_verbose(int level); + +#endif /* __SOCK_STUFF_H */ diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..f1fa2f6 --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,10 @@ +AM_CFLAGS = $(GLOBAL_CFLAGS) -I$(top_srcdir)/src +AM_LDFLAGS = + +bin_PROGRAMS = iproxy + +iproxy_SOURCES = iproxy.c +iproxy_CFLAGS = $(AM_CFLAGS) +iproxy_LDFLAGS = $(AM_LDFLAGS) +iproxy_LDADD = ../src/libusbmuxd.la + diff --git a/tools/iproxy.c b/tools/iproxy.c new file mode 100644 index 0000000..f7c0827 --- /dev/null +++ b/tools/iproxy.c @@ -0,0 +1,281 @@ +/* + iproxy -- proxy that enables tcp service access to iPhone/iPod + +Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009 Paul Sladen <libiphone@paul.sladen.org> + +Based upon iTunnel source code, Copyright (c) 2008 Jing Su. +http://www.cs.toronto.edu/~jingsu/itunnel/ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +TODO: improve code... + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#ifdef WIN32 +#include <windows.h> +#include <winsock2.h> +typedef unsigned int socklen_t; +#else +#include <sys/socket.h> +#include <sys/un.h> +#include <arpa/inet.h> +#include <pthread.h> +#include <netinet/in.h> +#endif +#include "sock_stuff.h" +#include "usbmuxd.h" + +static uint16_t listen_port = 0; +static uint16_t device_port = 0; + +struct client_data { + int fd; + int sfd; + volatile int stop_ctos; + volatile int stop_stoc; +}; + +static void *run_stoc_loop(void *arg) +{ + struct client_data *cdata = (struct client_data*)arg; + int recv_len; + int sent; + char buffer[131072]; + + printf("%s: fd = %d\n", __func__, cdata->fd); + + while (!cdata->stop_stoc && cdata->fd>0 && cdata->sfd>0) { + recv_len = recv_buf_timeout(cdata->sfd, buffer, sizeof(buffer), 0, 5000); + if (recv_len <= 0) { + if (recv_len == 0) { + // try again + continue; + } else { + fprintf(stderr, "recv failed: %s\n", strerror(errno)); + break; + } + } else { +// printf("received %d bytes from server\n", recv_len); + // send to socket + sent = send_buf(cdata->fd, buffer, recv_len); + if (sent < recv_len) { + if (sent <= 0) { + fprintf(stderr, "send failed: %s\n", strerror(errno)); + break; + } else { + fprintf(stderr, "only sent %d from %d bytes\n", sent, recv_len); + } + } else { + // sending succeeded, receive from device +// printf("pushed %d bytes to client\n", sent); + } + } + } + close(cdata->fd); + cdata->fd = -1; + cdata->stop_ctos = 1; + + return NULL; +} + +static void *run_ctos_loop(void *arg) +{ + struct client_data *cdata = (struct client_data*)arg; + int recv_len; + int sent; + char buffer[131072]; +#ifdef WIN32 + HANDLE stoc = NULL; +#else + pthread_t stoc; +#endif + + printf("%s: fd = %d\n", __func__, cdata->fd); + + cdata->stop_stoc = 0; +#ifdef WIN32 + stoc = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)run_stoc_loop, cdata, 0, NULL); +#else + pthread_create(&stoc, NULL, run_stoc_loop, cdata); +#endif + + while (!cdata->stop_ctos && cdata->fd>0 && cdata->sfd>0) { + recv_len = recv_buf_timeout(cdata->fd, buffer, sizeof(buffer), 0, 5000); + if (recv_len <= 0) { + if (recv_len == 0) { + // try again + continue; + } else { + fprintf(stderr, "recv failed: %s\n", strerror(errno)); + break; + } + } else { +// printf("pulled %d bytes from client\n", recv_len); + // send to local socket + sent = send_buf(cdata->sfd, buffer, recv_len); + if (sent < recv_len) { + if (sent <= 0) { + fprintf(stderr, "send failed: %s\n", strerror(errno)); + break; + } else { + fprintf(stderr, "only sent %d from %d bytes\n", sent, recv_len); + } + } else { + // sending succeeded, receive from device +// printf("sent %d bytes to server\n", sent); + } + } + } + close(cdata->fd); + cdata->fd = -1; + cdata->stop_stoc = 1; + +#ifdef WIN32 + WaitForSingleObject(stoc, INFINITE); +#else + pthread_join(stoc, NULL); +#endif + + return NULL; +} + +static void *acceptor_thread(void *arg) +{ + struct client_data *cdata; + usbmuxd_device_info_t *dev_list = NULL; +#ifdef WIN32 + HANDLE ctos = NULL; +#else + pthread_t ctos; +#endif + int count; + + if (!arg) { + fprintf(stderr, "invalid client_data provided!\n"); + return NULL; + } + + cdata = (struct client_data*)arg; + + if ((count = usbmuxd_get_device_list(&dev_list)) < 0) { + printf("Connecting to usbmuxd failed, terminating.\n"); + free(dev_list); + return NULL; + } + + fprintf(stdout, "Number of available devices == %d\n", count); + + if (dev_list == NULL || dev_list[0].handle == 0) { + printf("No connected device found, terminating.\n"); + free(dev_list); + return NULL; + } + + fprintf(stdout, "Requesting connecion to device handle == %d (serial: %s), port %d\n", dev_list[0].handle, dev_list[0].udid, device_port); + + cdata->sfd = usbmuxd_connect(dev_list[0].handle, device_port); + free(dev_list); + if (cdata->sfd < 0) { + fprintf(stderr, "Error connecting to device!\n"); + } else { + cdata->stop_ctos = 0; +#ifdef WIN32 + ctos = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)run_ctos_loop, cdata, 0, NULL); + WaitForSingleObject(ctos, INFINITE); +#else + pthread_create(&ctos, NULL, run_ctos_loop, cdata); + pthread_join(ctos, NULL); +#endif + } + + if (cdata->fd > 0) { + close(cdata->fd); + } + if (cdata->sfd > 0) { + close(cdata->sfd); + } + + return NULL; +} + +int main(int argc, char **argv) +{ + int mysock = -1; + + if (argc != 3) { + printf("usage: %s LOCAL_TCP_PORT DEVICE_TCP_PORT\n", argv[0]); + return 0; + } + + listen_port = atoi(argv[1]); + device_port = atoi(argv[2]); + + if (!listen_port) { + fprintf(stderr, "Invalid listen_port specified!\n"); + return -EINVAL; + } + + if (!device_port) { + fprintf(stderr, "Invalid device_port specified!\n"); + return -EINVAL; + } + + // first create the listening socket endpoint waiting for connections. + mysock = create_socket(listen_port); + if (mysock < 0) { + fprintf(stderr, "Error creating socket: %s\n", strerror(errno)); + return -errno; + } else { +#ifdef WIN32 + HANDLE acceptor = NULL; +#else + pthread_t acceptor; +#endif + struct sockaddr_in c_addr; + socklen_t len = sizeof(struct sockaddr_in); + struct client_data cdata; + int c_sock; + while (1) { + printf("waiting for connection\n"); + c_sock = accept(mysock, (struct sockaddr*)&c_addr, &len); + if (c_sock) { + printf("accepted connection, fd = %d\n", c_sock); + cdata.fd = c_sock; +#ifdef WIN32 + acceptor = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)acceptor_thread, &cdata, 0, NULL); + WaitForSingleObject(acceptor, INFINITE); +#else + pthread_create(&acceptor, NULL, acceptor_thread, &cdata); + pthread_join(acceptor, NULL); +#endif + } else { + break; + } + } + close(c_sock); + close(mysock); + } + + return 0; +} |