commit f557f8ea77fe88259fc374e1318e74200e51c21c Author: Justin Maggard Date: Thu Oct 23 17:30:45 2008 +0000 Initial checkin diff --git a/Changelog.txt b/Changelog.txt new file mode 100644 index 0000000..42af405 --- /dev/null +++ b/Changelog.txt @@ -0,0 +1,433 @@ +$Id$ + +2008/07/10: + Fixed compilation without ENABLE_L3F_SERVICE + +2008/04/27: + correct UNSUBSCRIBE processing + +2008/04/25(bis): + changed iptables_removeall.sh and iptables_init.sh in order + to remove IP from the rules + +VERSION 1.1 : +2008/04/25: + Eventing is allmost completly implemented + +2008/04/24: + Correct event handling ? + +2008/04/08: + enabling tag in PF rules. quick can be set off. + +2008/03/13: + implementing event notify + +2008/03/11: + fixing a command line parsing error + +2008/03/09: + optimisations in upnpsoap.c + +2008/03/08: + optimizing upnpsoap.c for size + +2008/03/06: + Worked on the Eventing : generating XML event notifications + Send initial notification after subscribe + Improved pretty print of testupnpdescgen + Reduced Memory usage of upnpdescgen + fixed a small bug in the description + +2008/03/03: + Fixed miniupnpd.c for compiling without natpmp support + fixed presentationURL not there with L3F + fixing lease file creation/modification + +2008/02/25: + Rewrite of Send501() and Send404() + More work on events + genconfig.sh autodetects pf/ipf + +2008/02/24: + Started to implement UPnP Events. do NOT use it at the moment ! + +2008/02/21: + Added support for the Layer3Forwarding Service + added init_redirect() and shutdown_redirect() functions + +2008/02/20: + Removed Ext: HTTP header when useless + enabled the dummy service by default to please windows XP ! + +2008/02/07: + upnp_enable patch by Nikos Mavrogiannopoulos. + lease_file patch by Nikos Mavrogiannopoulos. + +2008/01/29: + some changes to Makefile.openwrt + use daemon() - daemonize() is still available for systems lacking daemon() + +VERSION 1.0 : +2008/01/27: + moved lan_addr to upnpglobalvars.h/.c + Adding experimental multiple external IP support. + +2008/01/22: + removed dummy service from description to improve compatibility + with emule client + Add "secure mode". put runtime flags in the same variable + +2008/01/14: + Fixed a bug in options.c for the parsing of empty lines. + +2008/01/03: + Fixed CleanExpiredNATPMP() + +2008/01/02: + Adding a queue parameter for setting ALTQ in pf + +2007/12/27: + improving some stuff with the PF_ENABLE_FILTER_RULE. + +2007/12/22: + Adding a runtime option to enable/disable NAT-PMP + +2007/12/20: + Added a cache in linux getifstats(). Please enable by editing config.h + +2007/12/14: + Updating an existing NAT-PMP mapping now works + +2007/12/13: + NAT-PMP code now remove expired mappings + TCP/UDP where swapped in NAT-PMP code + +2007/12/04: + Adding details to the error message for sendto(udp_notify) + +2007/11/27: + pf code doesn't generate filter rules by default anymore. The + #ifdef PF_ENABLE_FILTER_RULES must be uncommented in config.h. + +2007/11/02: + moved some of the prototypes common to all firewalls to commonrdr.h + Added functionalities to NAT-PMP + +2007/11/01: + Debugged NAT-PMP code + +2007/10/28: + Cleaning and improving NAT-PMP code + +2007/10/25: + improved the NAT-PMP experimental support + updated README and INSTALL files + +2007/10/24: + Adding support for NAT-PMP (from apple !) + +2007/10/11: + Checking the commandline for errors. + +2007/10/08: + Improved the BSD/Solaris Makefile + Merging last code from Darren Reed. Solaris/IPF should work now ! + added a man page. + +2007/10/07: + Adding Darren Reed code for ipf. + +2007/10/06: + Adding SunOS support thanks to Darren Reed. + Reorganizing os/firewall dependent code thanks to Darren Reed. + +2007/09/27: + linux make install support PREFIX variable + +2007/09/25: + reorganizing LAN sockets/address to improve multi LAN support. + SSDP announces are sent to all configured networks. + SSDP responses are "customized" by subnetwork. + +2007/09/24: + prototype code to remove unused rules + miniupnpdctl now display current rules + synchronised add_filter_rule2() prototype between pf and netfilter code. + +2007/09/19: + Correctly filling the Cache-control header in SSDP packets + +2007/08/28: + update PFRULE_INOUT_COUNTS detection for FreeBSD + +2007/08/27: + update version in genconfig.sh + do not error when a duplicate redirection is requested. + +2007/07/16: + really fixed the compilation bug with linux>=2.6.22 + +2007/07/04: + fixed an error in options.c that prevented to use packet_log option + +2007/07/03: + improved genconfig.sh + fixed a compilation bug with linux>=2.6.22 + +2007/06/22: + added PFRULE_INOUT_COUNTS macro to enable separate in/out packet and + bytes counts in pf for OpenBSD >= 3.8 + +2007/06/15: + removed a possible racecondition in writepidfile() + +2007/06/12: + improved genconfig.sh : no more "echo -e", use lsb_release when available + +2007/06/11: + get_redirect_rule*() functions now return some statistics about + rule usage (bytes and packets) + +2007/06/07: + Fixed the get_redirect_desc() in the linux/netfilter code + +2007/06/05: + Clean up init code in miniupnpd.c + Added a syslog message in SoapError() + +2007/06/04: + Now store redirection descriptions in the linux/netfilter code + +2007/05/21: + Answers to SSDP M-SEARCH requests with ST: ssdp:all + added make install to Makefile.linux + +2007/05/10: + Fixed a bug int the DeletePortMapping linux/netfilter implementation + It was allways the 1st rule that was deleted. + +2007/04/26: + Fixed config.h.openwrt + +2007/04/16: + added something in the INSTALL file about the FreeBSD send(udp_notify) + problem fix (allowing 239.0.0.0/8 explicitely in pf.conf) + +2007/03/30: + added setsockopt(s, SOL_SOCKET, SO_BROADCAST ...) for broadcasting + socket + +2007/03/17: + Fixed filter rule under linux : it was using wrong port ! + thanks to Wesley W. Terpstra + +2007/03/01: + Moved some of the SSDP code from miniupnpd.c to minissdp.c + +2007/02/28: + creating miniupnpdctl + +2007/02/26: + use LOG_MINIUPNPD macro for openlog() + simplify miniupndShutdown() + +2007/02/09: + improved genconfig.h + Added stuff to change the pf rule "rdr" to "rdr pass" + +2007/02/07: + Corrected Bytes per seconds to bits per second. + Ryan cleaned up comments and typos. + Ryan cleaned up daemonize stuff. + Ryan added possibility to configure model number and serial number + +2007/01/30: + ryan improved the robustness of most UPnP Soap methods + I added a target in the Makefiles to properly generate an uuid using + command line tools. + Improved configuration file parsing. + +2007/01/29: + Adding uuid option in miniupnpd.conf + +2007/01/27: + Added upnppermissions stuff : adding some security to UPnP ! + fixed XML description thanks to Ryan Wagoner + improved QueryStateVariable thanks to Ryan Wagoner + +2007/01/22: + use getifaddr() for each GetExtenalIPAddress() Call. + We can change the ip during execution without pb + +2007/01/17: + Lots of code cleanup + +2007/01/12: + Fixed a nasty bug in the linux/netfilter version of get_filter_rule() + +2007/01/11: + Improved the handling of the miniupnpd.conf file. + added -f option to choose which config file to read. + +2007/01/10: + Fixed potential bugs with ClearNameValueList() + +2007/01/08: + All by Ryan Wagoner : + - coding style and comments cleanup + - using now option file miniupnpd.conf + +2007/01/03: + changed "xx active incoming HTTP connections" msg + +2007/01/02: + Patch from Ryan Wagoner : + - no need to open sockets if we can't set the error handlers + - format the usage so it fits nicely on a standard size terminal + - fix up log_err message so they have the same format and you know what + they are related to + - use same "white space" style throughout + - on shutdown no need to continue if opening socket or setsockopt fails + +2006/12/14: + reduce amount of log lines (keeping the same information) + +2006/12/07: + Fixed Makefiles + fixed typos in logs + version 1.0-RC1 released + +2006/12/02: + moved strings from upnpdescgen.c to upnpdescstrings.h for + easier modification + Server: HTTP header now comes from a #define + added a compilation-time generated config.h + +2006/11/30: + minixml updated. should have no impact + Added support for presentationURL with -w switch + implemented getifstats() for linux. Added testgetifstats program + improved error handling in getifstats() BSD + +2006/11/26: + no need to have miniupnpc sources to compile miniupnpd. + Makefile.openwrt updated + Closing sockets on exit thanks to Ryan Wagoner + +2006/11/23: + now handling signal SIGINT + setting HTTP socket with REUSEADDR thanks to Ryan Wagoner + daemon now tested on a Linksys WRT54G device running OpenWRT ! + +2006/11/21: + disabling rtableid in pf code. + +2006/11/22: + Also responds on M-SEARCH with the uuid + +2006/11/20: + gaining some space in upnpsoap.c + +2006/11/19: + Cleaning up code to comply with ANSI C89 + +2006/11/17: + Linux version now deleting both nat and accept rules + implemented -U option under Linux + +2006/11/16: + implemented delete_redirect_rule() for linux + returning error 714 in DeletePortMapping() when needed + +2006/11/12: + The linux/netfilter version should now WORK ! + fix in the writepidfile() function. open with a mode ! + +2006/11/10: + fixing the XML description generation for big endian machines + working on the linux/netfilter port + +2006/11/09: + improved a lot the handling of HTTP error cases + +2006/11/08: + Tried to make the Makefile compatible with both BSDmake + and GNUmake. It was hard because of $^ and $< + +2006/11/07: + Makefile compatible with BSD make + make install target. + getifstats.c compatible with both OpenBSD and FreeBSD. + +2006/11/06: + added getifstats.c for openBSD. May not work under FreeBSD ? + now reports bytes/packets sent/received + reporting bitrates + possibility to report system uptime + +2006/10/29: + added a -L option to enable loggin (is off by default now). + +2006/10/28: + Patch by Ryan Wagoner to correct the XML description (was NewUpTime + instead of NewUptime) and implement uptime. + Trying to fix the memory leak. Added some comments + added a -d option for debugging purpose + Tnaks to valgrind (under linux!) I removed a small memory access error. + +2006/10/27: + Thanks to a patch sent by Michael van Tellingen, miniupnpd is + now ignoring NOTIFY packets sent by other devices and is + writing is own pid to /var/run/miniupnpd.pid + +2006/10/23: + Allways set sendEvents="no" in XML description (was causing + pb with winXP as SUBSCRIBE is not implemented) + +2006/10/22: + added translation from hostname to IP in the AddPortMapping() method + Thanks to Ryan Wagoner. + +2006/10/18: + Added an INSTALL file + +2006/10/13: + Added the possibility to change the notify interval + +2006/09/29: + Improved compliance of the XML Descriptions + pretty print for testupnpdescgen + +2006/09/25: + improved the Error 404 response. + Better serviceType and serviceId for dummy service... + +2006/09/24: + updating the XML description generator + +2006/09/18: + Thanks to Rick Richard, support for SSDP "alive" and "byebye" notifications + was added. The -u options was also added. The SSDP response are now + improved. + The -o option is now working (to force a specific external IP address). + The Soap Methods errors are correctly responded (401 Invalid Action) + +2006/09/09: + Added code to handle filter rules. Thanks to Seth Mos (pfsense.com) + storing the descriptions in the label of the rule + +2006/09/02: + improved the generation of the XML descriptions. + I still need to add allowed values to variables. + +2006/07/29: + filtering SSDP requests and responding with same ST: field + +2006/07/25: + Added a dummy description for the WANDevice + +2006/07/20: + Command line arguments processing + Added possibility to listen internally on several interfaces + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..fda0f97 --- /dev/null +++ b/INSTALL @@ -0,0 +1,14 @@ +MiniDLNA project. +(c) 2008 Justin Maggard +Parts (c) 2006-2008 Thomas Bernard +Homepage : http://sourceforge.net/projects/minidlna/ + +Prerequisites : + +- taglib +- libexif +- sqlite3 + +To Build and install : + +- Just run make, and hope it works. :) diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..c4b67fa --- /dev/null +++ b/LICENCE @@ -0,0 +1,346 @@ +MiniDLNA is distributed under version 2 of the General Public License (included +in its entirety, below). Version 2 is the only version of this license which +this version of BusyBox (or modified versions derived from this one) may be +distributed under. + +------------------------------------------------------------------------ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/LICENCE.miniupnpd b/LICENCE.miniupnpd new file mode 100644 index 0000000..be27ea0 --- /dev/null +++ b/LICENCE.miniupnpd @@ -0,0 +1,26 @@ +Copyright (c) 2006-2007, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7d1c62e --- /dev/null +++ b/Makefile @@ -0,0 +1,101 @@ +# $Id$ +# MiniUPnP project +# http://miniupnp.free.fr/ +# Author : Thomas Bernard +# for use with GNU Make +# To install use : +# $ PREFIX=/dummyinstalldir make -f Makefile.linux install +# or : +# $ INSTALLPREFIX=/usr/local make -f Makefile.linux install +# or : +# $ make -f Makefile.linux install +# +#CFLAGS = -Wall -O -D_GNU_SOURCE -g -DDEBUG +#CFLAGS = -Wall -g -Os -D_GNU_SOURCE +CFLAGS = -Wall -g -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 +CC = gcc +RM = rm -f +INSTALL = install + +INSTALLPREFIX ?= $(PREFIX)/usr +SBININSTALLDIR = $(INSTALLPREFIX)/sbin +ETCINSTALLDIR = $(PREFIX)/etc/miniupnpd + +BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \ + upnpreplyparse.o minixml.o \ + getifaddr.o daemonize.o upnpglobalvars.o \ + options.o minissdp.o upnpevents.o \ + sql.o metadata.o scanner.o + +ALLOBJS = $(BASEOBJS) $(LNXOBJS) + +#LIBS = -liptc +LIBS = -lexif -ltag_c -lsqlite3 #-lgd + +TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o + +EXECUTABLES = minidlna testupnpdescgen + +.PHONY: all clean install depend genuuid + +all: $(EXECUTABLES) + +clean: + $(RM) $(ALLOBJS) + $(RM) $(EXECUTABLES) + $(RM) testupnpdescgen.o + +install: minidlna genuuid + $(INSTALL) -d $(SBININSTALLDIR) + $(INSTALL) minidlna $(SBININSTALLDIR) + $(INSTALL) -d $(ETCINSTALLDIR) + $(INSTALL) netfilter/iptables_init.sh $(ETCINSTALLDIR) + $(INSTALL) netfilter/iptables_removeall.sh $(ETCINSTALLDIR) + $(INSTALL) --mode=0644 minidlna.conf $(ETCINSTALLDIR) + $(INSTALL) -d $(PREFIX)/etc/init.d + $(INSTALL) linux/miniupnpd.init.d.script $(PREFIX)/etc/init.d/miniupnpd + +# genuuid is using the uuidgen CLI tool which is part of libuuid +# from the e2fsprogs +genuuid: + sed -i -e "s/^uuid=[-0-9a-f]*/uuid=`(genuuid||uuidgen) 2>/dev/null`/" minidlna.conf + +minidlna: $(BASEOBJS) $(LNXOBJS) $(LIBS) + +testupnpdescgen: $(TESTUPNPDESCGENOBJS) + +config.h: genconfig.sh + ./genconfig.sh + +depend: config.h + makedepend -f$(MAKEFILE_LIST) -Y \ + $(ALLOBJS:.o=.c) $(TESTUPNPDESCGENOBJS:.o=.c) 2>/dev/null + +# DO NOT DELETE + +minidlna.o: config.h upnpglobalvars.h miniupnpdtypes.h +minidlna.o: upnphttp.h upnpdescgen.h miniupnpdpath.h getifaddr.h upnpsoap.h +minidlna.o: options.h minissdp.h daemonize.h upnpevents.h +minidlna.o: commonrdr.h +upnphttp.o: config.h upnphttp.h upnpdescgen.h miniupnpdpath.h upnpsoap.h +upnphttp.o: upnpevents.h +upnpdescgen.o: config.h upnpdescgen.h miniupnpdpath.h upnpglobalvars.h +upnpdescgen.o: miniupnpdtypes.h upnpdescstrings.h +upnpsoap.o: config.h upnpglobalvars.h miniupnpdtypes.h +upnpsoap.o: upnphttp.h upnpsoap.h upnpreplyparse.h getifaddr.h +upnpreplyparse.o: upnpreplyparse.h minixml.h +minixml.o: minixml.h +getifaddr.o: getifaddr.h +daemonize.o: daemonize.h config.h +upnpglobalvars.o: config.h upnpglobalvars.h +upnpglobalvars.o: miniupnpdtypes.h +options.o: options.h config.h upnpglobalvars.h +options.o: miniupnpdtypes.h +minissdp.o: config.h upnpdescstrings.h miniupnpdpath.h upnphttp.h +minissdp.o: upnpglobalvars.h miniupnpdtypes.h minissdp.h +upnpevents.o: config.h upnpevents.h miniupnpdpath.h upnpglobalvars.h +upnpevents.o: miniupnpdtypes.h upnpdescgen.h +netfilter/iptcrdr.o: netfilter/iptcrdr.h commonrdr.h config.h +testupnpdescgen.o: config.h upnpdescgen.h +upnpdescgen.o: config.h upnpdescgen.h miniupnpdpath.h upnpglobalvars.h +upnpdescgen.o: miniupnpdtypes.h upnpdescstrings.h diff --git a/README b/README new file mode 100644 index 0000000..8111274 --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +MiniDLNA project +(c) 2008 Justin Maggard +Parts (c) 2006-2007 Thomas Bernard +webpage: http://sourceforge.net/projects/minidlna/ + +This directory contain the MiniDLNA daemon software. +This software is subject to the conditions detailed in +the LICENCE file provided with this distribution. + +Parts of the software including the discovery code are +licensed under the BSD revised license which is detailed +in the LICENSE.miniupnpd file provided with the distribution. +More information on MiniUPnPd can be found at http://miniupnp.free.fr. + + +The MiniDLNA daemon is an UPnP-A/V and DLNA service which +serves multimedia content to compatible clients on the network. +See http://www.upnp.org/ for more details on UPnP +and http://www.dlna.org/ for mode details on DLNA. + +See the INSTALL file for instructions on compiling, installing, +and configuring minidlna. + + +Justin Maggard diff --git a/commonrdr.h b/commonrdr.h new file mode 100644 index 0000000..7efc3de --- /dev/null +++ b/commonrdr.h @@ -0,0 +1,35 @@ +/* MiniUPnP project + * (c) 2006-2007 Thomas Bernard + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#ifndef __COMMONRDR_H__ +#define __COMMONRDR_H__ + +#include "config.h" + +/* init and shutdown functions */ +int +init_redirect(void); + +void +shutdown_redirect(void); + +/* get_redirect_rule() gets internal IP and port from + * interface, external port and protocl + */ +int +get_redirect_rule(const char * ifname, unsigned short eport, int proto, + char * iaddr, int iaddrlen, unsigned short * iport, + char * desc, int desclen, + u_int64_t * packets, u_int64_t * bytes); + +int +get_redirect_rule_by_index(int index, + char * ifname, unsigned short * eport, + char * iaddr, int iaddrlen, unsigned short * iport, + int * proto, char * desc, int desclen, + u_int64_t * packets, u_int64_t * bytes); + +#endif + diff --git a/config.h b/config.h new file mode 100644 index 0000000..530d926 --- /dev/null +++ b/config.h @@ -0,0 +1,55 @@ +/* MiniUPnP Project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * generated by ./genconfig.sh on Thu Sep 11 15:05:26 PDT 2008 */ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#define UPNP_VERSION "20070827" +#define USE_NETFILTER 1 +#define OS_NAME "JM" +#define OS_VERSION "Linux/2.6.25.14-108.fc9.i686" +#define OS_URL "http://www.kernel.org/" + +/* syslog facility to be used by miniupnpd */ +#define LOG_MINIUPNPD LOG_DAEMON + +/* Uncomment the following line to allow miniupnpd to be + * controlled by miniupnpdctl */ +/*#define USE_MINIUPNPDCTL*/ + +/* Comment the following line to disable NAT-PMP operations */ +//#define ENABLE_NATPMP + +/* Uncomment the following line to enable generation of + * filter rules with pf */ +/*#define PF_ENABLE_FILTER_RULES*/ + +/* Uncomment the following line to enable caching of results of + * the getifstats() function */ +/*#define ENABLE_GETIFSTATS_CACHING*/ +/* The cache duration is indicated in seconds */ +#define GETIFSTATS_CACHING_DURATION 2 + +/* Uncomment the following line to enable multiple external ip support */ +/* note : Thas is EXPERIMENTAL, do not use that unless you know perfectly what you are doing */ +/*#define MULTIPLE_EXTERNAL_IP*/ + +/* Comment the following line to use home made daemonize() func instead + * of BSD daemon() */ +#define USE_DAEMON + +/* Uncomment the following line to enable lease file support */ +/*#define ENABLE_LEASEFILE*/ + +/* Define one or none of the two following macros in order to make some + * clients happy. It will change the XML Root Description of the IGD. + * Enabling the Layer3Forwarding Service seems to be the more compatible + * option. */ +/*#define HAS_DUMMY_SERVICE*/ +#define ENABLE_L3F_SERVICE + +/* Experimental UPnP Events support. */ +#define ENABLE_EVENTS + +#endif diff --git a/daemonize.c b/daemonize.c new file mode 100644 index 0000000..4bfbc00 --- /dev/null +++ b/daemonize.c @@ -0,0 +1,129 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daemonize.h" +#include "config.h" + +#ifndef USE_DAEMON + +int +daemonize(void) +{ + int pid, i; + + switch(fork()) + { + /* fork error */ + case -1: + perror("fork()"); + exit(1); + + /* child process */ + case 0: + /* obtain a new process group */ + if( (pid = setsid()) < 0) + { + perror("setsid()"); + exit(1); + } + + /* close all descriptors */ + for (i=getdtablesize();i>=0;--i) close(i); + + i = open("/dev/null",O_RDWR); /* open stdin */ + dup(i); /* stdout */ + dup(i); /* stderr */ + + umask(027); + chdir("/"); /* chdir to /tmp ? */ + + return pid; + + /* parent process */ + default: + exit(0); + } +} +#endif + +int +writepidfile(const char * fname, int pid) +{ + char pidstring[16]; + int pidstringlen; + int pidfile; + + if(!fname || (strlen(fname) == 0)) + return -1; + + if( (pidfile = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) + { + syslog(LOG_ERR, "Unable to open pidfile for writing %s: %m", fname); + return -1; + } + + pidstringlen = snprintf(pidstring, sizeof(pidstring), "%d\n", pid); + if(pidstringlen <= 0) + { + syslog(LOG_ERR, + "Unable to write to pidfile %s: snprintf(): FAILED", fname); + close(pidfile); + return -1; + } + else + { + if(write(pidfile, pidstring, pidstringlen) < 0) + syslog(LOG_ERR, "Unable to write to pidfile %s: %m", fname); + } + + close(pidfile); + + return 0; +} + +int +checkforrunning(const char * fname) +{ + char buffer[64]; + int pidfile; + pid_t pid; + + if(!fname || (strlen(fname) == 0)) + return -1; + + if( (pidfile = open(fname, O_RDONLY)) < 0) + return 0; + + memset(buffer, 0, 64); + + if(read(pidfile, buffer, 63)) + { + if( (pid = atol(buffer)) > 0) + { + if(!kill(pid, 0)) + { + close(pidfile); + return -2; + } + } + } + + close(pidfile); + + return 0; +} + diff --git a/daemonize.h b/daemonize.h new file mode 100644 index 0000000..4939afe --- /dev/null +++ b/daemonize.h @@ -0,0 +1,34 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __DAEMONIZE_H__ +#define __DAEMONIZE_H__ + +#include "config.h" + +#ifndef USE_DAEMON +/* daemonize() + * "fork" to background, detach from terminal, etc... + * returns: pid of the daemon, exits upon failure */ +int +daemonize(void); +#endif + +/* writepidfile() + * write the pid to a file */ +int +writepidfile(const char * fname, int pid); + +/* checkforrunning() + * check for another instance running + * returns: 0 only instance + * -1 invalid filename + * -2 another instance running */ +int +checkforrunning(const char * fname); + +#endif + diff --git a/genconfig.sh b/genconfig.sh new file mode 100755 index 0000000..3d59456 --- /dev/null +++ b/genconfig.sh @@ -0,0 +1,179 @@ +#! /bin/sh +# $Id$ +# miniupnp daemon +# http://miniupnp.free.fr or http://miniupnp.tuxfamily.org/ +# (c) 2006-2007 Thomas Bernard +# This software is subject to the conditions detailed in the +# LICENCE file provided within the distribution + +RM="rm -f" +CONFIGFILE="config.h" +CONFIGMACRO="__CONFIG_H__" + +# version reported in XML descriptions +UPNP_VERSION=20070827 +# Facility to syslog +LOG_MINIUPNPD="LOG_DAEMON" + +# detecting the OS name and version +OS_NAME=`uname -s` +OS_VERSION=`uname -r` + +# pfSense special case +if [ -f /etc/platform ]; then + if [ `cat /etc/platform` = "pfSense" ]; then + OS_NAME=pfSense + OS_VERSION=`cat /etc/version` + fi +fi + +${RM} ${CONFIGFILE} + +echo "/* MiniUPnP Project" >> ${CONFIGFILE} +echo " * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/" >> ${CONFIGFILE} +echo " * (c) 2006-2008 Thomas Bernard" >> ${CONFIGFILE} +echo " * generated by $0 on `date` */" >> ${CONFIGFILE} +echo "#ifndef $CONFIGMACRO" >> ${CONFIGFILE} +echo "#define $CONFIGMACRO" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} +echo "#define UPNP_VERSION \"$UPNP_VERSION\"" >> ${CONFIGFILE} + +# OS Specific stuff +case $OS_NAME in + OpenBSD) + MAJORVER=`echo $OS_VERSION | cut -d. -f1` + MINORVER=`echo $OS_VERSION | cut -d. -f2` + #echo "OpenBSD majorversion=$MAJORVER minorversion=$MINORVER" + # rtableid was introduced in OpenBSD 4.0 + if [ $MAJORVER -ge 4 ]; then + echo "#define PFRULE_HAS_RTABLEID" >> ${CONFIGFILE} + fi + # from the 3.8 version, packets and bytes counters are double : in/out + if [ \( $MAJORVER -ge 4 \) -o \( $MAJORVER -eq 3 -a $MINORVER -ge 8 \) ]; then + echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} + fi + echo "#define USE_PF 1" >> ${CONFIGFILE} + OS_URL=http://www.openbsd.org/ + ;; + FreeBSD) + VER=`grep '#define __FreeBSD_version' /usr/include/sys/param.h | awk '{print $3}'` + if [ $VER -ge 700049 ]; then + echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} + fi + if [ -f /usr/include/net/pfvar.h ] ; then + echo "#define USE_PF 1" >> ${CONFIGFILE} + else + echo "#define USE_IPF 1" >> ${CONFIGFILE} + fi + OS_URL=http://www.freebsd.org/ + ;; + pfSense) + # we need to detect if PFRULE_INOUT_COUNTS macro is needed + echo "#define USE_PF 1" >> ${CONFIGFILE} + OS_URL=http://www.pfsense.com/ + ;; + NetBSD) + OS_URL=http://www.netbsd.org/ + if [ -f /usr/include/net/pfvar.h ] ; then + echo "#define USE_PF 1" >> ${CONFIGFILE} + else + echo "#define USE_IPF 1" >> ${CONFIGFILE} + fi + ;; + SunOS) + echo "#define USE_IPF 1" >> ${CONFIGFILE} + echo "#define LOG_PERROR 0" >> ${CONFIGFILE} + echo "#define SOLARIS_KSTATS 1" >> ${CONFIGFILE} + echo "typedef uint64_t u_int64_t;" >> ${CONFIGFILE} + echo "typedef uint32_t u_int32_t;" >> ${CONFIGFILE} + echo "typedef uint16_t u_int16_t;" >> ${CONFIGFILE} + echo "typedef uint8_t u_int8_t;" >> ${CONFIGFILE} + OS_URL=http://www.sun.com/solaris/ + ;; + Linux) + OS_URL=http://www.kernel.org/ + KERNVERA=`echo $OS_VERSION | awk -F. '{print $1}'` + KERNVERB=`echo $OS_VERSION | awk -F. '{print $2}'` + KERNVERC=`echo $OS_VERSION | awk -F. '{print $3}'` + KERNVERD=`echo $OS_VERSION | awk -F. '{print $4}'` + #echo "$KERNVERA.$KERNVERB.$KERNVERC.$KERNVERD" + # Debian GNU/Linux special case + if [ -f /etc/debian_version ]; then + OS_NAME=Debian + OS_VERSION=`cat /etc/debian_version` + OS_URL=http://www.debian.org/ + fi + # use lsb_release (Linux Standard Base) when available + LSB_RELEASE=`which lsb_release` + if [ 0 -eq $? ]; then + OS_NAME=`${LSB_RELEASE} -i -s` + OS_VERSION=`${LSB_RELEASE} -r -s` + fi + echo "#define USE_NETFILTER 1" >> ${CONFIGFILE} + ;; + *) + echo "Unknown OS : $OS_NAME" + echo "Please contact the author at http://miniupnp.free.fr/" + exit 1 + ;; +esac + +echo "#define OS_NAME \"$OS_NAME\"" >> ${CONFIGFILE} +echo "#define OS_VERSION \"$OS_NAME/$OS_VERSION\"" >> ${CONFIGFILE} +echo "#define OS_URL \"${OS_URL}\"" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* syslog facility to be used by miniupnpd */" >> ${CONFIGFILE} +echo "#define LOG_MINIUPNPD ${LOG_MINIUPNPD}" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to allow miniupnpd to be" >> ${CONFIGFILE} +echo " * controlled by miniupnpdctl */" >> ${CONFIGFILE} +echo "/*#define USE_MINIUPNPDCTL*/" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Comment the following line to disable NAT-PMP operations */" >> ${CONFIGFILE} +echo "#define ENABLE_NATPMP" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to enable generation of" >> ${CONFIGFILE} +echo " * filter rules with pf */" >> ${CONFIGFILE} +echo "/*#define PF_ENABLE_FILTER_RULES*/">> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to enable caching of results of" >> ${CONFIGFILE} +echo " * the getifstats() function */" >> ${CONFIGFILE} +echo "/*#define ENABLE_GETIFSTATS_CACHING*/" >> ${CONFIGFILE} +echo "/* The cache duration is indicated in seconds */" >> ${CONFIGFILE} +echo "#define GETIFSTATS_CACHING_DURATION 2" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to enable multiple external ip support */" >> ${CONFIGFILE} +echo "/* note : Thas is EXPERIMENTAL, do not use that unless you know perfectly what you are doing */" >> ${CONFIGFILE} +echo "/*#define MULTIPLE_EXTERNAL_IP*/" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Comment the following line to use home made daemonize() func instead" >> ${CONFIGFILE} +echo " * of BSD daemon() */" >> ${CONFIGFILE} +echo "#define USE_DAEMON" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to enable lease file support */" >> ${CONFIGFILE} +echo "/*#define ENABLE_LEASEFILE*/" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Define one or none of the two following macros in order to make some" >> ${CONFIGFILE} +echo " * clients happy. It will change the XML Root Description of the IGD." >> ${CONFIGFILE} +echo " * Enabling the Layer3Forwarding Service seems to be the more compatible" >> ${CONFIGFILE} +echo " * option. */" >> ${CONFIGFILE} +echo "/*#define HAS_DUMMY_SERVICE*/" >> ${CONFIGFILE} +echo "#define ENABLE_L3F_SERVICE" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Experimental UPnP Events support. */" >> ${CONFIGFILE} +echo "/*#define ENABLE_EVENTS*/" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "#endif" >> ${CONFIGFILE} + +exit 0 diff --git a/getifaddr.c b/getifaddr.c new file mode 100644 index 0000000..c7d10a0 --- /dev/null +++ b/getifaddr.c @@ -0,0 +1,55 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(sun) +#include +#endif + +#include "getifaddr.h" + +int +getifaddr(const char * ifname, char * buf, int len) +{ + /* SIOCGIFADDR struct ifreq * */ + int s; + struct ifreq ifr; + int ifrlen; + struct sockaddr_in * addr; + ifrlen = sizeof(ifr); + s = socket(PF_INET, SOCK_DGRAM, 0); + if(s < 0) + { + syslog(LOG_ERR, "socket(PF_INET, SOCK_DGRAM): %m"); + return -1; + } + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + if(ioctl(s, SIOCGIFADDR, &ifr, &ifrlen) < 0) + { + syslog(LOG_ERR, "ioctl(s, SIOCGIFADDR, ...): %m"); + close(s); + return -1; + } + addr = (struct sockaddr_in *)&ifr.ifr_addr; + if(!inet_ntop(AF_INET, &addr->sin_addr, buf, len)) + { + syslog(LOG_ERR, "inet_ntop(): %m"); + close(s); + return -1; + } + close(s); + return 0; +} + diff --git a/getifaddr.h b/getifaddr.h new file mode 100644 index 0000000..053c2c1 --- /dev/null +++ b/getifaddr.h @@ -0,0 +1,18 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __GETIFADDR_H__ +#define __GETIFADDR_H__ + +/* getifaddr() + * take a network interface name and write the + * ip v4 address as text in the buffer + * returns: 0 success, -1 failure */ +int +getifaddr(const char * ifname, char * buf, int len); + +#endif + diff --git a/linux/miniupnpd.init.d.script b/linux/miniupnpd.init.d.script new file mode 100644 index 0000000..1f25cf0 --- /dev/null +++ b/linux/miniupnpd.init.d.script @@ -0,0 +1,40 @@ +#!/bin/sh +# $Id$ +# MiniUPnP project +# author: Thomas Bernard +# website: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + +MINIUPNPD=/usr/sbin/miniupnpd +ARGS='-f /etc/miniupnpd/miniupnpd.conf' + +IPTABLES_CREATE=/etc/miniupnpd/iptables_init.sh +IPTABLES_REMOVE=/etc/miniupnpd/iptables_removeall.sh + +test -f $MINIUPNPD || exit 0 + +. /lib/lsb/init-functions + +case "$1" in +start) log_daemon_msg "Starting miniupnpd" "miniupnpd" + $IPTABLES_CREATE > /dev/null 2>&1 + start-stop-daemon --start --quiet --pidfile /var/run/miniupnpd.pid --startas $MINIUPNPD -- $ARGS $LSBNAMES + log_end_msg $? + ;; +stop) log_daemon_msg "Stopping miniupnpd" "miniupnpd" + start-stop-daemon --stop --quiet --pidfile /var/run/miniupnpd.pid + log_end_msg $? + $IPTABLES_REMOVE > /dev/null 2>&1 + ;; +restart|reload|force-reload) + log_daemon_msg "Restarting miniupnpd" "miniupnpd" + start-stop-daemon --stop --retry 5 --quiet --pidfile /var/run/miniupnpd.pid + $IPTABLES_REMOVE > /dev/null 2>&1 + $IPTABLES_CREATE > /dev/null 2>&1 + start-stop-daemon --start --quiet --pidfile /var/run/miniupnpd.pid --startas $MINIUPNPD -- $ARGS $LSBNAMES + log_end_msg $? + ;; +*) log_action_msg "Usage: /etc/init.d/miniupnpd {start|stop|restart|reload|force-reload}" + exit 2 + ;; +esac +exit 0 diff --git a/metadata.c b/metadata.c new file mode 100644 index 0000000..845a4f1 --- /dev/null +++ b/metadata.c @@ -0,0 +1,406 @@ +/* MiniDLNA media server + * Copyright (C) 2008 Justin Maggard + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "upnpglobalvars.h" +#include "metadata.h" + +#define FLAG_ARTIST 0x01 + +char * +trim(char *str) +{ + if (!str) + return(NULL); + int i; + for (i=0; i <= strlen(str) && (isspace(str[i]) || str[i] == '"'); i++) { + str++; + } + for (i=(strlen(str)-1); i >= 0 && (isspace(str[i]) || str[i] == '"'); i--) { + str[i] = '\0'; + } + return str; +} + +char * +modifyString(char * string, const char * before, const char * after, short like) +{ + int oldlen, newlen, chgcnt = 0; + char *s, *p, *t; + + oldlen = strlen(before); + newlen = strlen(after); + if( newlen > oldlen ) + { + s = string; + while( (p = strstr(s, before)) ) + { + chgcnt++; + s = p+oldlen; + } + string = realloc(string, strlen(string)+((newlen-oldlen)*chgcnt)+1); + } + + s = string; + while( s ) + { + p = strcasestr(s, before); + if( !p ) + return string; + if( like ) + { + t = p+oldlen; + while( isspace(*t) ) + t++; + if( *t == '"' ) + while( *++t != '"' ) + continue; + memmove(t+1, t, strlen(t)+1); + *t = '%'; + } + memmove(p + newlen, p + oldlen, strlen(p + oldlen) + 1); + memcpy(p, after, newlen); + s = p + newlen; + } + if( newlen < oldlen ) + string = realloc(string, strlen(string)+1); + + return string; +} + +void +strip_ext(char * name) +{ + if( rindex(name, '.') ) + *rindex(name, '.') = '\0'; +} + +sqlite_int64 +GetAudioMetadata(const char * path, char * name) +{ + size_t size = 0; + char date[16], duration[16], dlna_pn[24], mime[16]; + struct stat file; + int seconds, minutes; + sqlite_int64 ret; + TagLib_File *audio_file; + TagLib_Tag *tag; + const TagLib_AudioProperties *properties; + char *sql; + char *zErrMsg = NULL; + char *title, *artist, *album, *genre, *comment; + int free_flags = 0; + + if ( stat(path, &file) == 0 ) + size = file.st_size; + else + return 0; + strip_ext(name); + + taglib_set_strings_unicode(1); + + audio_file = taglib_file_new(path); + if(audio_file == NULL) + return 0; + + tag = taglib_file_tag(audio_file); + properties = taglib_file_audioproperties(audio_file); + + seconds = taglib_audioproperties_length(properties) % 60; + minutes = (taglib_audioproperties_length(properties) - seconds) / 60; + + date[0] = '\0'; + if( taglib_tag_year(tag) ) + sprintf(date, "%04d-01-01", taglib_tag_year(tag)); + sprintf(duration, "%d:%02d:%02d.000", minutes/60, minutes, seconds); + + title = taglib_tag_title(tag); + if( strlen(title) ) + { + title = trim(title); + if( index(title, '&') ) + { + title = modifyString(strdup(title), "&", "&amp;", 0); + } + } + else + { + title = name; + } + artist = taglib_tag_artist(tag); + if( strlen(artist) ) + { + artist = trim(artist); + if( index(artist, '&') ) + { + free_flags |= FLAG_ARTIST; + artist = modifyString(strdup(artist), "&", "&amp;", 0); + } + } + else + { + artist = NULL; + } + album = taglib_tag_album(tag); + if( strlen(album) ) + { + album = trim(album); + if( index(album, '&') ) + { + album = modifyString(strdup(album), "&", "&amp;", 0); + } + } + else + { + album = NULL; + } + genre = taglib_tag_genre(tag); + if( strlen(genre) ) + { + genre = trim(genre); + if( index(genre, '&') ) + { + genre = modifyString(strdup(genre), "&", "&amp;", 0); + } + } + else + { + genre = NULL; + } + comment = taglib_tag_comment(tag); + if( strlen(comment) ) + { + comment = trim(comment); + if( index(comment, '&') ) + { + comment = modifyString(strdup(comment), "&", "&amp;", 0); + } + } + else + { + comment = NULL; + } + + + if( 1 ) // Switch on audio file type + { + strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01"); + strcpy(mime, "audio/mpeg"); + } + + sql = sqlite3_mprintf( "INSERT into DETAILS" + " (SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," + " TITLE, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) " + "VALUES" + " (%d, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');", + size, duration, taglib_audioproperties_channels(properties), + taglib_audioproperties_bitrate(properties)*1024, + taglib_audioproperties_samplerate(properties), + strlen(date) ? date : NULL, + title, + artist, + album, + genre, + comment, + taglib_tag_track(tag), + dlna_pn, mime); + + taglib_tag_free_strings(); + taglib_file_free(audio_file); + + if( free_flags & FLAG_ARTIST ) + free(artist); + + //DEBUG printf("SQL: %s\n", sql); + if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) + { + fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg); + if (zErrMsg) + sqlite3_free(zErrMsg); + ret = 0; + } + else + { + ret = sqlite3_last_insert_rowid(db); + } + sqlite3_free(sql); + return ret; +} + +sqlite_int64 +GetImageMetadata(const char * path, char * name) +{ + ExifData *ed; + ExifEntry *e = NULL; + ExifTag tag; + int width=0, height=0, thumb=0; + size_t size; + char date[64], make[32], model[64], dlna_pn[64]; + char b[1024]; + struct stat file; + sqlite_int64 ret; + char *sql; + char *zErrMsg = NULL; + + date[0] = '\0'; + model[0] = '\0'; + dlna_pn[0] = '\0'; + + //DEBUG printf("Parsing %s...\n", path); + if ( stat(path, &file) == 0 ) + size = file.st_size; + else + return 0; + strip_ext(name); + //DEBUG printf(" * size: %d\n", size); + + ExifLoader * l = exif_loader_new(); + exif_loader_write_file(l, path); + ed = exif_loader_get_data(l); + exif_loader_unref(l); + + tag = EXIF_TAG_PIXEL_X_DIMENSION; + e = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], tag); + if( e ) + width = atoi( exif_entry_get_value(e, b, sizeof(b)) ); + + tag = EXIF_TAG_PIXEL_Y_DIMENSION; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tag); + if( e ) + height = atoi( exif_entry_get_value(e, b, sizeof(b)) ); + //DEBUG printf(" * resolution: %dx%d\n", width, height); + + tag = EXIF_TAG_DATE_TIME_ORIGINAL; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tag); + if( e ) { + strncpy(date, exif_entry_get_value(e, b, sizeof(b)), sizeof(date)); + if( strlen(date) > 10 ) + { + date[4] = '-'; + date[7] = '-'; + date[10] = 'T'; + } + else { + strcpy(date, "0000-00-00"); + } + } + else { + strcpy(date, "0000-00-00"); + } + //DEBUG printf(" * date: %s\n", date); + + model[0] = '\0'; + tag = EXIF_TAG_MAKE; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tag); + if( e ) + { + strncpy(make, exif_entry_get_value(e, b, sizeof(b)), sizeof(make)); + tag = EXIF_TAG_MODEL; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tag); + if( e ) + { + strncpy(model, exif_entry_get_value(e, b, sizeof(b)), sizeof(model)); + if( !strcasestr(model, make) ) + snprintf(model, sizeof(model), "%s %s", make, exif_entry_get_value(e, b, sizeof(b))); + } + } + if( !strlen(model) ) + strcpy(model, "Unknown"); + //DEBUG printf(" * model: %s\n", model); + + if( ed->size ) + thumb = 1; + else + thumb = 0; + //DEBUG printf(" * thumbnail: %d\n", thumb); + + exif_data_unref(ed); + + if( width <= 640 && height <= 480 ) + strcpy(dlna_pn, "JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + else if( width <= 1024 && height <= 768 ) + strcpy(dlna_pn, "JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + else if( width <= 4096 && height <= 4096 ) + strcpy(dlna_pn, "JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + else + strcpy(dlna_pn, "JPEG_XL"); + + sql = sqlite3_mprintf( "INSERT into DETAILS" + " (TITLE, SIZE, DATE, WIDTH, HEIGHT, THUMBNAIL, CREATOR, DLNA_PN, MIME) " + "VALUES" + " ('%q', %d, '%s', %d, %d, %d, '%q', '%s', '%s');", + name, size, date, width, height, thumb, model, dlna_pn, "image/jpeg"); + //DEBUG printf("SQL: %s\n", sql); + if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) + { + fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg); + if (zErrMsg) + sqlite3_free(zErrMsg); + ret = 0; + } + else + { + ret = sqlite3_last_insert_rowid(db); + } + sqlite3_free(sql); + return ret; +} + +sqlite_int64 +GetVideoMetadata(const char * path, char * name) +{ + size_t size = 0; + struct stat file; + char *sql; + char *zErrMsg = NULL; + int ret; + + //DEBUG printf("Parsing %s...\n", path); + if ( stat(path, &file) == 0 ) + size = file.st_size; + strip_ext(name); + //DEBUG printf(" * size: %d\n", size); + + sql = sqlite3_mprintf( "INSERT into DETAILS" + " (TITLE, SIZE, MIME) " + "VALUES" + " ('%q', %d, %Q);", + name, size, "video/mpeg"); + //DEBUG printf("SQL: %s\n", sql); + if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) + { + fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg); + if (zErrMsg) + sqlite3_free(zErrMsg); + ret = 0; + } + else + { + ret = sqlite3_last_insert_rowid(db); + } + sqlite3_free(sql); + return ret; +} diff --git a/metadata.h b/metadata.h new file mode 100644 index 0000000..4c2eff2 --- /dev/null +++ b/metadata.h @@ -0,0 +1,25 @@ +/* Metadata extraction + * + * Project : minidlna + * Website : http://sourceforge.net/projects/minidlna/ + * Author : Justin Maggard + * Copyright (c) 2008 Justin Maggard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __METADATA_H__ +#define __METADATA_H__ + +char * +modifyString(char * string, const char * before, const char * after, short like); + +sqlite_int64 +GetAudioMetadata(const char * path, char * name); + +sqlite_int64 +GetImageMetadata(const char * path, char * name); + +sqlite_int64 +GetVideoMetadata(const char * path, char * name); + +#endif diff --git a/minidlna.c b/minidlna.c new file mode 100644 index 0000000..a9f02b4 --- /dev/null +++ b/minidlna.c @@ -0,0 +1,872 @@ +/* MiniDLNA project + * + * http://sourceforge.net/projects/minidlna/ + * (c) 2008 Justin Maggard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution + * + * Portions of the code (c) Thomas Bernard, subject to + * the conditions detailed in the LICENSE.miniupnpd file. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(sun) +#include +#else +/* for BSD's sysctl */ +#include +#endif + +#include + +/* unix sockets */ +#include "config.h" + +#include "upnpglobalvars.h" +#include "upnphttp.h" +#include "upnpdescgen.h" +#include "miniupnpdpath.h" +#include "getifaddr.h" +#include "upnpsoap.h" +#include "options.h" +#include "minissdp.h" +#include "miniupnpdtypes.h" +#include "daemonize.h" +#include "upnpevents.h" +#include "scanner.h" +#include "commonrdr.h" + +/* MAX_LAN_ADDR : maximum number of interfaces + * to listen to SSDP traffic */ +/*#define MAX_LAN_ADDR (4)*/ + +static volatile int quitting = 0; + +/* OpenAndConfHTTPSocket() : + * setup the socket used to handle incoming HTTP connections. */ +static int +OpenAndConfHTTPSocket(unsigned short port) +{ + int s; + int i = 1; + struct sockaddr_in listenname; + + if( (s = socket(PF_INET, SOCK_STREAM, 0)) < 0) + { + syslog(LOG_ERR, "socket(http): %m"); + return -1; + } + + if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) + { + syslog(LOG_WARNING, "setsockopt(http, SO_REUSEADDR): %m"); + } + + memset(&listenname, 0, sizeof(struct sockaddr_in)); + listenname.sin_family = AF_INET; + listenname.sin_port = htons(port); + listenname.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(s, (struct sockaddr *)&listenname, sizeof(struct sockaddr_in)) < 0) + { + syslog(LOG_ERR, "bind(http): %m"); + close(s); + return -1; + } + + if(listen(s, 6) < 0) + { + syslog(LOG_ERR, "listen(http): %m"); + close(s); + return -1; + } + + return s; +} + +/* Handler for the SIGTERM signal (kill) + * SIGINT is also handled */ +static void +sigterm(int sig) +{ + /*int save_errno = errno;*/ + signal(sig, SIG_IGN); /* Ignore this signal while we are quitting */ + + syslog(LOG_NOTICE, "received signal %d, good-bye", sig); + + quitting = 1; + /*errno = save_errno;*/ +} + +/* record the startup time, for returning uptime */ +static void +set_startup_time(int sysuptime) +{ + startup_time = time(NULL); + if(sysuptime) + { + /* use system uptime instead of daemon uptime */ + char buff[64]; + int uptime, fd; + fd = open("/proc/uptime", O_RDONLY); + if(fd < 0) + { + syslog(LOG_ERR, "open(\"/proc/uptime\" : %m"); + } + else + { + memset(buff, 0, sizeof(buff)); + read(fd, buff, sizeof(buff) - 1); + uptime = atoi(buff); + syslog(LOG_INFO, "system uptime is %d seconds", uptime); + close(fd); + startup_time -= uptime; + } + } +} + +/* structure containing variables used during "main loop" + * that are filled during the init */ +struct runtime_vars { + /* LAN IP addresses for SSDP traffic and HTTP */ + /* moved to global vars */ + /*int n_lan_addr;*/ + /*struct lan_addr_s lan_addr[MAX_LAN_ADDR];*/ + int port; /* HTTP Port */ + int notify_interval; /* seconds between SSDP announces */ + /* unused rules cleaning related variables : */ + int clean_ruleset_threshold; /* threshold for removing unused rules */ + int clean_ruleset_interval; /* (minimum) interval between checks */ +}; + +/* parselanaddr() + * parse address with mask + * ex: 192.168.1.1/24 + * return value : + * 0 : ok + * -1 : error */ +static int +parselanaddr(struct lan_addr_s * lan_addr, const char * str) +{ + const char * p; + int nbits = 24; + int n; + p = str; + while(*p && *p != '/' && !isspace(*p)) + p++; + n = p - str; + if(*p == '/') + { + nbits = atoi(++p); + while(*p && !isspace(*p)) + p++; + } + if(n>15) + { + fprintf(stderr, "Error parsing address/mask : %s\n", str); + return -1; + } + memcpy(lan_addr->str, str, n); + lan_addr->str[n] = '\0'; + if(!inet_aton(lan_addr->str, &lan_addr->addr)) + { + fprintf(stderr, "Error parsing address/mask : %s\n", str); + return -1; + } + lan_addr->mask.s_addr = htonl(nbits ? (0xffffffff << (32 - nbits)) : 0); +#ifdef MULTIPLE_EXTERNAL_IP + while(*p && isspace(*p)) + p++; + if(*p) { + n = 0; + while(p[n] && !isspace(*p)) + n++; + if(n<=15) { + memcpy(lan_addr->ext_ip_str, p, n); + lan_addr->ext_ip_str[n] = '\0'; + if(!inet_aton(lan_addr->ext_ip_str, &lan_addr->ext_ip_addr)) { + /* error */ + fprintf(stderr, "Error parsing address : %s\n", lan_addr->ext_ip_str); + } + } + } +#endif + return 0; +} + +/* init phase : + * 1) read configuration file + * 2) read command line arguments + * 3) daemonize + * 4) open syslog + * 5) check and write pid file + * 6) set startup time stamp + * 7) compute presentation URL + * 8) set signal handlers */ +static int +init(int argc, char * * argv, struct runtime_vars * v) +{ + int i; + int pid; + int debug_flag = 0; + int options_flag = 0; + int openlog_option; + struct sigaction sa; + /*const char * logfilename = 0;*/ + const char * presurl = 0; + const char * optionsfile = "/etc/minidlna.conf"; + + /* first check if "-f" option is used */ + for(i=2; in_lan_addr = 0;*/ + char ext_ip_addr[INET_ADDRSTRLEN]; + if( (getifaddr("eth0", ext_ip_addr, INET_ADDRSTRLEN) < 0) && + (getifaddr("eth1", ext_ip_addr, INET_ADDRSTRLEN) < 0) ) + { + printf("No IP!\n"); + return 1; + } + if( parselanaddr(&lan_addr[n_lan_addr], ext_ip_addr) == 0 ) + n_lan_addr++; + v->port = -1; + v->notify_interval = 30; /* seconds between SSDP announces */ + v->clean_ruleset_threshold = 20; + v->clean_ruleset_interval = 0; /* interval between ruleset check. 0=disabled */ + + /* read options file first since + * command line arguments have final say */ + if(readoptionsfile(optionsfile) < 0) + { + /* only error if file exists or using -f */ + if(access(optionsfile, F_OK) == 0 || options_flag) + fprintf(stderr, "Error reading configuration file %s\n", optionsfile); + } + else + { + for(i=0; in_lan_addr < MAX_LAN_ADDR)*/ + { + /*if(parselanaddr(&v->lan_addr[v->n_lan_addr],*/ + if(parselanaddr(&lan_addr[n_lan_addr], + ary_options[i].value) == 0) + n_lan_addr++; /*v->n_lan_addr++; */ + } + else + { + fprintf(stderr, "Too many listening ips (max: %d), ignoring %s\n", + MAX_LAN_ADDR, ary_options[i].value); + } + break; + case UPNPPORT: + v->port = atoi(ary_options[i].value); + break; + case UPNPBITRATE_UP: + upstream_bitrate = strtoul(ary_options[i].value, 0, 0); + break; + case UPNPBITRATE_DOWN: + downstream_bitrate = strtoul(ary_options[i].value, 0, 0); + break; + case UPNPPRESENTATIONURL: + presurl = ary_options[i].value; + break; + case UPNPNOTIFY_INTERVAL: + v->notify_interval = atoi(ary_options[i].value); + break; + case UPNPSYSTEM_UPTIME: + if(strcmp(ary_options[i].value, "yes") == 0) + SETFLAG(SYSUPTIMEMASK); /*sysuptime = 1;*/ + break; + case UPNPPACKET_LOG: + if(strcmp(ary_options[i].value, "yes") == 0) + SETFLAG(LOGPACKETSMASK); /*logpackets = 1;*/ + break; + case UPNPUUID: + strncpy(uuidvalue+5, ary_options[i].value, + strlen(uuidvalue+5) + 1); + break; + case UPNPSERIAL: + strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); + serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; + break; + case UPNPMODEL_NUMBER: + strncpy(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); + modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; + break; + case UPNPCLEANTHRESHOLD: + v->clean_ruleset_threshold = atoi(ary_options[i].value); + break; + case UPNPCLEANINTERVAL: + v->clean_ruleset_interval = atoi(ary_options[i].value); + break; +#ifdef USE_PF + case UPNPQUEUE: + queue = ary_options[i].value; + break; + case UPNPTAG: + tag = ary_options[i].value; + break; +#endif +#ifdef PF_ENABLE_FILTER_RULES + case UPNPQUICKRULES: + if(strcmp(ary_options[i].value, "no") == 0) + SETFLAG(PFNOQUICKRULESMASK); + break; +#endif + case UPNPSECUREMODE: + if(strcmp(ary_options[i].value, "yes") == 0) + SETFLAG(SECUREMODEMASK); + break; +#ifdef ENABLE_LEASEFILE + case UPNPLEASEFILE: + lease_file = ary_options[i].value; + remove(lease_file); + break; +#endif + case UPNPMEDIADIR: + strncpy(media_dir, ary_options[i].value, MEDIADIR_MAX_LEN); + media_dir[MEDIADIR_MAX_LEN-1] = '\0'; + break; + default: + fprintf(stderr, "Unknown option in file %s\n", + optionsfile); + } + } + } + + /* command line arguments processing */ + for(i=1; inotify_interval = atoi(argv[++i]); + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'u': + if(i+1 < argc) + strncpy(uuidvalue+5, argv[++i], strlen(uuidvalue+5) + 1); + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 's': + if(i+1 < argc) + strncpy(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; + break; + case 'm': + if(i+1 < argc) + strncpy(modelnumber, argv[++i], MODELNUMBER_MAX_LEN); + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; + break; + case 'U': + /*sysuptime = 1;*/ + SETFLAG(SYSUPTIMEMASK); + break; + /*case 'l': + logfilename = argv[++i]; + break;*/ + case 'L': + /*logpackets = 1;*/ + SETFLAG(LOGPACKETSMASK); + break; + case 'S': + SETFLAG(SECUREMODEMASK); + break; + case 'i': + if(i+1 < argc) + ext_if_name = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; +#ifdef USE_PF + case 'q': + if(i+1 < argc) + queue = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'T': + if(i+1 < argc) + tag = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; +#endif + case 'p': + if(i+1 < argc) + v->port = atoi(argv[++i]); + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'P': + if(i+1 < argc) + pidfilename = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'd': + debug_flag = 1; + break; + case 'w': + if(i+1 < argc) + presurl = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'B': + if(i+2n_lan_addr; j++)*/ + { + struct lan_addr_s tmpaddr; + parselanaddr(&tmpaddr, argv[i]); + /*if(0 == strcmp(v->lan_addr[j].str, tmpaddr.str))*/ + if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) + address_already_there = 1; + } + if(address_already_there) + break; + if(n_lan_addr < MAX_LAN_ADDR) /*if(v->n_lan_addr < MAX_LAN_ADDR)*/ + { + /*v->lan_addr[v->n_lan_addr++] = argv[i];*/ + /*if(parselanaddr(&v->lan_addr[v->n_lan_addr], argv[i]) == 0)*/ + if(parselanaddr(&lan_addr[n_lan_addr], argv[i]) == 0) + n_lan_addr++; /*v->n_lan_addr++;*/ + } + else + { + fprintf(stderr, "Too many listening ips (max: %d), ignoring %s\n", + MAX_LAN_ADDR, argv[i]); + } + } + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'f': + i++; /* discarding, the config file is already read */ + break; + default: + fprintf(stderr, "Unknown option: %s\n", argv[i]); + } + } + if(!ext_if_name || (/*v->*/n_lan_addr==0) || v->port<=0) + { + fprintf(stderr, "Usage:\n\t" + "%s [-f config_file] [-i ext_ifname] [-o ext_ip]\n" + "\t\t[-a listening_ip] [-p port] [-d] [-L] [-U] [-S]\n" + /*"[-l logfile] " not functionnal */ + "\t\t[-u uuid] [-s serial] [-m model_number] \n" + "\t\t[-t notify_interval] [-P pid_filename]\n" +#ifdef USE_PF + "\t\t[-B down up] [-w url] [-q queue] [-T tag]\n" +#else + "\t\t[-B down up] [-w url]\n" +#endif + "\nNotes:\n\tThere can be one or several listening_ips.\n" + "\tNotify interval is in seconds. Default is 30 seconds.\n" + "\tDefault pid file is %s.\n" + "\tWith -d miniupnpd will run as a standard program.\n" + "\t-L sets packet log in pf and ipf on.\n" + "\t-S sets \"secure\" mode : clients can only add mappings to their own ip\n" + "\t-U causes miniupnpd to report system uptime instead " + "of daemon uptime.\n" + "\t-B sets bitrates reported by daemon in bits per second.\n" + "\t-w sets the presentation url. Default is http address on port 80\n" +#ifdef USE_PF + "\t-q sets the ALTQ queue in pf.\n" + "\t-T sets the tag name in pf.\n" +#endif + "", argv[0], pidfilename); + return 1; + } + + if(debug_flag) + { + pid = getpid(); + } + else + { +#ifdef USE_DAEMON + if(daemon(0, 0)<0) { + perror("daemon()"); + } + pid = getpid(); +#else + pid = daemonize(); +#endif + } + + openlog_option = LOG_PID|LOG_CONS; + if(debug_flag) + { + openlog_option |= LOG_PERROR; /* also log on stderr */ + } + + openlog("miniupnpd", openlog_option, LOG_MINIUPNPD); + + if(!debug_flag) + { + /* speed things up and ignore LOG_INFO and LOG_DEBUG */ + setlogmask(LOG_UPTO(LOG_NOTICE)); + } + + if(checkforrunning(pidfilename) < 0) + { + syslog(LOG_ERR, "MiniUPnPd is already running. EXITING"); + return 1; + } + + set_startup_time(GETFLAG(SYSUPTIMEMASK)/*sysuptime*/); + + /* presentation url */ + if(presurl) + { + strncpy(presentationurl, presurl, PRESENTATIONURL_MAX_LEN); + presentationurl[PRESENTATIONURL_MAX_LEN-1] = '\0'; + } + else + { + snprintf(presentationurl, PRESENTATIONURL_MAX_LEN, + "http://%s/", lan_addr[0].str); + /*"http://%s:%d/", lan_addr[0].str, 80);*/ + /*"http://%s:%d/", v->lan_addr[0].str, 80);*/ + } + + /* set signal handler */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = sigterm; + + if (sigaction(SIGTERM, &sa, NULL)) + { + syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGTERM"); + return 1; + } + if (sigaction(SIGINT, &sa, NULL)) + { + syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGINT"); + return 1; + } + + if(signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + syslog(LOG_ERR, "Failed to ignore SIGPIPE signals"); + } + + writepidfile(pidfilename, pid); + + return 0; +} + +/* === main === */ +/* process HTTP or SSDP requests */ +int +main(int argc, char * * argv) +{ + int i; + int sudp = -1, shttpl = -1; + int snotify[MAX_LAN_ADDR]; + LIST_HEAD(httplisthead, upnphttp) upnphttphead; + struct upnphttp * e = 0; + struct upnphttp * next; + fd_set readset; /* for select() */ +#ifdef ENABLE_EVENTS + fd_set writeset; +#endif + struct timeval timeout, timeofday, lasttimeofday = {0, 0}; + int max_fd = -1; + struct runtime_vars v; + + if(init(argc, argv, &v) != 0) + return 1; + + LIST_INIT(&upnphttphead); + + if( access("/tmp/files.db", F_OK) ) + { + sqlite3_open("/tmp/files.db", &db); + ScanDirectory(media_dir, NULL); + return 0; + } + else + { + sqlite3_open("/tmp/files.db", &db); + } + + + /* open socket for SSDP connections */ + /*sudp = OpenAndConfSSDPReceiveSocket(v.n_lan_addr, v.lan_addr);*/ + sudp = OpenAndConfSSDPReceiveSocket(n_lan_addr, lan_addr); + if(sudp < 0) + { + syslog(LOG_ERR, "Failed to open socket for receiving SSDP. EXITING"); + return 1; + } + /* open socket for HTTP connections. Listen on the 1st LAN address */ + shttpl = OpenAndConfHTTPSocket(v.port); + if(shttpl < 0) + { + syslog(LOG_ERR, "Failed to open socket for HTTP. EXITING"); + return 1; + } + syslog(LOG_NOTICE, "HTTP listening on port %d", v.port); + + /* open socket for sending notifications */ + if(OpenAndConfSSDPNotifySockets(snotify) < 0) + { + syslog(LOG_ERR, "Failed to open sockets for sending SSDP notify " + "messages. EXITING"); + return 1; + } + + SendSSDPGoodbye(snotify, n_lan_addr); + + /* main loop */ + while(!quitting) + { + /* Check if we need to send SSDP NOTIFY messages and do it if + * needed */ + if(gettimeofday(&timeofday, 0) < 0) + { + syslog(LOG_ERR, "gettimeofday(): %m"); + timeout.tv_sec = v.notify_interval; + timeout.tv_usec = 0; + } + else + { + /* the comparaison is not very precise but who cares ? */ + if(timeofday.tv_sec >= (lasttimeofday.tv_sec + v.notify_interval)) + { + SendSSDPNotifies2(snotify, + (unsigned short)v.port, + (v.notify_interval << 1)+10); + memcpy(&lasttimeofday, &timeofday, sizeof(struct timeval)); + timeout.tv_sec = v.notify_interval; + timeout.tv_usec = 0; + } + else + { + timeout.tv_sec = lasttimeofday.tv_sec + v.notify_interval + - timeofday.tv_sec; + if(timeofday.tv_usec > lasttimeofday.tv_usec) + { + timeout.tv_usec = 1000000 + lasttimeofday.tv_usec + - timeofday.tv_usec; + timeout.tv_sec--; + } + else + { + timeout.tv_usec = lasttimeofday.tv_usec - timeofday.tv_usec; + } + } + } + + /* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */ + FD_ZERO(&readset); + + if (sudp >= 0) + { + FD_SET(sudp, &readset); + max_fd = MAX( max_fd, sudp); + } + + if (shttpl >= 0) + { + FD_SET(shttpl, &readset); + max_fd = MAX( max_fd, shttpl); + } + + i = 0; /* active HTTP connections count */ + for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) + { + if((e->socket >= 0) && (e->state <= 2)) + { + FD_SET(e->socket, &readset); + max_fd = MAX( max_fd, e->socket); + i++; + } + } + /* for debug */ +#ifdef DEBUG + if(i > 1) + { + syslog(LOG_DEBUG, "%d active incoming HTTP connections", i); + } +#endif + +#ifdef ENABLE_EVENTS + FD_ZERO(&writeset); + upnpevents_selectfds(&readset, &writeset, &max_fd); +#endif + +#ifdef ENABLE_EVENTS + if(select(max_fd+1, &readset, &writeset, 0, &timeout) < 0) +#else + if(select(max_fd+1, &readset, 0, 0, &timeout) < 0) +#endif + { + if(quitting) goto shutdown; + syslog(LOG_ERR, "select(all): %m"); + syslog(LOG_ERR, "Failed to select open sockets. EXITING"); + return 1; /* very serious cause of error */ + } +#ifdef ENABLE_EVENTS + upnpevents_processfds(&readset, &writeset); +#endif + /* process SSDP packets */ + if(sudp >= 0 && FD_ISSET(sudp, &readset)) + { + /*syslog(LOG_INFO, "Received UDP Packet");*/ + /*ProcessSSDPRequest(sudp, v.lan_addr, v.n_lan_addr,*/ + ProcessSSDPRequest(sudp, (unsigned short)v.port); + } + /* process active HTTP connections */ + /* LIST_FOREACH macro is not available under linux */ + for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) + { + if( (e->socket >= 0) && (e->state <= 2) + &&(FD_ISSET(e->socket, &readset)) ) + { + Process_upnphttp(e); + } + } + /* process incoming HTTP connections */ + if(shttpl >= 0 && FD_ISSET(shttpl, &readset)) + { + int shttp; + socklen_t clientnamelen; + struct sockaddr_in clientname; + clientnamelen = sizeof(struct sockaddr_in); + shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); + if(shttp<0) + { + syslog(LOG_ERR, "accept(http): %m"); + } + else + { + struct upnphttp * tmp = 0; + syslog(LOG_INFO, "HTTP connection from %s:%d", + inet_ntoa(clientname.sin_addr), + ntohs(clientname.sin_port) ); + /*if (fcntl(shttp, F_SETFL, O_NONBLOCK) < 0) { + syslog(LOG_ERR, "fcntl F_SETFL, O_NONBLOCK"); + }*/ + /* Create a new upnphttp object and add it to + * the active upnphttp object list */ + tmp = New_upnphttp(shttp); + if(tmp) + { + tmp->clientaddr = clientname.sin_addr; + LIST_INSERT_HEAD(&upnphttphead, tmp, entries); + } + else + { + syslog(LOG_ERR, "New_upnphttp() failed"); + close(shttp); + } + } + } + /* delete finished HTTP connections */ + for(e = upnphttphead.lh_first; e != NULL; ) + { + next = e->entries.le_next; + if(e->state >= 100) + { + LIST_REMOVE(e, entries); + Delete_upnphttp(e); + } + e = next; + } + } + +shutdown: + /* close out open sockets */ + while(upnphttphead.lh_first != NULL) + { + e = upnphttphead.lh_first; + LIST_REMOVE(e, entries); + Delete_upnphttp(e); + } + + if (sudp >= 0) close(sudp); + if (shttpl >= 0) close(shttpl); + + if(SendSSDPGoodbye(snotify, n_lan_addr) < 0) + { + syslog(LOG_ERR, "Failed to broadcast good-bye notifications"); + } + for(i=0; i +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "upnpdescstrings.h" +#include "miniupnpdpath.h" +#include "upnphttp.h" +#include "upnpglobalvars.h" +#include "minissdp.h" + +/* SSDP ip/port */ +#define SSDP_PORT (1900) +#define SSDP_MCAST_ADDR ("239.255.255.250") + +static int +AddMulticastMembership(int s, in_addr_t ifaddr/*const char * ifaddr*/) +{ + struct ip_mreq imr; /* Ip multicast membership */ + + /* setting up imr structure */ + imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); + /*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/ + imr.imr_interface.s_addr = ifaddr; /*inet_addr(ifaddr);*/ + + if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(struct ip_mreq)) < 0) + { + syslog(LOG_ERR, "setsockopt(udp, IP_ADD_MEMBERSHIP): %m"); + return -1; + } + + return 0; +} + +int +OpenAndConfSSDPReceiveSocket() +/*OpenAndConfSSDPReceiveSocket(int n_lan_addr, + struct lan_addr_s * lan_addr)*/ +{ + int s; + int i = 1; + struct sockaddr_in sockname; + + if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + { + syslog(LOG_ERR, "socket(udp): %m"); + return -1; + } + + if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) + { + syslog(LOG_WARNING, "setsockopt(udp, SO_REUSEADDR): %m"); + } + + memset(&sockname, 0, sizeof(struct sockaddr_in)); + sockname.sin_family = AF_INET; + sockname.sin_port = htons(SSDP_PORT); + /* NOTE : it seems it doesnt work when binding on the specific address */ + /*sockname.sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/ + sockname.sin_addr.s_addr = htonl(INADDR_ANY); + /*sockname.sin_addr.s_addr = inet_addr(ifaddr);*/ + + if(bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) + { + syslog(LOG_ERR, "bind(udp): %m"); + close(s); + return -1; + } + + i = n_lan_addr; + while(i>0) + { + i--; + if(AddMulticastMembership(s, lan_addr[i].addr.s_addr) < 0) + { + syslog(LOG_WARNING, + "Failed to add multicast membership for address %s", + lan_addr[i].str ); + } + } + + return s; +} + +/* open the UDP socket used to send SSDP notifications to + * the multicast group reserved for them */ +static int +OpenAndConfSSDPNotifySocket(in_addr_t addr) +{ + int s; + unsigned char loopchar = 0; + int bcast = 1; + struct in_addr mc_if; + struct sockaddr_in sockname; + + if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + { + syslog(LOG_ERR, "socket(udp_notify): %m"); + return -1; + } + + mc_if.s_addr = addr; /*inet_addr(addr);*/ + + if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0) + { + syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %m"); + close(s); + return -1; + } + + if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0) + { + syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_IF): %m"); + close(s); + return -1; + } + + if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0) + { + syslog(LOG_ERR, "setsockopt(udp_notify, SO_BROADCAST): %m"); + close(s); + return -1; + } + + memset(&sockname, 0, sizeof(struct sockaddr_in)); + sockname.sin_family = AF_INET; + sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/ + + if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) + { + syslog(LOG_ERR, "bind(udp_notify): %m"); + close(s); + return -1; + } + + return s; +} + +int +OpenAndConfSSDPNotifySockets(int * sockets) +/*OpenAndConfSSDPNotifySockets(int * sockets, + struct lan_addr_s * lan_addr, int n_lan_addr)*/ +{ + int i, j; + for(i=0; i=sizeof(bufr)) + { + syslog(LOG_WARNING, "SendSSDPNotifies(): truncated output"); + l = sizeof(bufr); + } + //DEBUG printf("Sending NOTIFY:\n%s", bufr); + n = sendto(s, bufr, l, 0, + (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); + if(n < 0) + { + syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s, host); + } + i++; + } + } +} + +void +SendSSDPNotifies2(int * sockets, + unsigned short port, + unsigned int lifetime) +/*SendSSDPNotifies2(int * sockets, struct lan_addr_s * lan_addr, int n_lan_addr, + unsigned short port, + unsigned int lifetime)*/ +{ + int i; + for(i=0; i + +/* structure for storing lan addresses + * with ascii representation and mask */ +struct lan_addr_s { + char str[16]; /* example: 192.168.0.1 */ + struct in_addr addr, mask; /* ip/mask */ +#ifdef MULTIPLE_EXTERNAL_IP + char ext_ip_str[16]; + struct in_addr ext_ip_addr; +#endif +}; + +#endif diff --git a/minixml.c b/minixml.c new file mode 100644 index 0000000..b335b68 --- /dev/null +++ b/minixml.c @@ -0,0 +1,191 @@ +/* $Id$ */ +/* minixml.c : the minimum size a xml parser can be ! */ +/* Project : miniupnp + * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + +Copyright (c) 2005-2007, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include "minixml.h" + +/* parseatt : used to parse the argument list + * return 0 (false) in case of success and -1 (true) if the end + * of the xmlbuffer is reached. */ +int parseatt(struct xmlparser * p) +{ + const char * attname; + int attnamelen; + const char * attvalue; + int attvaluelen; + while(p->xml < p->xmlend) + { + if(*p->xml=='/' || *p->xml=='>') + return 0; + if( !IS_WHITE_SPACE(*p->xml) ) + { + char sep; + attname = p->xml; + attnamelen = 0; + while(*p->xml!='=' && !IS_WHITE_SPACE(*p->xml) ) + { + attnamelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + while(*(p->xml++) != '=') + { + if(p->xml >= p->xmlend) + return -1; + } + while(IS_WHITE_SPACE(*p->xml)) + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + sep = *p->xml; + if(sep=='\'' || sep=='\"') + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + attvalue = p->xml; + attvaluelen = 0; + while(*p->xml != sep) + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + else + { + attvalue = p->xml; + attvaluelen = 0; + while( !IS_WHITE_SPACE(*p->xml) + && *p->xml != '>' && *p->xml != '/') + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + /*printf("%.*s='%.*s'\n", + attnamelen, attname, attvaluelen, attvalue);*/ + if(p->attfunc) + p->attfunc(p->data, attname, attnamelen, attvalue, attvaluelen); + } + p->xml++; + } + return -1; +} + +/* parseelt parse the xml stream and + * call the callback functions when needed... */ +void parseelt(struct xmlparser * p) +{ + int i; + const char * elementname; + while(p->xml < (p->xmlend - 1)) + { + if((p->xml)[0]=='<' && (p->xml)[1]!='?') + { + i = 0; elementname = ++p->xml; + while( !IS_WHITE_SPACE(*p->xml) + && (*p->xml!='>') && (*p->xml!='/') + ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + /* to ignore namespace : */ + if(*p->xml==':') + { + i = 0; + elementname = ++p->xml; + } + } + if(i>0) + { + if(p->starteltfunc) + p->starteltfunc(p->data, elementname, i); + if(parseatt(p)) + return; + if(*p->xml!='/') + { + const char * data; + i = 0; data = ++p->xml; + if (p->xml >= p->xmlend) + return; + while( IS_WHITE_SPACE(*p->xml) ) + { + p->xml++; + if (p->xml >= p->xmlend) + return; + } + while(*p->xml!='<') + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(i>0 && p->datafunc) + p->datafunc(p->data, data, i); + } + } + else if(*p->xml == '/') + { + i = 0; elementname = ++p->xml; + if (p->xml >= p->xmlend) + return; + while((*p->xml != '>')) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(p->endeltfunc) + p->endeltfunc(p->data, elementname, i); + p->xml++; + } + } + else + { + p->xml++; + } + } +} + +/* the parser must be initialized before calling this function */ +void parsexml(struct xmlparser * parser) +{ + parser->xml = parser->xmlstart; + parser->xmlend = parser->xmlstart + parser->xmlsize; + parseelt(parser); +} + + diff --git a/minixml.h b/minixml.h new file mode 100644 index 0000000..7be06db --- /dev/null +++ b/minixml.h @@ -0,0 +1,36 @@ +/* minimal xml parser + * + * Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __MINIXML_H__ +#define __MINIXML_H__ +#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) + +/* if a callback function pointer is set to NULL, + * the function is not called */ +struct xmlparser { + const char *xmlstart; + const char *xmlend; + const char *xml; /* pointer to current character */ + int xmlsize; + void * data; + void (*starteltfunc) (void *, const char *, int); + void (*endeltfunc) (void *, const char *, int); + void (*datafunc) (void *, const char *, int); + void (*attfunc) (void *, const char *, int, const char *, int); +}; + +/* parsexml() + * the xmlparser structure must be initialized before the call + * the following structure members have to be initialized : + * xmlstart, xmlsize, data, *func + * xml is for internal usage, xmlend is computed automatically */ +void parsexml(struct xmlparser *); + +#endif + diff --git a/options.c b/options.c new file mode 100644 index 0000000..5741eea --- /dev/null +++ b/options.c @@ -0,0 +1,173 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * author: Ryan Wagoner + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include +#include +#include +#include "options.h" +#include "upnpglobalvars.h" + +struct option * ary_options = NULL; +int num_options = 0; + +static const struct { + enum upnpconfigoptions id; + const char * name; +} optionids[] = { + { UPNPEXT_IFNAME, "ext_ifname" }, + { UPNPEXT_IP, "ext_ip" }, + { UPNPLISTENING_IP, "listening_ip" }, + { UPNPPORT, "port" }, + { UPNPBITRATE_UP, "bitrate_up" }, + { UPNPBITRATE_DOWN, "bitrate_down" }, + { UPNPPRESENTATIONURL, "presentation_url" }, + { UPNPNOTIFY_INTERVAL, "notify_interval" }, + { UPNPSYSTEM_UPTIME, "system_uptime" }, + { UPNPPACKET_LOG, "packet_log" }, + { UPNPUUID, "uuid"}, + { UPNPSERIAL, "serial"}, + { UPNPMODEL_NUMBER, "model_number"}, + { UPNPCLEANTHRESHOLD, "clean_ruleset_threshold"}, + { UPNPCLEANINTERVAL, "clean_ruleset_interval"}, +#ifdef ENABLE_NATPMP + { UPNPENABLENATPMP, "enable_natpmp"}, +#endif + { UPNPENABLE, "enable_upnp"}, +#ifdef USE_PF + { UPNPQUEUE, "queue"}, + { UPNPTAG, "tag"}, +#endif +#ifdef PF_ENABLE_FILTER_RULES + { UPNPQUICKRULES, "quickrules" }, +#endif +#ifdef ENABLE_LEASEFILE + { UPNPLEASEFILE, "lease_file"}, +#endif + { UPNPMEDIADIR, "media_dir"}, + { UPNPSECUREMODE, "secure_mode"} +}; + +int +readoptionsfile(const char * fname) +{ + FILE *hfile = NULL; + char buffer[1024]; + char *equals; + char *name; + char *value; + char *t; + int linenum = 0; + int i; + enum upnpconfigoptions id; + + if(!fname || (strlen(fname) == 0)) + return -1; + + memset(buffer, 0, sizeof(buffer)); + +#ifdef DEBUG + printf("Reading configuration from file %s\n", fname); +#endif + + if(!(hfile = fopen(fname, "r"))) + return -1; + + if(ary_options != NULL) + { + free(ary_options); + num_options = 0; + } + + while(fgets(buffer, sizeof(buffer), hfile)) + { + linenum++; + t = strchr(buffer, '\n'); + if(t) + { + *t = '\0'; + t--; + while((t >= buffer) && isspace(*t)) + { + *t = '\0'; + t--; + } + } + + /* skip leading whitespaces */ + name = buffer; + while(isspace(*name)) + name++; + + /* check for comments or empty lines */ + if(name[0] == '#' || name[0] == '\0') continue; + + if(!(equals = strchr(name, '='))) + { + fprintf(stderr, "parsing error file %s line %d : %s\n", + fname, linenum, name); + continue; + } + + /* remove ending whitespaces */ + for(t=equals-1; t>name && isspace(*t); t--) + *t = '\0'; + + *equals = '\0'; + value = equals+1; + + /* skip leading whitespaces */ + while(isspace(*value)) + value++; + + id = UPNP_INVALID; + for(i=0; i +#include +#include +#include +#include +#include +#include + +#include + +#include "upnpglobalvars.h" +#include "metadata.h" +#include "sql.h" +#include "scanner.h" + +int +ends_with(const char * haystack, const char * needle) +{ + const char *found = strcasestr(haystack, needle); + return (found && found[strlen(needle)] == '\0'); +} + +int +is_video(const char * file) +{ + return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") || + ends_with(file, ".ts") || ends_with(file, ".avi")); +} + +int +is_audio(const char * file) +{ + return (ends_with(file, ".mp3") || + ends_with(file, ".m4a") || ends_with(file, ".aac")); +} + +int +is_image(const char * file) +{ + return (ends_with(file, ".jpg") || ends_with(file, ".jpeg")); +} + +long long int +insert_container(const char * tmpTable, const char * item, const char * rootParent, const char *subParent, const char *class, long unsigned int detailID) +{ + char **result; + char *sql; + int cols, rows, ret; + char *zErrMsg = NULL; + int parentID = 0, objectID = 0; + + sql = sqlite3_mprintf("SELECT * from %s where ITEM = '%q' and SUBITEM = '%q'", tmpTable, item, subParent); + ret = sql_get_table(db, sql, &result, &rows, &cols, &zErrMsg); + sqlite3_free(sql); + if( cols ) + { + sscanf(result[4], "%X", &parentID); + sqlite3_free_table(result); + sql = sqlite3_mprintf("SELECT OBJECT_ID, max(ID) from OBJECTS where PARENT_ID = '%s$%X'", rootParent, parentID); + ret = sql_get_table(db, sql, &result, 0, &cols, &zErrMsg); + sqlite3_free(sql); + if( result[2] && (sscanf(rindex(result[2], '$')+1, "%X", &objectID) == 1) ) + { + objectID++; + } + } + else + { + sqlite3_free_table(result); + sql = sqlite3_mprintf("SELECT OBJECT_ID, max(ID) from OBJECTS where PARENT_ID = '%s'", rootParent); + sql_get_table(db, sql, &result, &rows, &cols, &zErrMsg); + sqlite3_free(sql); + if( result[2] && (sscanf(rindex(result[2], '$')+1, "%X", &parentID) == 1) ) + { + parentID++; + } + else + { + parentID = 0; + } + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME) " + "VALUES" + " ('%s$%X', '%s', %lu, 'container.%s', '%q')", + rootParent, parentID, rootParent, detailID, class, item); + ret = sql_exec(db, sql); + sqlite3_free(sql); + sql = sqlite3_mprintf("INSERT into %s values ('%q', '%X', '%q')", tmpTable, item, parentID, subParent); + sql_exec(db, sql); + sqlite3_free(sql); + } + sqlite3_free_table(result); + + return (long long)parentID<<32|objectID; +} + +void +insert_containers(const char * name, const char *path, const char * refID, const char * class, long unsigned int detailID) +{ + char sql_buf[128]; + char *sql; + char **result; + int ret; + int cols, row; + char *zErrMsg = NULL; + long long int container; + int parentID; + int objectID = -1; + + sprintf(sql_buf, "SELECT * from DETAILS where ID = %lu", detailID); + ret = sql_get_table(db, sql_buf, &result, &row, &cols, &zErrMsg); + + if( strstr(class, "imageItem") ) + { + char *date = result[12+cols], *cam = result[16+cols]; + char date_taken[11]; date_taken[10] = '\0'; + static int last_all_objectID = 0; + if( date ) + strncpy(date_taken, date, 10); + if( date ) + { + container = insert_container("DATES", date_taken, "3$12", NULL, "album.photoAlbum", 0); + parentID = container>>32; + objectID = container; + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('3$12$%X$%X', '3$12$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); + } + if( cam && date ) + { + container = insert_container("CAMS", cam, "3$13", NULL, "storageFolder", 0); + parentID = container>>32; + //objectID = container; + char parent[64]; + sprintf(parent, "3$13$%X", parentID); + long long int subcontainer = insert_container("CAMDATE", date_taken, parent, cam, "storageFolder", 0); + int subParentID = subcontainer>>32; + int subObjectID = subcontainer; + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('3$13$%X$%X$%X', '3$13$%X$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, subParentID, subObjectID, parentID, subParentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); + } + /* All Images */ + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('3$11$%X', '3$11', '%s', '%s', %lu, %Q, %Q)", + last_all_objectID++, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); + } + else if( strstr(class, "audioItem") ) + { + char *artist = result[6+cols], *album = result[7+cols], *genre = result[8+cols]; + static char last_artist[1024]; + static int last_artist_parentID, last_artist_objectID; + static char last_album[1024]; + static int last_album_parentID, last_album_objectID; + static char last_genre[1024]; + static int last_genre_parentID, last_genre_objectID; + static int last_all_objectID = 0; + + if( artist ) + { + if( strcmp(artist, last_artist) == 0 ) + { + objectID = ++last_artist_objectID; + parentID = last_artist_parentID; + } + else + { + strcpy(last_artist, artist); + container = insert_container("ARTISTS", artist, "1$6", NULL, "person.musicArtist", 0); + parentID = container>>32; + objectID = container; + last_artist_objectID = objectID; + last_artist_parentID = parentID; + } + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$6$%X$%X', '1$6$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); + } + if( album ) + { + if( strcmp(album, last_album) == 0 ) + { + objectID = ++last_album_objectID; + parentID = last_album_parentID; + } + else + { + strcpy(last_album, album); + container = insert_container("ALBUMS", album, "1$7", NULL, "album.musicAlbum", detailID); + parentID = container>>32; + objectID = container; + last_album_objectID = objectID; + last_album_parentID = parentID; + } + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$7$%X$%X', '1$7$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); + } + if( genre ) + { + if( strcmp(genre, last_genre) == 0 ) + { + objectID = ++last_genre_objectID; + parentID = last_genre_parentID; + } + else + { + strcpy(last_genre, genre); + container = insert_container("GENRES", genre, "1$5", NULL, "genre.musicGenre", 0); + parentID = container>>32; + objectID = container; + last_genre_objectID = objectID; + last_genre_parentID = parentID; + } + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$5$%X$%X', '1$5$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); + } + /* All Music */ + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$4$%X', '1$4', '%s', '%s', %lu, %Q, %Q)", + last_all_objectID++, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); + } + sqlite3_free_table(result); +} + +int +insert_directory(const char * name, const char * path, const char * parentID, int objectID) +{ + char *sql; + int ret, i; + char class[] = "container.storageFolder"; + const char * const base[] = { BROWSEDIR_ID, MUSIC_DIR_ID, VIDEO_DIR_ID, IMAGE_DIR_ID, 0 }; + + for( i=0; base[i]; i++ ) + { + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, CLASS, PATH, NAME) " + "VALUES" + " ('%s%s$%X', '%s%s', '%s', '%q', '%q')", + base[i], parentID, objectID, base[i], parentID, class, path, name); + //DEBUG printf("SQL: %s\n", sql); + ret = sql_exec(db, sql); + sqlite3_free(sql); + } + return -1; +} + +int +insert_file(char * name, const char * path, const char * parentID, int object) +{ + char *sql; + char class[32]; + char objectID[64]; + unsigned long int detailID = 0; + char base[8]; + + static long unsigned int fileno = 0; + printf("Scanned %lu files...\r", fileno++); fflush(stdout); + + sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object); + + if( is_image(name) ) + { + strcpy(base, IMAGE_DIR_ID); + strcpy(class, "item.imageItem"); + detailID = GetImageMetadata(path, name); + } + else if( is_audio(name) ) + { + strcpy(base, MUSIC_DIR_ID); + strcpy(class, "item.audioItem.musicTrack"); + detailID = GetAudioMetadata(path, name); + } + else if( is_video(name) ) + { + strcpy(base, VIDEO_DIR_ID); + strcpy(class, "item.videoItem"); + detailID = GetVideoMetadata(path, name); + } + //DEBUG printf("Got DetailID %lu!\n", detailID); + + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('%s', '%s%s', '%s', %lu, '%q', '%q')", + objectID, BROWSEDIR_ID, parentID, class, detailID, path, name); + //DEBUG printf("SQL: %s\n", sql); + sql_exec(db, sql); + sqlite3_free(sql); + #if 0 + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q', '%q')", + base, parentID, object, base, parentID, objectID, class, detailID, path, name); + //DEBUG printf("SQL: %s\n", sql); + sql_exec(db, sql); + sqlite3_free(sql); + #else + insert_containers(name, path, objectID, class, detailID); + #endif + return -1; +} + +int +create_database(void) +{ + int ret, i; + char sql_buf[512]; + const char * containers[] = { "0","-1", "root", + "1", "0", "Music", + "1$4", "1", "All Music", + "1$5", "1", "Genre", + "1$6", "1", "Artist", + "1$7", "1", "Album", + "1$20", "1", "Folders", + "2", "0", "Video", + "2$8", "2", "All Video", + "2$21", "2", "Folders", + "3", "0", "Pictures", + "3$11", "3", "All Pictures", + "3$12", "3", "Date Taken", + "3$13", "3", "Camera", + "3$22", "3", "Folders", + "64", "0", "Browse Folders", + 0 }; + + sql_exec(db, "pragma temp_store = MEMORY"); + sql_exec(db, "pragma synchronous = OFF;"); + sql_exec(db, "pragma cache_size = 8192;"); + + ret = sql_exec(db, "CREATE TABLE OBJECTS ( " + "ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "OBJECT_ID TEXT NOT NULL, " + "PARENT_ID TEXT NOT NULL, " + "REF_ID TEXT DEFAULT NULL, " + "CLASS TEXT NOT NULL, " + "DETAIL_ID INTEGER DEFAULT NULL, " + "PATH TEXT DEFAULT NULL, " + "NAME TEXT DEFAULT NULL" + ");"); + if( ret != SQLITE_OK ) + goto sql_failed; + ret = sql_exec(db, "CREATE TABLE DETAILS ( " + "ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "SIZE INTEGER, " + "TITLE TEXT, " + "DURATION TEXT, " + "BITRATE INTEGER, " + "SAMPLERATE INTEGER, " + "ARTIST TEXT, " + "ALBUM TEXT, " + "GENRE TEXT, " + "COMMENT TEXT, " + "CHANNELS INTEGER, " + "TRACK INTEGER, " + "DATE DATE, " + "WIDTH TEXT, " + "HEIGHT TEXT, " + "THUMBNAIL BOOL DEFAULT 0, " + "CREATOR TEXT, " + "DLNA_PN TEXT, " + "MIME TEXT" + ");"); + if( ret != SQLITE_OK ) + goto sql_failed; + for( i=0; containers[i]; i=i+3 ) + { + sprintf(sql_buf, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, CLASS, NAME) values ( '%s', '%s', 'container.storageFolder', '%s')", + containers[i], containers[i+1], containers[i+2]); + ret = sql_exec(db, sql_buf); + if( ret != SQLITE_OK ) + goto sql_failed; + } + sql_exec(db, "create TEMP TABLE ARTISTS (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE ALBUMS (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE GENRES (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE DATES (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE CAMS (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE CAMDATE (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + + sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);"); + sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); + sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); + sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); + sql_exec(db, "create INDEX IDX_OBJECTS_PATH ON OBJECTS(PATH);"); + sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); + + +sql_failed: + if( ret != SQLITE_OK ) + fprintf(stderr, "Error creating SQLite3 database!\n"); + return (ret != SQLITE_OK); +} + +int +filter_media(const struct dirent *d) +{ + struct stat entry; + return ( (*d->d_name != '.') && + (stat(d->d_name, &entry) == 0) && + (S_ISDIR(entry.st_mode) || + (S_ISREG(entry.st_mode) && + (is_image(d->d_name) || + is_audio(d->d_name) || + is_video(d->d_name) + ) + )) ); +} + +void +ScanDirectory(const char * dir, const char * parent) +{ + struct dirent **namelist; + struct stat entry; + int n, i; + char parent_id[PATH_MAX]; + char full_path[PATH_MAX]; + char * name; + + if( !parent ) + { + if( create_database() != 0 ) + { + fprintf(stderr, "Error creating database!\n"); + return; + } + } + + setlocale(LC_COLLATE, ""); + if( chdir(dir) != 0 ) + return; + n = scandir(".", &namelist, filter_media, alphasort); + if (n < 0) { + fprintf(stderr, "Error scanning %s [scandir]\n", dir); + return; + } + for (i=0; i < n; i++) { + if( stat(namelist[i]->d_name, &entry) == 0 ) + { + name = NULL; + sprintf(full_path, "%s/%s", dir, namelist[i]->d_name); + if( index(namelist[i]->d_name, '&') ) + { + name = modifyString(strdup(namelist[i]->d_name), "&", "&amp;", 0); + } + if( S_ISDIR(entry.st_mode) ) + { + insert_directory(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i); + sprintf(parent_id, "%s$%X", (parent ? parent:""), i); + ScanDirectory(full_path, parent_id); + } + else + { + insert_file(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i); + } + } + if( name ) + free(name); + free(namelist[i]); + } + free(namelist); + chdir(".."); +} diff --git a/scanner.h b/scanner.h new file mode 100644 index 0000000..0e4747c --- /dev/null +++ b/scanner.h @@ -0,0 +1,21 @@ +/* Media file scanner + * + * Project : minidlna + * Website : http://sourceforge.net/projects/minidlna/ + * Author : Justin Maggard + * Copyright (c) 2008 Justin Maggard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __SCANNER_H__ +#define __SCANNER_H__ + +#define BROWSEDIR_ID "64" +#define MUSIC_DIR_ID "1$20" +#define VIDEO_DIR_ID "2$21" +#define IMAGE_DIR_ID "3$22" + +void +ScanDirectory(const char * dir, const char * parent); + +#endif diff --git a/sql.c b/sql.c new file mode 100644 index 0000000..9883987 --- /dev/null +++ b/sql.c @@ -0,0 +1,53 @@ +/* MiniDLNA media server + * Copyright (C) 2008 Justin Maggard + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include "sql.h" + +int +sql_exec(sqlite3 * db, const char * sql) +{ + int ret; + char *errMsg = NULL; + //DEBUG printf("SQL: %s\n", sql); + + //ret = sqlite3_exec(db, sql, 0, 0, &errMsg); + ret = sqlite3_exec(db, sql, 0, 0, &errMsg); + if( ret != SQLITE_OK ) + { + fprintf(stderr, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); + if (errMsg) + sqlite3_free(errMsg); + } + return ret; +} + +int +sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn, char **pzErrmsg) +{ + //DEBUG printf("SQL: %s\n", zSql); + int ret; + ret = sqlite3_get_table(db, zSql, pazResult, pnRow, pnColumn, pzErrmsg); + if( ret != SQLITE_OK ) + { + fprintf(stderr, "SQL ERROR [%s]\n%s\n", *pzErrmsg, zSql); + if (*pzErrmsg) + sqlite3_free(*pzErrmsg); + } + return ret; +} + diff --git a/sql.h b/sql.h new file mode 100644 index 0000000..7e15511 --- /dev/null +++ b/sql.h @@ -0,0 +1,21 @@ +/* Reusable SQLite3 wrapper functions + * + * Project : minidlna + * Website : http://sourceforge.net/projects/minidlna/ + * Author : Justin Maggard + * Copyright (c) 2008 Justin Maggard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __SQL_H__ +#define __SQL_H__ + +#include + +int +sql_exec(sqlite3 * db, const char * sql); + +int +sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn, char **pzErrmsg); + +#endif diff --git a/testupnpdescgen.c b/testupnpdescgen.c new file mode 100644 index 0000000..d072d7a --- /dev/null +++ b/testupnpdescgen.c @@ -0,0 +1,121 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include + +#include "config.h" +#include "upnpdescgen.h" + +char uuidvalue[] = "uuid:12345678-0000-0000-0000-00000000abcd"; +char serialnumber[] = "12345678"; +char modelnumber[] = "1"; +char presentationurl[] = "http://192.168.0.1:8080/"; + +char * use_ext_ip_addr = NULL; +const char * ext_if_name = "eth0"; + +int getifaddr(const char * ifname, char * buf, int len) +{ + strncpy(buf, "1.2.3.4", len); + return 0; +} + +int upnp_get_portmapping_number_of_entries() +{ + return 42; +} + +/* To be improved */ +int +xml_pretty_print(const char * s, int len, FILE * f) +{ + int n = 0, i; + int elt_close = 0; + int c, indent = 0; + while(len > 0) + { + c = *(s++); len--; + switch(c) + { + case '<': + if(len>0 && *s == '/') + elt_close++; + else if(len>0 && *s == '?') + elt_close = 1; + else + elt_close = 0; + if(elt_close!=1) + { + if(elt_close > 1) + indent--; + fputc('\n', f); n++; + for(i=indent; i>0; i--) + fputc(' ', f); + n += indent; + } + fputc(c, f); n++; + break; + case '>': + fputc(c, f); n++; + if(elt_close==1) + { + /*fputc('\n', f); n++; */ + //elt_close = 0; + if(indent > 0) + indent--; + } + else if(elt_close == 0) + indent++; + break; + default: + fputc(c, f); n++; + } + } + return n; +} + +/* stupid test */ +const char * str1 = "Prefix123String"; +const char * str2 = "123String"; + +void stupid_test() +{ + printf("str1:'%s' str2:'%s'\n", str1, str2); + printf("str1:%p str2:%p str2-str1:%ld\n", str1, str2, (long)(str2-str1)); +} + +/* main */ + +int +main(int argc, char * * argv) +{ + char * rootDesc; + int rootDescLen; + char * s; + int l; + rootDesc = genRootDesc(&rootDescLen); + xml_pretty_print(rootDesc, rootDescLen, stdout); + free(rootDesc); + printf("\n-------------\n"); + s = genContentDirectory(&l); + xml_pretty_print(s, l, stdout); + free(s); + printf("\n-------------\n"); + s = genConnectionManager(&l); + xml_pretty_print(s, l, stdout); + free(s); + printf("\n-------------\n"); +#ifdef ENABLE_EVENTS +#endif +/* + stupid_test(); +*/ + return 0; +} + diff --git a/upnpdescgen.c b/upnpdescgen.c new file mode 100644 index 0000000..b937b78 --- /dev/null +++ b/upnpdescgen.c @@ -0,0 +1,933 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include + +#include + +#include "config.h" +#ifdef ENABLE_EVENTS +#include "getifaddr.h" +#endif +#include "upnpdescgen.h" +#include "miniupnpdpath.h" +#include "upnpglobalvars.h" +#include "upnpdescstrings.h" + +static const char * const upnptypes[] = +{ + "string", + "boolean", + "ui2", + "ui4", + "i4", + "uri", + "int", + "bin.base64" +}; + +static const char * const upnpdefaultvalues[] = +{ + 0, + "Unconfigured" +}; + +static const char * const upnpallowedvalues[] = +{ + 0, /* 0 */ + "DSL", /* 1 */ + "POTS", + "Cable", + "Ethernet", + 0, + "Up", /* 6 */ + "Down", + "Initializing", + "Unavailable", + 0, + "TCP", /* 11 */ + "UDP", + 0, + "Unconfigured", /* 14 */ + "IP_Routed", + "IP_Bridged", + 0, + "Unconfigured", /* 18 */ + "Connecting", + "Connected", + "PendingDisconnect", + "Disconnecting", + "Disconnected", + 0, + "ERROR_NONE", /* 25 */ + 0, + "OK", /* 27 */ + "ContentFormatMismatch", + "InsufficientBandwidth", + "UnreliableChannel", + "Unknown", + 0, + "Input", /* 33 */ + "Output", + 0, + "BrowseMetadata", /* 36 */ + "BrowseDirectChildren", + 0, + "COMPLETED", /* 39 */ + "ERROR", + "IN_PROGRESS", + "STOPPED", + 0, + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," /* 44 */ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," + "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," + "http-get:*:audio/x-ms-wma:*," + "http-get:*:audio/wav:*," + "http-get:*:audio/mp4:*," + "http-get:*:audio/x-aiff:*," + "http-get:*:audio/x-flac:*," + "http-get:*:application/ogg:*," + "http-get:*:image/jpeg:*," + "http-get:*:image/gif:*," + "http-get:*:audio/x-mpegurl:*," + "http-get:*:video/mpeg:*," + "http-get:*:video/x-msvideo:*," + "http-get:*:video/avi:*," + "http-get:*:video/mpeg2:*," + "http-get:*:video/dvd:*," + "http-get:*:video/x-ms-wmv:*", + 0, + "", /* 46 */ + 0 +}; + +static const char xmlver[] = + "\r\n"; +static const char root_service[] = + "scpd xmlns=\"urn:schemas-upnp-org:service-1-0\""; +static const char root_device[] = + "root xmlns=\"urn:schemas-upnp-org:device-1-0\""; + +/* root Description of the UPnP Device + * fixed to match UPnP_IGD_InternetGatewayDevice 1.0.pdf + * presentationURL is only "recommended" but the router doesn't appears + * in "Network connections" in Windows XP if it is not present. */ +static const struct XMLElt rootDesc[] = +{ + {root_device, INITHELPER(1,2)}, + {"specVersion", INITHELPER(3,2)}, + {"device", INITHELPER(5,13)}, + {"/major", "1"}, + {"/minor", "0"}, + {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"}, + {"/friendlyName", ROOTDEV_FRIENDLYNAME}, /* required */ + {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ + {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ + {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ + {"/modelName", ROOTDEV_MODELNAME}, /* required */ + {"/modelNumber", modelnumber}, + {"/modelURL", ROOTDEV_MODELURL}, + {"/serialNumber", serialnumber}, + {"/UDN", uuidvalue}, /* required */ + {"/dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"", "DMS-1.50"}, + {"/presentationURL", presentationurl}, /* recommended */ + {"serviceList", INITHELPER(18,3)}, + {"service", INITHELPER(21,5)}, + {"service", INITHELPER(26,5)}, + {"service", INITHELPER(31,5)}, + {"/serviceType", "urn:schemas-upnp-org:service:ContentDirectory:1"}, + {"/serviceId", "urn:upnp-org:serviceId:ContentDirectory"}, + {"/controlURL", CONTENTDIRECTORY_CONTROLURL}, + {"/eventSubURL", CONTENTDIRECTORY_EVENTURL}, + {"/SCPDURL", CONTENTDIRECTORY_PATH}, + {"/serviceType", "urn:schemas-upnp-org:service:ConnectionManager:1"}, + {"/serviceId", "urn:upnp-org:serviceId:ConnectionManager"}, + {"/controlURL", CONNECTIONMGR_CONTROLURL}, + {"/eventSubURL", CONNECTIONMGR_EVENTURL}, + {"/SCPDURL", CONNECTIONMGR_PATH}, + {"/serviceType", "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"}, + {"/serviceId", "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar"}, + {"/controlURL", X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL}, + {"/eventSubURL", X_MS_MEDIARECEIVERREGISTRAR_EVENTURL}, + {"/SCPDURL", X_MS_MEDIARECEIVERREGISTRAR_PATH}, + {0, 0} +}; + +static const struct argument AddPortMappingArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 1, 13}, + {NULL, 1, 15}, + {NULL, 1, 9}, + {NULL, 1, 16}, + {NULL, 1, 10}, + {NULL, 0, 0} +}; + +static const struct argument GetExternalIPAddressArgs[] = +{ + {NULL, 2, 7}, + {NULL, 0, 0} +}; + +static const struct argument DeletePortMappingArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 0, 0} +}; + +static const struct argument SetConnectionTypeArgs[] = +{ + {NULL, 1, 0}, + {NULL, 0, 0} +}; + +static const struct argument GetConnectionTypeInfoArgs[] = +{ + {NULL, 2, 0}, + {NULL, 2, 1}, + {NULL, 0, 0} +}; + +static const struct argument GetStatusInfoArgs[] = +{ + {NULL, 2, 2}, + {NULL, 2, 4}, + {NULL, 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetNATRSIPStatusArgs[] = +{ + {NULL, 2, 5}, + {NULL, 2, 6}, + {NULL, 0, 0} +}; + +static const struct argument GetGenericPortMappingEntryArgs[] = +{ + {NULL, 1, 8}, + {NULL, 2, 11}, + {NULL, 2, 12}, + {NULL, 2, 14}, + {NULL, 2, 13}, + {NULL, 2, 15}, + {NULL, 2, 9}, + {NULL, 2, 16}, + {NULL, 2, 10}, + {NULL, 0, 0} +}; + +static const struct argument GetSpecificPortMappingEntryArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 2, 13}, + {NULL, 2, 15}, + {NULL, 2, 9}, + {NULL, 2, 16}, + {NULL, 2, 10}, + {NULL, 0, 0} +}; + +/* For ConnectionManager */ +static const struct argument GetProtocolInfoArgs[] = +{ + {"Source", 2, 0}, + {"Sink", 2, 1}, + {NULL, 0, 0} +}; + +static const struct argument PrepareForConnectionArgs[] = +{ + {"RemoteProtocolInfo", 1, 6}, + {"PeerConnectionManager", 1, 4}, + {"PeerConnectionID", 1, 7}, + {"Direction", 1, 5}, + {"ConnectionID", 2, 7}, + {"AVTransportID", 2, 8}, + {"RcsID", 2, 9}, + {NULL, 0, 0} +}; + +static const struct argument ConnectionCompleteArgs[] = +{ + {"ConnectionID", 1, 7}, + {NULL, 0, 0} +}; + +static const struct argument GetCurrentConnectionIDsArgs[] = +{ + {"ConnectionIDs", 2, 2}, + {NULL, 0, 0} +}; + +static const struct argument GetCurrentConnectionInfoArgs[] = +{ + {"ConnectionID", 1, 7}, + {"RcsID", 2, 9}, + {"AVTransportID", 2, 8}, + {"ProtocolInfo", 2, 6}, + {"PeerConnectionManager", 2, 4}, + {"PeerConnectionID", 2, 7}, + {"Direction", 2, 5}, + {"Status", 2, 3}, + {NULL, 0, 0} +}; + +static const struct action ConnectionManagerActions[] = +{ + {"GetProtocolInfo", GetProtocolInfoArgs}, /* R */ + //OPTIONAL {"PrepareForConnection", PrepareForConnectionArgs}, /* R */ + //OPTIONAL {"ConnectionComplete", ConnectionCompleteArgs}, /* R */ + {"GetCurrentConnectionIDs", GetCurrentConnectionIDsArgs}, /* R */ + {"GetCurrentConnectionInfo", GetCurrentConnectionInfoArgs}, /* R */ + {0, 0} +}; + +static const struct stateVar ConnectionManagerVars[] = +{ + {"SourceProtocolInfo", 1<<7, 0, 44, 44}, /* required */ + {"SinkProtocolInfo", 1<<7, 0}, /* required */ + {"CurrentConnectionIDs", 1<<7, 0}, /* required */ + {"A_ARG_TYPE_ConnectionStatus", 0, 0, 27}, /* required */ + {"A_ARG_TYPE_ConnectionManager", 0, 0}, /* required */ + {"A_ARG_TYPE_Direction", 0, 0, 33}, /* required */ + {"A_ARG_TYPE_ProtocolInfo", 0, 0}, /* required */ + {"A_ARG_TYPE_ConnectionID", 4, 0}, /* required */ + {"A_ARG_TYPE_AVTransportID", 4, 0}, /* required */ + {"A_ARG_TYPE_RcsID", 4, 0}, /* required */ + {0, 0} +}; + +static const struct argument GetSearchCapabilitiesArgs[] = +{ + {"SearchCaps", 2, 16}, + {0, 0} +}; + +static const struct argument GetSortCapabilitiesArgs[] = +{ + {"SortCaps", 2, 17}, + {0, 0} +}; + +static const struct argument GetSystemUpdateIDArgs[] = +{ + {"Id", 2, 18}, + {0, 0} +}; + +static const struct argument BrowseArgs[] = +{ + {"ObjectID", 1, 1}, + {"BrowseFlag", 1, 4}, + {"Filter", 1, 5}, + {"StartingIndex", 1, 7}, + {"RequestedCount", 1, 8}, + {"SortCriteria", 1, 6}, + {"Result", 2, 2}, + {"NumberReturned", 2, 8}, + {"TotalMatches", 2, 8}, + {"UpdateID", 2, 9}, + {0, 0} +}; + +static const struct argument SearchArgs[] = +{ + {"ContainerID", 1, 1}, + {"SearchCriteria", 1, 3}, + {"Filter", 1, 5}, + {"StartingIndex", 1, 7}, + {"RequestedCount", 1, 8}, + {"SortCriteria", 1, 6}, + {"Result", 2, 2}, + {"NumberReturned", 2, 8}, + {"TotalMatches", 2, 8}, + {"UpdateID", 2, 9}, + {0, 0} +}; + +static const struct action ContentDirectoryActions[] = +{ + {"GetSearchCapabilities", GetSearchCapabilitiesArgs}, /* R */ + {"GetSortCapabilities", GetSortCapabilitiesArgs}, /* R */ + {"GetSystemUpdateID", GetSystemUpdateIDArgs}, /* R */ + {"Browse", BrowseArgs}, /* R */ + {"Search", SearchArgs}, /* O */ +#if 0 // Not implementing optional features yet... + {"CreateObject", CreateObjectArgs}, /* O */ + {"DestroyObject", DestroyObjectArgs}, /* O */ + {"UpdateObject", UpdateObjectArgs}, /* O */ + {"ImportResource", ImportResourceArgs}, /* O */ + {"ExportResource", ExportResourceArgs}, /* O */ + {"StopTransferResource", StopTransferResourceArgs}, /* O */ + {"GetTransferProgress", GetTransferProgressArgs}, /* O */ + {"DeleteResource", DeleteResourceArgs}, /* O */ + {"CreateReference", CreateReferenceArgs}, /* O */ +#endif + {0, 0} +}; + +static const struct stateVar ContentDirectoryVars[] = +{ + {"TransferIDs", 1<<7, 0, 46, 46}, /* 0 */ + {"A_ARG_TYPE_ObjectID", 0, 0}, + {"A_ARG_TYPE_Result", 0, 0}, + {"A_ARG_TYPE_SearchCriteria", 0, 0}, + {"A_ARG_TYPE_BrowseFlag", 0, 0, 36}, + /* Allowed Values : BrowseMetadata / BrowseDirectChildren */ + {"A_ARG_TYPE_Filter", 0, 0}, /* 5 */ + {"A_ARG_TYPE_SortCriteria", 0, 0}, + {"A_ARG_TYPE_Index", 3, 0}, + {"A_ARG_TYPE_Count", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + //JM{"A_ARG_TYPE_TransferID", 3, 0}, /* 10 */ + //JM{"A_ARG_TYPE_TransferStatus", 0, 0, 39}, + /* Allowed Values : COMPLETED / ERROR / IN_PROGRESS / STOPPED */ + //JM{"A_ARG_TYPE_TransferLength", 0, 0}, + //JM{"A_ARG_TYPE_TransferTotal", 0, 0}, + //JM{"A_ARG_TYPE_TagValueList", 0, 0}, + //JM{"A_ARG_TYPE_URI", 5, 0}, /* 15 */ + {"SearchCapabilities", 0, 0}, + {"SortCapabilities", 0, 0}, + {"SystemUpdateID", 3|0x80, 0, 46, 46}, + //{"ContainerUpdateIDs", 0, 0}, + {0, 0} +}; + +static const struct argument GetIsAuthorizedArgs[] = +{ + {"DeviceID", 1, 0}, + {"Result", 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetIsValidatedArgs[] = +{ + {"DeviceID", 1, 0}, + {"Result", 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetRegisterDeviceArgs[] = +{ + {"RegistrationReqMsg", 1, 1}, + {"RegistrationRespMsg", 2, 2}, + {NULL, 0, 0} +}; + +static const struct action X_MS_MediaReceiverRegistrarActions[] = +{ + {"IsAuthorized", GetIsAuthorizedArgs}, /* R */ + {"IsValidated", GetIsValidatedArgs}, /* R */ + {"RegisterDevice", GetRegisterDeviceArgs}, /* R */ + {0, 0} +}; + +static const struct stateVar X_MS_MediaReceiverRegistrarVars[] = +{ + {"A_ARG_TYPE_DeviceID", 0, 0}, + {"A_ARG_TYPE_RegistrationReqMsg", 7, 0}, + {"A_ARG_TYPE_RegistrationRespMsg", 7, 0}, + {"A_ARG_TYPE_Result", 6, 0}, + {"AuthorizationDeniedUpdateID", (1<<7)|3, 0}, + {"AuthorizationGrantedUpdateID", (1<<7)|3, 0}, + {"ValidationRevokedUpdateID", (1<<7)|3, 0}, + {"ValidationSucceededUpdateID", (1<<7)|3, 0}, + {0, 0} +}; + +/* WANCfg.xml */ +/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ + +static const struct argument GetCommonLinkPropertiesArgs[] = +{ + {NULL, 2, 0}, + {NULL, 2, 1}, + {NULL, 2, 2}, + {NULL, 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalBytesSentArgs[] = +{ + {NULL, 2, 4}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalBytesReceivedArgs[] = +{ + {NULL, 2, 5}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalPacketsSentArgs[] = +{ + {NULL, 2, 6}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalPacketsReceivedArgs[] = +{ + {NULL, 2, 7}, + {NULL, 0, 0} +}; + +static const struct action WANCfgActions[] = +{ + {"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */ + {"GetTotalBytesSent", GetTotalBytesSentArgs}, /* optional */ + {"GetTotalBytesReceived", GetTotalBytesReceivedArgs}, /* optional */ + {"GetTotalPacketsSent", GetTotalPacketsSentArgs}, /* optional */ + {"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */ + {0, 0} +}; + +/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ +static const struct stateVar WANCfgVars[] = +{ + {"WANAccessType", 0, 0, 1}, + /* Allowed Values : DSL / POTS / Cable / Ethernet + * Default value : empty string */ + {"Layer1UpstreamMaxBitRate", 3, 0}, + {"Layer1DownstreamMaxBitRate", 3, 0}, + {"PhysicalLinkStatus", 0|0x80, 0, 6, 6}, + /* allowed values : + * Up / Down / Initializing (optional) / Unavailable (optionnal) + * no Default value + * Evented */ + {"TotalBytesSent", 3, 0}, /* Optional */ + {"TotalBytesReceived", 3, 0}, /* Optional */ + {"TotalPacketsSent", 3, 0}, /* Optional */ + {"TotalPacketsReceived", 3, 0},/* Optional */ + /*{"MaximumActiveConnections", 2, 0}, // allowed Range value // OPTIONAL */ + {0, 0} +}; + +static const struct serviceDesc scpdWANCfg = +{ WANCfgActions, WANCfgVars }; + +#ifdef ENABLE_L3F_SERVICE +/* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */ +static const struct argument SetDefaultConnectionServiceArgs[] = +{ + {NULL, 1, 0}, /* in */ + {NULL, 0, 0} +}; + +static const struct argument GetDefaultConnectionServiceArgs[] = +{ + {NULL, 2, 0}, /* out */ + {0, 0} +}; + +static const struct action L3FActions[] = +{ + {"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */ + {"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */ + {0, 0} +}; + +static const struct stateVar L3FVars[] = +{ + {"DefaultConnectionService", 0|0x80, 0, 0, 255}, /* Required */ + {0, 0} +}; + +static const struct serviceDesc scpdL3F = +{ L3FActions, L3FVars }; +#endif + +static const struct serviceDesc scpdContentDirectory = +{ ContentDirectoryActions, ContentDirectoryVars }; +//{ ContentDirectoryActions, ContentDirectoryVars }; + +static const struct serviceDesc scpdConnectionManager = +{ ConnectionManagerActions, ConnectionManagerVars }; + +static const struct serviceDesc scpdX_MS_MediaReceiverRegistrar = +{ X_MS_MediaReceiverRegistrarActions, X_MS_MediaReceiverRegistrarVars }; + +/* strcat_str() + * concatenate the string and use realloc to increase the + * memory buffer if needed. */ +static char * +strcat_str(char * str, int * len, int * tmplen, const char * s2) +{ + int s2len; + s2len = (int)strlen(s2); + if(*tmplen <= (*len + s2len)) + { + if(s2len < 256) + *tmplen += 256; + else + *tmplen += s2len + 1; + str = (char *)realloc(str, *tmplen); + } + /*strcpy(str + *len, s2); */ + memcpy(str + *len, s2, s2len + 1); + *len += s2len; + return str; +} + +/* strcat_char() : + * concatenate a character and use realloc to increase the + * size of the memory buffer if needed */ +static char * +strcat_char(char * str, int * len, int * tmplen, char c) +{ + if(*tmplen <= (*len + 1)) + { + *tmplen += 256; + str = (char *)realloc(str, *tmplen); + } + str[*len] = c; + (*len)++; + return str; +} + +/* iterative subroutine using a small stack + * This way, the progam stack usage is kept low */ +static char * +genXML(char * str, int * len, int * tmplen, + const struct XMLElt * p) +{ + unsigned short i, j, k; + int top; + const char * eltname, *s; + char c; + char element[64]; + struct { + unsigned short i; + unsigned short j; + const char * eltname; + } pile[16]; /* stack */ + top = -1; + i = 0; /* current node */ + j = 1; /* i + number of nodes*/ + for(;;) + { + eltname = p[i].eltname; + if(!eltname) + return str; + if(eltname[0] == '/') + { + /*printf("<%s>%s<%s>\n", eltname+1, p[i].data, eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname+1); + str = strcat_char(str, len, tmplen, '>'); + str = strcat_str(str, len, tmplen, p[i].data); + str = strcat_char(str, len, tmplen, '<'); + sscanf(eltname, "%s", element); + str = strcat_str(str, len, tmplen, element); + str = strcat_char(str, len, tmplen, '>'); + for(;;) + { + if(top < 0) + return str; + i = ++(pile[top].i); + j = pile[top].j; + /*printf(" pile[%d]\t%d %d\n", top, i, j); */ + if(i==j) + { + /*printf("\n", pile[top].eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_char(str, len, tmplen, '/'); + s = pile[top].eltname; + for(c = *s; c > ' '; c = *(++s)) + str = strcat_char(str, len, tmplen, c); + str = strcat_char(str, len, tmplen, '>'); + top--; + } + else + break; + } + } + else + { + /*printf("<%s>\n", eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname); + str = strcat_char(str, len, tmplen, '>'); + k = i; + /*i = p[k].index; */ + /*j = i + p[k].nchild; */ + i = (unsigned)p[k].data & 0xffff; + j = i + ((unsigned)p[k].data >> 16); + top++; + /*printf(" +pile[%d]\t%d %d\n", top, i, j); */ + pile[top].i = i; + pile[top].j = j; + pile[top].eltname = eltname; + } + } +} + +/* genRootDesc() : + * - Generate the root description of the UPnP device. + * - the len argument is used to return the length of + * the returned string. + * - tmp_uuid argument is used to build the uuid string */ +char * +genRootDesc(int * len) +{ + char * str; + int tmplen; + tmplen = 2048; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; +#if 1 + * len = strlen(xmlver); + /*strcpy(str, xmlver); */ + memcpy(str, xmlver, *len + 1); + str = genXML(str, len, &tmplen, rootDesc); + str[*len] = '\0'; + return str; +#else +char *ret = calloc(1, 8192); +sprintf(ret, "\r\n" +"10urn:schemas-upnp-org:device:MediaServer:1MiniDLNA (MaggardMachine2)NETGEARhttp://www.netgear.comNETGEAR ReadyNAS NVReadyNASNVhttp://www.netgear.comuuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6dDMS-1.50\r\n" +//"10urn:schemas-upnp-org:device:MediaServer:1MiniDLNA (MaggardMachine2)NETGEARhttp://www.netgear.comNETGEAR ReadyNAS NVReadyNASNVhttp://www.netgear.comuuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6dav-upload,image-upload,create-child-container,audio-uploadDMS-1.50\r\n" +"urn:schemas-upnp-org:service:ConnectionManager:1urn:upnp-org:serviceId:ConnectionManager/ConnectionMgr.xml/ctl/ConnectionMgr/evt/ConnectionMgrurn:schemas-upnp-org:service:ContentDirectory:1urn:upnp-org:serviceId:ContentDirectory/ContentDir.xml/ctl/ContentDir/evt/ContentDir"); +//"urn:schemas-upnp-org:service:ConnectionManager:1urn:upnp-org:serviceId:ConnectionManager/ConnectionMgr.xml/ctl/ConnectionMgr/evt/ConnectionMgrurn:schemas-upnp-org:service:ContentDirectory:1urn:upnp-org:serviceId:ContentDirectory/ContentDir.xml/ctl/ContentDir/evt/ContentDirurn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1763f907c-8cfb-11dd-a382-c9c0ad9eae41/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/control/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/events/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d\r\n"); + * len = strlen(ret); +return ret; +#endif +} + +/* genServiceDesc() : + * Generate service description with allowed methods and + * related variables. */ +static char * +genServiceDesc(int * len, const struct serviceDesc * s) +{ + int i, j; + const struct action * acts; + const struct stateVar * vars; + const struct argument * args; + const char * p; + char * str; + int tmplen; + tmplen = 2048; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + /*strcpy(str, xmlver); */ + *len = strlen(xmlver); + memcpy(str, xmlver, *len + 1); + + acts = s->actionList; + vars = s->serviceStateTable; + + str = strcat_char(str, len, &tmplen, '<'); + str = strcat_str(str, len, &tmplen, root_service); + str = strcat_char(str, len, &tmplen, '>'); + + str = strcat_str(str, len, &tmplen, + "10"); + + i = 0; + str = strcat_str(str, len, &tmplen, ""); + while(acts[i].name) + { + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, acts[i].name); + str = strcat_str(str, len, &tmplen, ""); + /* argument List */ + args = acts[i].args; + if(args) + { + str = strcat_str(str, len, &tmplen, ""); + j = 0; + while(args[j].dir) + { + //JM str = strcat_str(str, len, &tmplen, "New"); + str = strcat_str(str, len, &tmplen, ""); + p = vars[args[j].relatedVar].name; + if(0 == memcmp(p, "PortMapping", 11) + && 0 != memcmp(p + 11, "Description", 11)) { + if(0 == memcmp(p + 11, "NumberOfEntries", 15)) + str = strcat_str(str, len, &tmplen, "PortMappingIndex"); + else + str = strcat_str(str, len, &tmplen, p + 11); + } else { + str = strcat_str(str, len, &tmplen, (args[j].name ? args[j].name : p)); + } + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, args[j].dir==1?"in":"out"); + str = strcat_str(str, len, &tmplen, + ""); + str = strcat_str(str, len, &tmplen, p); + str = strcat_str(str, len, &tmplen, + ""); + j++; + } + str = strcat_str(str, len, &tmplen,""); + } + str = strcat_str(str, len, &tmplen, ""); + /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ + i++; + } + str = strcat_str(str, len, &tmplen, ""); + i = 0; + while(vars[i].name) + { + str = strcat_str(str, len, &tmplen, + ""); + str = strcat_str(str, len, &tmplen, vars[i].name); + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]); + str = strcat_str(str, len, &tmplen, ""); + if(vars[i].iallowedlist) + { + str = strcat_str(str, len, &tmplen, ""); + for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++) + { + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]); + str = strcat_str(str, len, &tmplen, ""); + } + str = strcat_str(str, len, &tmplen, ""); + } + /*if(vars[i].defaultValue) */ + if(vars[i].idefault) + { + str = strcat_str(str, len, &tmplen, ""); + /*str = strcat_str(str, len, &tmplen, vars[i].defaultValue); */ + str = strcat_str(str, len, &tmplen, upnpdefaultvalues[vars[i].idefault]); + str = strcat_str(str, len, &tmplen, ""); + } + str = strcat_str(str, len, &tmplen, ""); + /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ + i++; + } + str = strcat_str(str, len, &tmplen, ""); + str[*len] = '\0'; + return str; +} + +/* genContentDirectory() : + * Generate the ContentDirectory xml description */ +char * +genContentDirectory(int * len) +{ + return genServiceDesc(len, &scpdContentDirectory); +} + +/* genConnectionManager() : + * Generate the ConnectionManager xml description */ +char * +genConnectionManager(int * len) +{ + return genServiceDesc(len, &scpdConnectionManager); +} + +/* genX_MS_MediaReceiverRegistrar() : + * Generate the X_MS_MediaReceiverRegistrar xml description */ +char * +genX_MS_MediaReceiverRegistrar(int * len) +{ + return genServiceDesc(len, &scpdX_MS_MediaReceiverRegistrar); +} + +#ifdef ENABLE_EVENTS +static char * +genEventVars(int * len, const struct serviceDesc * s, const char * servns) +{ + const struct stateVar * v; + char * str; + int tmplen; + tmplen = 512; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + *len = 0; + v = s->serviceStateTable; + str = strcat_str(str, len, &tmplen, ""); + while(v->name) { + if(v->itype & 0x80) { + str = strcat_str(str, len, &tmplen, "<"); + str = strcat_str(str, len, &tmplen, v->name); + str = strcat_str(str, len, &tmplen, ">"); + //printf("", v->name); + switch(v->ieventvalue) { + case 0: + break; + case 255: /* Magical values should go around here */ + break; + default: + str = strcat_str(str, len, &tmplen, upnpallowedvalues[v->ieventvalue]); + //printf("%s", upnpallowedvalues[v->ieventvalue]); + } + str = strcat_str(str, len, &tmplen, "name); + str = strcat_str(str, len, &tmplen, ">"); + //printf("\n", v->name); + } + v++; + } + str = strcat_str(str, len, &tmplen, ""); + //printf("\n"); + //printf("\n"); + //printf("%d\n", tmplen); + str[*len] = '\0'; + return str; +} + +char * +getVarsContentDirectory(int * l) +{ + return genEventVars(l, + &scpdContentDirectory, + "urn:schemas-upnp-org:service:ContentDirectory:1"); +} + +char * +getVarsConnectionManager(int * l) +{ + return genEventVars(l, + &scpdConnectionManager, + "urn:schemas-upnp-org:service:ConnectionManager:1"); +} + +char * +getVarsX_MS_MediaReceiverRegistrar(int * l) +{ + return genEventVars(l, + &scpdX_MS_MediaReceiverRegistrar, + "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"); +} + +#endif diff --git a/upnpdescgen.c.dlna b/upnpdescgen.c.dlna new file mode 100644 index 0000000..c17b3c9 --- /dev/null +++ b/upnpdescgen.c.dlna @@ -0,0 +1,944 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include + +#include + +#include "config.h" +#ifdef ENABLE_EVENTS +#include "getifaddr.h" +#include "upnpredirect.h" +#endif +#include "upnpdescgen.h" +#include "miniupnpdpath.h" +#include "upnpglobalvars.h" +#include "upnpdescstrings.h" + +static const char * const upnptypes[] = +{ + "string", + "boolean", + "ui2", + "ui4", + "i4", + "uri" +}; + +static const char * const upnpdefaultvalues[] = +{ + 0, + "Unconfigured" +}; + +static const char * const upnpallowedvalues[] = +{ + 0, /* 0 */ + "DSL", /* 1 */ + "POTS", + "Cable", + "Ethernet", + 0, + "Up", /* 6 */ + "Down", + "Initializing", + "Unavailable", + 0, + "TCP", /* 11 */ + "UDP", + 0, + "Unconfigured", /* 14 */ + "IP_Routed", + "IP_Bridged", + 0, + "Unconfigured", /* 18 */ + "Connecting", + "Connected", + "PendingDisconnect", + "Disconnecting", + "Disconnected", + 0, + "ERROR_NONE", /* 25 */ + 0, + "OK", /* 27 */ + "ContentFormatMismatch", + "InsufficientBandwidth", + "UnreliableChannel", + "Unknown", + 0, + "Input", /* 33 */ + "Output", + 0, + "BrowseMetadata", /* 36 */ + "BrowseDirectChildren", + 0, + "COMPLETED", /* 39 */ + "ERROR", + "IN_PROGRESS", + "STOPPED", + 0, + "", /* 44 */ + 0 +}; + +static const char xmlver[] = + "\r\n"; +static const char root_service[] = + "scpd xmlns=\"urn:schemas-upnp-org:service-1-0\""; +static const char root_device[] = + "root xmlns=\"urn:schemas-upnp-org:device-1-0\""; + +/* root Description of the UPnP Device + * fixed to match UPnP_IGD_InternetGatewayDevice 1.0.pdf + * presentationURL is only "recommended" but the router doesn't appears + * in "Network connections" in Windows XP if it is not present. */ +static const struct XMLElt rootDesc[] = +{ + {root_device, INITHELPER(1,3)}, + {"specVersion", INITHELPER(4,2)}, + {"/URLBase", "http://192.168.10.112:5555/"}, + {"device", INITHELPER(6,12)}, + {"/major", "1"}, + {"/minor", "0"}, + {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"}, + {"/friendlyName", ROOTDEV_FRIENDLYNAME}, /* required */ + {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ + {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ + {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ + {"/modelName", ROOTDEV_MODELNAME}, /* required */ + {"/modelNumber", modelnumber}, + {"/modelURL", ROOTDEV_MODELURL}, + {"/serialNumber", serialnumber}, + {"/UDN", uuidvalue}, /* required */ + {"serviceList", INITHELPER(18,2)}, + {"/presentationURL", presentationurl}, /* recommended */ + {"service", INITHELPER(20,5)}, + {"service", INITHELPER(25,5)}, + {"/serviceType", "urn:schemas-upnp-org:service:ContentDirectory:1"}, + {"/serviceId", "urn:upnp-org:serviceId:ContentDirectory"}, + {"/controlURL", CONTENTDIRECTORY_CONTROLURL}, + {"/eventSubURL", CONTENTDIRECTORY_EVENTURL}, + {"/SCPDURL", CONTENTDIRECTORY_PATH}, + {"/serviceType", "urn:schemas-upnp-org:service:ConnectionManager:1"}, + {"/serviceId", "urn:upnp-org:serviceId:ConnectionManager"}, + {"/controlURL", CONNECTIONMGR_CONTROLURL}, + {"/eventSubURL", CONNECTIONMGR_EVENTURL}, + {"/SCPDURL", CONNECTIONMGR_PATH}, + {0, 0} +}; + +/* WANIPCn.xml */ +/* see UPnP_IGD_WANIPConnection 1.0.pdf +static struct XMLElt scpdWANIPCn[] = +{ + {root_service, {INITHELPER(1,2)}}, + {0, {0}} +}; +*/ +static const struct argument AddPortMappingArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 1, 13}, + {NULL, 1, 15}, + {NULL, 1, 9}, + {NULL, 1, 16}, + {NULL, 1, 10}, + {NULL, 0, 0} +}; + +static const struct argument GetExternalIPAddressArgs[] = +{ + {NULL, 2, 7}, + {NULL, 0, 0} +}; + +static const struct argument DeletePortMappingArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 0, 0} +}; + +static const struct argument SetConnectionTypeArgs[] = +{ + {NULL, 1, 0}, + {NULL, 0, 0} +}; + +static const struct argument GetConnectionTypeInfoArgs[] = +{ + {NULL, 2, 0}, + {NULL, 2, 1}, + {NULL, 0, 0} +}; + +static const struct argument GetStatusInfoArgs[] = +{ + {NULL, 2, 2}, + {NULL, 2, 4}, + {NULL, 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetNATRSIPStatusArgs[] = +{ + {NULL, 2, 5}, + {NULL, 2, 6}, + {NULL, 0, 0} +}; + +static const struct argument GetGenericPortMappingEntryArgs[] = +{ + {NULL, 1, 8}, + {NULL, 2, 11}, + {NULL, 2, 12}, + {NULL, 2, 14}, + {NULL, 2, 13}, + {NULL, 2, 15}, + {NULL, 2, 9}, + {NULL, 2, 16}, + {NULL, 2, 10}, + {NULL, 0, 0} +}; + +static const struct argument GetSpecificPortMappingEntryArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 2, 13}, + {NULL, 2, 15}, + {NULL, 2, 9}, + {NULL, 2, 16}, + {NULL, 2, 10}, + {NULL, 0, 0} +}; + +/* For ConnectionManager */ +static const struct argument GetProtocolInfoArgs[] = +{ + {"Source", 2, 0}, + {"Sink", 2, 1}, + {NULL, 0, 0} +}; + +static const struct argument PrepareForConnectionArgs[] = +{ + {"RemoteProtocolInfo", 1, 6}, + {"PeerConnectionManager", 1, 4}, + {"PeerConnectionID", 1, 7}, + {"Direction", 1, 5}, + {"ConnectionID", 2, 7}, + {"AVTransportID", 2, 8}, + {"RcsID", 2, 9}, + {NULL, 0, 0} +}; + +static const struct argument ConnectionCompleteArgs[] = +{ + {"ConnectionID", 1, 7}, + {NULL, 0, 0} +}; + +static const struct argument GetCurrentConnectionIDsArgs[] = +{ + {"ConnectionIDs", 2, 2}, + {NULL, 0, 0} +}; + +static const struct argument GetCurrentConnectionInfoArgs[] = +{ + {"ConnectionID", 1, 7}, + {"RcsID", 2, 9}, + {"AVTransportID", 2, 8}, + {"ProtocolInfo", 2, 6}, + {"PeerConnectionManager", 2, 4}, + {"PeerConnectionID", 2, 7}, + {"Direction", 2, 5}, + {"Status", 2, 3}, + {NULL, 0, 0} +}; + +static const struct action ConnectionManagerActions[] = +{ + {"GetProtocolInfo", GetProtocolInfoArgs}, /* R */ + {"PrepareForConnection", PrepareForConnectionArgs}, /* R */ + {"ConnectionComplete", ConnectionCompleteArgs}, /* R */ + {"GetCurrentConnectionIDs", GetCurrentConnectionIDsArgs}, /* R */ + {"GetCurrentConnectionInfo", GetCurrentConnectionInfoArgs}, /* R */ + {0, 0} +}; + +static const struct stateVar ConnectionManagerVars[] = +{ + {"SourceProtocolInfo", 1<<7, 0}, /* required */ + {"SinkProtocolInfo", 1<<7, 0}, /* required */ + {"CurrentConnectionIDs", 1<<7, 0}, /* required */ + {"A_ARG_TYPE_ConnectionStatus", 0, 0, 27}, /* required */ + {"A_ARG_TYPE_ConnectionManager", 0, 0}, /* required */ + {"A_ARG_TYPE_Direction", 0, 0, 33}, /* required */ + {"A_ARG_TYPE_ProtocolInfo", 0, 0}, /* required */ + {"A_ARG_TYPE_ConnectionID", 4, 0}, /* required */ + {"A_ARG_TYPE_AVTransportID", 4, 0}, /* required */ + {"A_ARG_TYPE_RcsID", 4, 0}, /* required */ + {0, 0} +}; + +static const struct argument GetSearchCapabilitiesArgs[] = +{ + {"SearchCaps", 2, 16}, + {0, 0} +}; + +static const struct argument GetSortCapabilitiesArgs[] = +{ + {"SortCaps", 2, 17}, + {0, 0} +}; + +static const struct argument GetSystemUpdateIDArgs[] = +{ + {"Id", 2, 18}, + {0, 0} +}; + +static const struct argument BrowseArgs[] = +{ + {"ObjectID", 1, 1}, + {"BrowseFlag", 1, 4}, + {"Filter", 1, 5}, + {"StartingIndex", 1, 7}, + {"RequestedCount", 1, 8}, + {"SortCriteria", 1, 6}, + {"Result", 2, 2}, + {"NumberReturned", 2, 8}, + {"TotalMatches", 2, 8}, + {"UpdateID", 2, 9}, + {0, 0} +}; + +static const struct action ContentDirectoryActions[] = +{ + {"GetSearchCapabilities", GetSearchCapabilitiesArgs}, /* R */ + {"GetSortCapabilities", GetSortCapabilitiesArgs}, /* R */ + {"GetSystemUpdateID", GetSystemUpdateIDArgs}, /* R */ + {"Browse", BrowseArgs}, /* R */ +#if 0 // Not implementing optional features yet... + {"Search", SearchArgs}, /* O */ + {"CreateObject", CreateObjectArgs}, /* O */ + {"DestroyObject", DestroyObjectArgs}, /* O */ + {"UpdateObject", UpdateObjectArgs}, /* O */ + {"ImportResource", ImportResourceArgs}, /* O */ + {"ExportResource", ExportResourceArgs}, /* O */ + {"StopTransferResource", StopTransferResourceArgs}, /* O */ + {"GetTransferProgress", GetTransferProgressArgs}, /* O */ + {"DeleteResource", DeleteResourceArgs}, /* O */ + {"CreateReference", CreateReferenceArgs}, /* O */ +#endif + {0, 0} +}; + +static const struct stateVar ContentDirectoryVars[] = +{ + {"TransferIDs", 1<<7, 0}, /* 0 */ + {"A_ARG_TYPE_ObjectID", 0, 0}, + {"A_ARG_TYPE_Result", 0, 0}, + {"A_ARG_TYPE_SearchCriteria", 0, 0}, + {"A_ARG_TYPE_BrowseFlag", 0, 0, 36}, + /* Allowed Values : BrowseMetadata / BrowseDirectChildren */ + {"A_ARG_TYPE_Filter", 0, 0}, /* 5 */ + {"A_ARG_TYPE_SortCriteria", 0, 0}, + {"A_ARG_TYPE_Index", 3, 0}, + {"A_ARG_TYPE_Count", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_TransferID", 3, 0}, /* 10 */ + {"A_ARG_TYPE_TransferStatus", 0, 0, 39}, + /* Allowed Values : COMPLETED / ERROR / IN_PROGRESS / STOPPED */ + {"A_ARG_TYPE_TransferLength", 0, 0}, + {"A_ARG_TYPE_TransferTotal", 0, 0}, + {"A_ARG_TYPE_TagValueList", 0, 0}, + {"A_ARG_TYPE_URI", 5, 0}, /* 15 */ + {"SearchCapabilities", 0, 0}, + {"SortCapabilities", 0, 0}, + {"SystemUpdateID", 3, 0}, + {"ContainerUpdateIDs", 0, 0}, + {0, 0} +}; + +static const struct action WANIPCnActions[] = +{ + {"AddPortMapping", AddPortMappingArgs}, /* R */ + {"GetExternalIPAddress", GetExternalIPAddressArgs}, /* R */ + {"DeletePortMapping", DeletePortMappingArgs}, /* R */ + {"SetConnectionType", SetConnectionTypeArgs}, /* R */ + {"GetConnectionTypeInfo", GetConnectionTypeInfoArgs}, /* R */ + {"RequestConnection", 0}, /* R */ + {"ForceTermination", 0}, /* R */ + {"GetStatusInfo", GetStatusInfoArgs}, /* R */ + {"GetNATRSIPStatus", GetNATRSIPStatusArgs}, /* R */ + {"GetGenericPortMappingEntry", GetGenericPortMappingEntryArgs}, /* R */ + {"GetSpecificPortMappingEntry", GetSpecificPortMappingEntryArgs}, /* R */ + {0, 0} +}; +/* R=Required, O=Optional */ + +static const struct stateVar WANIPCnVars[] = +{ +/* 0 */ + {"ConnectionType", 0, 0/*1*/}, /* required */ + {"PossibleConnectionTypes", 0|0x80, 0, 14, 15}, + /* Required + * Allowed values : Unconfigured / IP_Routed / IP_Bridged */ + {"ConnectionStatus", 0|0x80, 0/*1*/, 18, 20}, /* required */ + /* Allowed Values : Unconfigured / Connecting(opt) / Connected + * PendingDisconnect(opt) / Disconnecting (opt) + * Disconnected */ + {"Uptime", 3, 0}, /* Required */ + {"LastConnectionError", 0, 0, 25}, /* required : */ + /* Allowed Values : ERROR_NONE(req) / ERROR_COMMAND_ABORTED(opt) + * ERROR_NOT_ENABLED_FOR_INTERNET(opt) + * ERROR_USER_DISCONNECT(opt) + * ERROR_ISP_DISCONNECT(opt) + * ERROR_IDLE_DISCONNECT(opt) + * ERROR_FORCED_DISCONNECT(opt) + * ERROR_NO_CARRIER(opt) + * ERROR_IP_CONFIGURATION(opt) + * ERROR_UNKNOWN(opt) */ + {"RSIPAvailable", 1, 0}, /* required */ + {"NATEnabled", 1, 0}, /* required */ + {"ExternalIPAddress", 0|0x80, 0, 0, 254}, /* required. Default : empty string */ + {"PortMappingNumberOfEntries", 2|0x80, 0, 0, 253}, /* required >= 0 */ + {"PortMappingEnabled", 1, 0}, /* Required */ + {"PortMappingLeaseDuration", 3, 0}, /* required */ + {"RemoteHost", 0, 0}, /* required. Default : empty string */ + {"ExternalPort", 2, 0}, /* required */ + {"InternalPort", 2, 0}, /* required */ + {"PortMappingProtocol", 0, 0, 11}, /* required allowedValues: TCP/UDP */ + {"InternalClient", 0, 0}, /* required */ + {"PortMappingDescription", 0, 0}, /* required default: empty string */ + {0, 0} +}; + +static const struct serviceDesc scpdWANIPCn = +{ WANIPCnActions, WANIPCnVars }; + +/* WANCfg.xml */ +/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ + +static const struct argument GetCommonLinkPropertiesArgs[] = +{ + {NULL, 2, 0}, + {NULL, 2, 1}, + {NULL, 2, 2}, + {NULL, 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalBytesSentArgs[] = +{ + {NULL, 2, 4}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalBytesReceivedArgs[] = +{ + {NULL, 2, 5}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalPacketsSentArgs[] = +{ + {NULL, 2, 6}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalPacketsReceivedArgs[] = +{ + {NULL, 2, 7}, + {NULL, 0, 0} +}; + +static const struct action WANCfgActions[] = +{ + {"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */ + {"GetTotalBytesSent", GetTotalBytesSentArgs}, /* optional */ + {"GetTotalBytesReceived", GetTotalBytesReceivedArgs}, /* optional */ + {"GetTotalPacketsSent", GetTotalPacketsSentArgs}, /* optional */ + {"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */ + {0, 0} +}; + +/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ +static const struct stateVar WANCfgVars[] = +{ + {"WANAccessType", 0, 0, 1}, + /* Allowed Values : DSL / POTS / Cable / Ethernet + * Default value : empty string */ + {"Layer1UpstreamMaxBitRate", 3, 0}, + {"Layer1DownstreamMaxBitRate", 3, 0}, + {"PhysicalLinkStatus", 0|0x80, 0, 6, 6}, + /* allowed values : + * Up / Down / Initializing (optional) / Unavailable (optionnal) + * no Default value + * Evented */ + {"TotalBytesSent", 3, 0}, /* Optional */ + {"TotalBytesReceived", 3, 0}, /* Optional */ + {"TotalPacketsSent", 3, 0}, /* Optional */ + {"TotalPacketsReceived", 3, 0},/* Optional */ + /*{"MaximumActiveConnections", 2, 0}, // allowed Range value // OPTIONAL */ + {0, 0} +}; + +static const struct serviceDesc scpdWANCfg = +{ WANCfgActions, WANCfgVars }; + +#ifdef ENABLE_L3F_SERVICE +/* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */ +static const struct argument SetDefaultConnectionServiceArgs[] = +{ + {NULL, 1, 0}, /* in */ + {NULL, 0, 0} +}; + +static const struct argument GetDefaultConnectionServiceArgs[] = +{ + {NULL, 2, 0}, /* out */ + {0, 0} +}; + +static const struct action L3FActions[] = +{ + {"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */ + {"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */ + {0, 0} +}; + +static const struct stateVar L3FVars[] = +{ + {"DefaultConnectionService", 0|0x80, 0, 0, 255}, /* Required */ + {0, 0} +}; + +static const struct serviceDesc scpdL3F = +{ L3FActions, L3FVars }; +#endif + +static const struct serviceDesc scpdContentDirectory = +{ ContentDirectoryActions, ContentDirectoryVars }; +//{ ContentDirectoryActions, ContentDirectoryVars }; + +static const struct serviceDesc scpdConnectionManager = +{ ConnectionManagerActions, ConnectionManagerVars }; + +/* strcat_str() + * concatenate the string and use realloc to increase the + * memory buffer if needed. */ +static char * +strcat_str(char * str, int * len, int * tmplen, const char * s2) +{ + int s2len; + s2len = (int)strlen(s2); + if(*tmplen <= (*len + s2len)) + { + if(s2len < 256) + *tmplen += 256; + else + *tmplen += s2len + 1; + str = (char *)realloc(str, *tmplen); + } + /*strcpy(str + *len, s2); */ + memcpy(str + *len, s2, s2len + 1); + *len += s2len; + return str; +} + +/* strcat_char() : + * concatenate a character and use realloc to increase the + * size of the memory buffer if needed */ +static char * +strcat_char(char * str, int * len, int * tmplen, char c) +{ + if(*tmplen <= (*len + 1)) + { + *tmplen += 256; + str = (char *)realloc(str, *tmplen); + } + str[*len] = c; + (*len)++; + return str; +} + +/* iterative subroutine using a small stack + * This way, the progam stack usage is kept low */ +static char * +genXML(char * str, int * len, int * tmplen, + const struct XMLElt * p) +{ + unsigned short i, j, k; + int top; + const char * eltname, *s; + char c; + struct { + unsigned short i; + unsigned short j; + const char * eltname; + } pile[16]; /* stack */ + top = -1; + i = 0; /* current node */ + j = 1; /* i + number of nodes*/ + for(;;) + { + eltname = p[i].eltname; + if(!eltname) + return str; + if(eltname[0] == '/') + { + /*printf("<%s>%s<%s>\n", eltname+1, p[i].data, eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname+1); + str = strcat_char(str, len, tmplen, '>'); + str = strcat_str(str, len, tmplen, p[i].data); + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname); + str = strcat_char(str, len, tmplen, '>'); + for(;;) + { + if(top < 0) + return str; + i = ++(pile[top].i); + j = pile[top].j; + /*printf(" pile[%d]\t%d %d\n", top, i, j); */ + if(i==j) + { + /*printf("\n", pile[top].eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_char(str, len, tmplen, '/'); + s = pile[top].eltname; + for(c = *s; c > ' '; c = *(++s)) + str = strcat_char(str, len, tmplen, c); + str = strcat_char(str, len, tmplen, '>'); + top--; + } + else + break; + } + } + else + { + /*printf("<%s>\n", eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname); + str = strcat_char(str, len, tmplen, '>'); + k = i; + /*i = p[k].index; */ + /*j = i + p[k].nchild; */ + i = (unsigned)p[k].data & 0xffff; + j = i + ((unsigned)p[k].data >> 16); + top++; + /*printf(" +pile[%d]\t%d %d\n", top, i, j); */ + pile[top].i = i; + pile[top].j = j; + pile[top].eltname = eltname; + } + } +} + +/* genRootDesc() : + * - Generate the root description of the UPnP device. + * - the len argument is used to return the length of + * the returned string. + * - tmp_uuid argument is used to build the uuid string */ +char * +genRootDesc(int * len) +{ + char * str; + int tmplen; + tmplen = 2048; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + * len = strlen(xmlver); + /*strcpy(str, xmlver); */ + memcpy(str, xmlver, *len + 1); + str = genXML(str, len, &tmplen, rootDesc); + str[*len] = '\0'; + return str; +} + +/* genServiceDesc() : + * Generate service description with allowed methods and + * related variables. */ +static char * +genServiceDesc(int * len, const struct serviceDesc * s) +{ + int i, j; + const struct action * acts; + const struct stateVar * vars; + const struct argument * args; + const char * p; + char * str; + int tmplen; + tmplen = 2048; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + /*strcpy(str, xmlver); */ + *len = strlen(xmlver); + memcpy(str, xmlver, *len + 1); + + acts = s->actionList; + vars = s->serviceStateTable; + + str = strcat_char(str, len, &tmplen, '<'); + str = strcat_str(str, len, &tmplen, root_service); + str = strcat_char(str, len, &tmplen, '>'); + + str = strcat_str(str, len, &tmplen, + "10"); + + i = 0; + str = strcat_str(str, len, &tmplen, ""); + while(acts[i].name) + { + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, acts[i].name); + str = strcat_str(str, len, &tmplen, ""); + /* argument List */ + args = acts[i].args; + if(args) + { + str = strcat_str(str, len, &tmplen, ""); + j = 0; + while(args[j].dir) + { + //JM str = strcat_str(str, len, &tmplen, "New"); + str = strcat_str(str, len, &tmplen, ""); + p = vars[args[j].relatedVar].name; + if(0 == memcmp(p, "PortMapping", 11) + && 0 != memcmp(p + 11, "Description", 11)) { + if(0 == memcmp(p + 11, "NumberOfEntries", 15)) + str = strcat_str(str, len, &tmplen, "PortMappingIndex"); + else + str = strcat_str(str, len, &tmplen, p + 11); + } else { + str = strcat_str(str, len, &tmplen, (args[j].name ? args[j].name : p)); + } + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, args[j].dir==1?"in":"out"); + str = strcat_str(str, len, &tmplen, + ""); + str = strcat_str(str, len, &tmplen, p); + str = strcat_str(str, len, &tmplen, + ""); + j++; + } + str = strcat_str(str, len, &tmplen,""); + } + str = strcat_str(str, len, &tmplen, ""); + /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ + i++; + } + str = strcat_str(str, len, &tmplen, ""); + i = 0; + while(vars[i].name) + { + str = strcat_str(str, len, &tmplen, + ""); + str = strcat_str(str, len, &tmplen, vars[i].name); + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]); + str = strcat_str(str, len, &tmplen, ""); + if(vars[i].iallowedlist) + { + str = strcat_str(str, len, &tmplen, ""); + for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++) + { + str = strcat_str(str, len, &tmplen, ""); + str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]); + str = strcat_str(str, len, &tmplen, ""); + } + str = strcat_str(str, len, &tmplen, ""); + } + /*if(vars[i].defaultValue) */ + if(vars[i].idefault) + { + str = strcat_str(str, len, &tmplen, ""); + /*str = strcat_str(str, len, &tmplen, vars[i].defaultValue); */ + str = strcat_str(str, len, &tmplen, upnpdefaultvalues[vars[i].idefault]); + str = strcat_str(str, len, &tmplen, ""); + } + str = strcat_str(str, len, &tmplen, ""); + /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ + i++; + } + str = strcat_str(str, len, &tmplen, ""); + str[*len] = '\0'; + return str; +} + +/* genContentDirectory() : + * Generate the ContentDirectory xml description */ +char * +genContentDirectory(int * len) +{ + return genServiceDesc(len, &scpdContentDirectory); +} + +/* genConnectionManager() : + * Generate the ConnectionManager xml description */ +char * +genConnectionManager(int * len) +{ + return genServiceDesc(len, &scpdConnectionManager); +} + +/* genWANIPCn() : + * Generate the WANIPConnection xml description */ +char * +genWANIPCn(int * len) +{ + return genServiceDesc(len, &scpdWANIPCn); +} + +/* genWANCfg() : + * Generate the WANInterfaceConfig xml description. */ +char * +genWANCfg(int * len) +{ + return genServiceDesc(len, &scpdWANCfg); +} + +#ifdef ENABLE_L3F_SERVICE +char * +genL3F(int * len) +{ + return genServiceDesc(len, &scpdL3F); +} +#endif + +#ifdef ENABLE_EVENTS +static char * +genEventVars(int * len, const struct serviceDesc * s, const char * servns) +{ + char tmp[16]; + const struct stateVar * v; + char * str; + int tmplen; + tmplen = 512; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + *len = 0; + v = s->serviceStateTable; + str = strcat_str(str, len, &tmplen, ""); + while(v->name) { + if(v->itype & 0x80) { + str = strcat_str(str, len, &tmplen, "name); + str = strcat_str(str, len, &tmplen, ">"); + //printf("", v->name); + switch(v->ieventvalue) { + case 0: + break; + case 253: /* Port mapping number of entries magical value */ + snprintf(tmp, sizeof(tmp), "%d", upnp_get_portmapping_number_of_entries()); + str = strcat_str(str, len, &tmplen, tmp); + break; + case 254: /* External ip address magical value */ + if(use_ext_ip_addr) + str = strcat_str(str, len, &tmplen, use_ext_ip_addr); + else { + char ext_ip_addr[INET_ADDRSTRLEN]; + if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN) < 0) { + str = strcat_str(str, len, &tmplen, "0.0.0.0"); + } else { + str = strcat_str(str, len, &tmplen, ext_ip_addr); + } + } + /*str = strcat_str(str, len, &tmplen, "0.0.0.0");*/ + break; + case 255: /* DefaultConnectionService magical value */ + str = strcat_str(str, len, &tmplen, uuidvalue); + str = strcat_str(str, len, &tmplen, ":WANConnectionDevice:1,urn:upnp-org:serviceId:WANIPConn1"); + //printf("%s:WANConnectionDevice:1,urn:upnp-org:serviceId:WANIPConn1", uuidvalue); + break; + default: + str = strcat_str(str, len, &tmplen, upnpallowedvalues[v->ieventvalue]); + //printf("%s", upnpallowedvalues[v->ieventvalue]); + } + str = strcat_str(str, len, &tmplen, "name); + str = strcat_str(str, len, &tmplen, ">"); + //printf("\n", v->name); + } + v++; + } + str = strcat_str(str, len, &tmplen, ""); + //printf("\n"); + //printf("\n"); + //printf("%d\n", tmplen); + str[*len] = '\0'; + return str; +} + +char * +getVarsContentDirectory(int * l) +{ + return genEventVars(l, + &scpdContentDirectory, + "urn:schemas-upnp-org:service:ContentDirectory:1"); +} + +char * +getVarsConnectionManager(int * l) +{ + return genEventVars(l, + &scpdConnectionManager, + "urn:schemas-upnp-org:service:ConnectionManager:1"); +} + +char * +getVarsWANIPCn(int * l) +{ + return genEventVars(l, + &scpdWANIPCn, + "urn:schemas-upnp-org:service:WANIPConnection:1"); +} + +char * +getVarsWANCfg(int * l) +{ + return genEventVars(l, + &scpdWANCfg, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"); +} + +#ifdef ENABLE_L3F_SERVICE +char * +getVarsL3F(int * l) +{ + return genEventVars(l, + &scpdL3F, + "urn:schemas-upnp-org:service:Layer3Forwarding:1"); +} +#endif +#endif diff --git a/upnpdescgen.h b/upnpdescgen.h new file mode 100644 index 0000000..f84da77 --- /dev/null +++ b/upnpdescgen.h @@ -0,0 +1,94 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __UPNPDESCGEN_H__ +#define __UPNPDESCGEN_H__ + +#include "config.h" + +/* for the root description + * The child list reference is stored in "data" member using the + * INITHELPER macro with index/nchild always in the + * same order, whatever the endianness */ +struct XMLElt { + const char * eltname; /* begin with '/' if no child */ + const char * data; /* Value */ +}; + +/* for service description */ +struct serviceDesc { + const struct action * actionList; + const struct stateVar * serviceStateTable; +}; + +struct action { + const char * name; + const struct argument * args; +}; + +struct argument { + const char * name; /* the name of the argument */ + unsigned char dir; /* 1 = in, 2 = out */ + unsigned char relatedVar; /* index of the related variable */ +}; + +struct stateVar { + const char * name; + unsigned char itype; /* MSB: sendEvent flag, 7 LSB: index in upnptypes */ + unsigned char idefault; /* default value */ + unsigned char iallowedlist; /* index in allowed values list */ + unsigned char ieventvalue; /* fixed value returned or magical values */ +}; + +/* little endian + * The code has now be tested on big endian architecture */ +#define INITHELPER(i, n) ((char *)((n<<16)|i)) + +/* char * genRootDesc(int *); + * returns: NULL on error, string allocated on the heap */ +char * +genRootDesc(int * len); + +/* for the two following functions */ +char * +genContentDirectory(int * len); + +char * +genConnectionManager(int * len); + +char * +genX_MS_MediaReceiverRegistrar(int * len); + +char * +genWANIPCn(int * len); + +char * +genWANCfg(int * len); + +#ifdef ENABLE_L3F_SERVICE +char * +genL3F(int * len); +#endif + +#ifdef ENABLE_EVENTS +char * +getVarsContentDirectory(int * len); + +char * +getVarsConnectionManager(int * len); + +char * +getVarsWANIPCn(int * len); + +char * +getVarsWANCfg(int * len); + +char * +getVarsL3F(int * len); +#endif + +#endif + diff --git a/upnpdescstrings.h b/upnpdescstrings.h new file mode 100644 index 0000000..57b543e --- /dev/null +++ b/upnpdescstrings.h @@ -0,0 +1,38 @@ +/* miniupnp project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the coditions detailed in + * the LICENCE file provided within the distribution */ +#ifndef __UPNPDESCSTRINGS_H__ +#define __UPNPDESCSTRINGS_H__ + +#include "config.h" + +/* strings used in the root device xml description */ +#define ROOTDEV_FRIENDLYNAME "MiniDLNA ReadyNAS:" +#define ROOTDEV_MANUFACTURER "NETGEAR" +#define ROOTDEV_MANUFACTURERURL OS_URL +#define ROOTDEV_MODELNAME "Windows Media Connect compatible (minidlna)" +#define ROOTDEV_MODELDESCRIPTION OS_NAME " *ReadyNAS dev DLNA" +#define ROOTDEV_MODELURL OS_URL + +#define WANDEV_FRIENDLYNAME "WANDevice" +#define WANDEV_MANUFACTURER "MiniUPnP" +#define WANDEV_MANUFACTURERURL "http://miniupnp.free.fr/" +#define WANDEV_MODELNAME "WAN Device" +#define WANDEV_MODELDESCRIPTION "WAN Device" +#define WANDEV_MODELNUMBER UPNP_VERSION +#define WANDEV_MODELURL "http://miniupnp.free.fr/" +#define WANDEV_UPC "MINIUPNPD" + +#define WANCDEV_FRIENDLYNAME "WANConnectionDevice" +#define WANCDEV_MANUFACTURER WANDEV_MANUFACTURER +#define WANCDEV_MANUFACTURERURL WANDEV_MANUFACTURERURL +#define WANCDEV_MODELNAME "MiniUPnPd" +#define WANCDEV_MODELDESCRIPTION "MiniUPnP daemon" +#define WANCDEV_MODELNUMBER UPNP_VERSION +#define WANCDEV_MODELURL "http://miniupnp.free.fr/" +#define WANCDEV_UPC "MINIUPNPD" + +#endif + diff --git a/upnpevents.c b/upnpevents.c new file mode 100644 index 0000000..07e06fb --- /dev/null +++ b/upnpevents.c @@ -0,0 +1,478 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "upnpevents.h" +#include "miniupnpdpath.h" +#include "upnpglobalvars.h" +#include "upnpdescgen.h" + +#ifdef ENABLE_EVENTS +/*enum subscriber_service_enum { + EWanCFG = 1, + EWanIPC, + EL3F +};*/ + +/* stuctures definitions */ +struct subscriber { + LIST_ENTRY(subscriber) entries; + struct upnp_event_notify * notify; + time_t timeout; + uint32_t seq; + /*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/ + enum subscriber_service_enum service; + char uuid[42]; + char callback[]; +}; + +struct upnp_event_notify { + LIST_ENTRY(upnp_event_notify) entries; + int s; /* socket */ + enum { ECreated=1, + EConnecting, + ESending, + EWaitingForResponse, + EFinished, + EError } state; + struct subscriber * sub; + char * buffer; + int buffersize; + int tosend; + int sent; + const char * path; + char addrstr[16]; + char portstr[8]; +}; + +/* prototypes */ +static void +upnp_event_create_notify(struct subscriber * sub); + +/* Subscriber list */ +LIST_HEAD(listhead, subscriber) subscriberlist = { NULL }; + +/* notify list */ +LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL }; + +/* create a new subscriber */ +static struct subscriber * +newSubscriber(const char * eventurl, const char * callback, int callbacklen) +{ + struct subscriber * tmp; + if(!eventurl || !callback || !callbacklen) + return NULL; + tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); + if(strcmp(eventurl, WANCFG_EVENTURL)==0) + tmp->service = EWanCFG; + else if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) + tmp->service = EContentDirectory; + else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) + tmp->service = EConnectionManager; + else if(strcmp(eventurl, WANIPC_EVENTURL)==0) + tmp->service = EWanIPC; +#ifdef ENABLE_L3F_SERVICE + else if(strcmp(eventurl, L3F_EVENTURL)==0) + tmp->service = EL3F; +#endif + else { + free(tmp); + return NULL; + } + memcpy(tmp->callback, callback, callbacklen); + tmp->callback[callbacklen] = '\0'; + /* make a dummy uuid */ + /* TODO: improve that */ + strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); + tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; + snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); + return tmp; +} + +/* creates a new subscriber and adds it to the subscriber list + * also initiate 1st notify */ +const char * +upnpevents_addSubscriber(const char * eventurl, + const char * callback, int callbacklen, + int timeout) +{ + struct subscriber * tmp; + /*static char uuid[42];*/ + /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */ + syslog(LOG_DEBUG, "addSubscriber(%s, %.*s, %d)", + eventurl, callbacklen, callback, timeout); + /*strncpy(uuid, uuidvalue, sizeof(uuid)); + uuid[sizeof(uuid)-1] = '\0';*/ + tmp = newSubscriber(eventurl, callback, callbacklen); + if(!tmp) + return NULL; + if(timeout) + tmp->timeout = time(NULL) + timeout; + LIST_INSERT_HEAD(&subscriberlist, tmp, entries); + upnp_event_create_notify(tmp); + return tmp->uuid; +} + +/* renew a subscription (update the timeout) */ +int +renewSubscription(const char * sid, int sidlen, int timeout) +{ + struct subscriber * sub; + for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { + if(memcmp(sid, sub->uuid, 41)) { + sub->timeout = (timeout ? time(NULL) + timeout : 0); + return 0; + } + } + return -1; +} + +int +upnpevents_removeSubscriber(const char * sid, int sidlen) +{ + struct subscriber * sub; + if(!sid) + return -1; + for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { + if(memcmp(sid, sub->uuid, 41)) { + if(sub->notify) { + sub->notify->sub = NULL; + } + LIST_REMOVE(sub, entries); + free(sub); + return 0; + } + } + return -1; +} + +/* notifies all subscriber of a number of port mapping change + * or external ip address change */ +void +upnp_event_var_change_notify(enum subscriber_service_enum service) +{ + struct subscriber * sub; + for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { + if(sub->service == service && sub->notify == NULL) + upnp_event_create_notify(sub); + } +} + +/* create and add the notify object to the list */ +static void +upnp_event_create_notify(struct subscriber * sub) +{ + struct upnp_event_notify * obj; + int flags; + obj = calloc(1, sizeof(struct upnp_event_notify)); + if(!obj) { + syslog(LOG_ERR, "%s: calloc(): %m", "upnp_event_create_notify"); + return; + } + obj->sub = sub; + obj->state = ECreated; + obj->s = socket(PF_INET, SOCK_STREAM, 0); + if(obj->s<0) { + syslog(LOG_ERR, "%s: socket(): %m", "upnp_event_create_notify"); + goto error; + } + if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) { + syslog(LOG_ERR, "%s: fcntl(..F_GETFL..): %m", + "upnp_event_create_notify"); + goto error; + } + if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) { + syslog(LOG_ERR, "%s: fcntl(..F_SETFL..): %m", + "upnp_event_create_notify"); + goto error; + } + if(sub) + sub->notify = obj; + LIST_INSERT_HEAD(¬ifylist, obj, entries); + return; +error: + if(obj->s >= 0) + close(obj->s); + free(obj); +} + +static void +upnp_event_notify_connect(struct upnp_event_notify * obj) +{ + int i; + const char * p; + unsigned short port; + struct sockaddr_in addr; + if(!obj) + return; + memset(&addr, 0, sizeof(addr)); + i = 0; + if(obj->sub == NULL) { + obj->state = EError; + return; + } + p = obj->sub->callback; + p += 7; /* http:// */ + while(*p != '/' && *p != ':') + obj->addrstr[i++] = *(p++); + obj->addrstr[i] = '\0'; + if(*p == ':') { + obj->portstr[0] = *p; + i = 1; + p++; + port = (unsigned short)atoi(p); + while(*p != '/') { + if(i<7) obj->portstr[i++] = *p; + p++; + } + obj->portstr[i] = 0; + } else { + port = 80; + obj->portstr[0] = '\0'; + } + obj->path = p; + addr.sin_family = AF_INET; + inet_aton(obj->addrstr, &addr.sin_addr); + addr.sin_port = htons(port); + syslog(LOG_DEBUG, "%s: '%s' %hu '%s'", "upnp_event_notify_connect", + obj->addrstr, port, obj->path); + obj->state = EConnecting; + if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + if(errno != EINPROGRESS && errno != EWOULDBLOCK) { + syslog(LOG_ERR, "%s: connect(): %m", "upnp_event_notify_connect"); + obj->state = EError; + } + } +} + +static void upnp_event_prepare(struct upnp_event_notify * obj) +{ + static const char notifymsg[] = + "NOTIFY %s HTTP/1.1\r\n" + "Host: %s%s\r\n" + "Content-Type: text/xml; charset=\"utf-8\"\r\n" + "Content-Length: %d\r\n" + "NT: upnp:event\r\n" + "NTS: upnp:propchange\r\n" + "SID: %s\r\n" + "SEQ: %u\r\n" + "Connection: close\r\n" + "Cache-Control: no-cache\r\n" + "\r\n" + "%.*s\r\n"; + char * xml; + int l; + if(obj->sub == NULL) { + obj->state = EError; + return; + } + switch(obj->sub->service) { + case EContentDirectory: + xml = getVarsContentDirectory(&l); + break; + case EConnectionManager: + xml = getVarsConnectionManager(&l); + break; + default: + xml = NULL; + l = 0; + } + obj->buffersize = 1536; + obj->buffer = malloc(obj->buffersize); + /*if(!obj->buffer) { + }*/ + obj->tosend = snprintf(obj->buffer, obj->buffersize, notifymsg, + obj->path, obj->addrstr, obj->portstr, l+2, + obj->sub->uuid, obj->sub->seq, + l, xml); + if(xml) { + free(xml); + xml = NULL; + } + //DEBUG printf("Preparing buffer:\n%s\n", obj->buffer); + obj->state = ESending; +} + +static void upnp_event_send(struct upnp_event_notify * obj) +{ + int i; + i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0); + if(i<0) { + syslog(LOG_NOTICE, "%s: send(): %m", "upnp_event_send"); + obj->state = EError; + return; + } + else if(i != (obj->tosend - obj->sent)) + syslog(LOG_NOTICE, "%s: %d bytes send out of %d", + "upnp_event_send", i, obj->tosend - obj->sent); + obj->sent += i; + if(obj->sent == obj->tosend) + obj->state = EWaitingForResponse; +} + +static void upnp_event_recv(struct upnp_event_notify * obj) +{ + int n; + n = recv(obj->s, obj->buffer, obj->buffersize, 0); + if(n<0) { + syslog(LOG_ERR, "%s: recv(): %m", "upnp_event_recv"); + obj->state = EError; + return; + } + syslog(LOG_DEBUG, "%s: (%dbytes) %.*s", "upnp_event_recv", + n, n, obj->buffer); + obj->state = EFinished; + if(obj->sub) + obj->sub->seq++; +} + +static void +upnp_event_process_notify(struct upnp_event_notify * obj) +{ + switch(obj->state) { + case EConnecting: + /* now connected or failed to connect */ + upnp_event_prepare(obj); + upnp_event_send(obj); + break; + case ESending: + upnp_event_send(obj); + break; + case EWaitingForResponse: + upnp_event_recv(obj); + break; + case EFinished: + close(obj->s); + obj->s = -1; + break; + default: + syslog(LOG_ERR, "upnp_event_process_notify: unknown state"); + } +} + +void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) +{ + struct upnp_event_notify * obj; + for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { + syslog(LOG_DEBUG, "upnpevents_selectfds: %p %d %d", + obj, obj->state, obj->s); + if(obj->s >= 0) { + switch(obj->state) { + case ECreated: + upnp_event_notify_connect(obj); + if(obj->state != EConnecting) + break; + case EConnecting: + case ESending: + FD_SET(obj->s, writeset); + if(obj->s > *max_fd) + *max_fd = obj->s; + break; + case EFinished: + case EError: + case EWaitingForResponse: + FD_SET(obj->s, readset); + if(obj->s > *max_fd) + *max_fd = obj->s; + break; + } + } + } +} + +void upnpevents_processfds(fd_set *readset, fd_set *writeset) +{ + struct upnp_event_notify * obj; + struct upnp_event_notify * next; + struct subscriber * sub; + struct subscriber * subnext; + time_t curtime; + for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { + syslog(LOG_DEBUG, "%s: %p %d %d %d %d", + "upnpevents_processfds", obj, obj->state, obj->s, + FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset)); + if(obj->s >= 0) { + if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset)) + upnp_event_process_notify(obj); + } + } + obj = notifylist.lh_first; + while(obj != NULL) { + next = obj->entries.le_next; + if(obj->state == EError || obj->state == EFinished) { + if(obj->s >= 0) { + close(obj->s); + } + if(obj->sub) + obj->sub->notify = NULL; + /* remove also the subscriber from the list if there was an error */ + if(obj->state == EError && obj->sub) { + LIST_REMOVE(obj->sub, entries); + free(obj->sub); + } + if(obj->buffer) { + free(obj->buffer); + } + LIST_REMOVE(obj, entries); + free(obj); + } + obj = next; + } + /* remove timeouted subscribers */ + curtime = time(NULL); + for(sub = subscriberlist.lh_first; sub != NULL; ) { + subnext = sub->entries.le_next; + if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) { + LIST_REMOVE(sub, entries); + free(sub); + } + sub = subnext; + } +} + +#ifdef USE_MINIUPNPDCTL +void write_events_details(int s) { + int n; + char buff[80]; + struct upnp_event_notify * obj; + struct subscriber * sub; + write(s, "Events details\n", 15); + for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { + n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n", + obj, obj->sub, obj->state, obj->s); + write(s, buff, n); + } + write(s, "Subscribers :\n", 14); + for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { + n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n", + sub, sub->timeout, sub->seq, sub->service); + write(s, buff, n); + n = snprintf(buff, sizeof(buff), " notify=%p %s\n", + sub->notify, sub->uuid); + write(s, buff, n); + n = snprintf(buff, sizeof(buff), " %s\n", + sub->callback); + write(s, buff, n); + } +} +#endif + +#endif + diff --git a/upnpevents.h b/upnpevents.h new file mode 100644 index 0000000..b4d2566 --- /dev/null +++ b/upnpevents.h @@ -0,0 +1,40 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __UPNPEVENTS_H__ +#define __UPNPEVENTS_H__ +#ifdef ENABLE_EVENTS +enum subscriber_service_enum { + EWanCFG = 1, + EContentDirectory, + EConnectionManager, + EWanIPC, + EL3F +}; + +void +upnp_event_var_change_notify(enum subscriber_service_enum service); + +const char * +upnpevents_addSubscriber(const char * eventurl, + const char * callback, int callbacklen, + int timeout); + +int +upnpevents_removeSubscriber(const char * sid, int sidlen); + +int +renewSubscription(const char * sid, int sidlen, int timeout); + +void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd); +void upnpevents_processfds(fd_set *readset, fd_set *writeset); + +#ifdef USE_MINIUPNPDCTL +void write_events_details(int s); +#endif + +#endif +#endif diff --git a/upnpglobalvars.c b/upnpglobalvars.c new file mode 100644 index 0000000..0459352 --- /dev/null +++ b/upnpglobalvars.c @@ -0,0 +1,81 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include + +#include "config.h" +#include "upnpglobalvars.h" + +/* network interface for internet */ +const char * ext_if_name = 0; + +/* file to store leases */ +#ifdef ENABLE_LEASEFILE +const char* lease_file = 0; +#endif + +/* forced ip address to use for this interface + * when NULL, getifaddr() is used */ +const char * use_ext_ip_addr = 0; + +/* LAN address */ +/*const char * listen_addr = 0;*/ + +unsigned long downstream_bitrate = 0; +unsigned long upstream_bitrate = 0; + +/* startup time */ +time_t startup_time = 0; + +#if 0 +/* use system uptime */ +int sysuptime = 0; + +/* log packets flag */ +int logpackets = 0; + +#ifdef ENABLE_NATPMP +int enablenatpmp = 0; +#endif +#endif + +int runtime_flags = 0; + +const char * pidfilename = "/var/run/minidlna.pid"; + +char uuidvalue[] = "uuid:00000000-0000-0000-0000-000000000000"; +char serialnumber[SERIALNUMBER_MAX_LEN] = "00000000"; + +char modelnumber[MODELNUMBER_MAX_LEN] = "1"; + +/* presentation url : + * http://nnn.nnn.nnn.nnn:ppppp/ => max 30 bytes including terminating 0 */ +char presentationurl[PRESENTATIONURL_MAX_LEN]; + +/* UPnP permission rules : */ +struct upnpperm * upnppermlist = 0; +unsigned int num_upnpperm = 0; + +#ifdef ENABLE_NATPMP +/* NAT-PMP */ +unsigned int nextnatpmptoclean_timestamp = 0; +unsigned short nextnatpmptoclean_eport = 0; +unsigned short nextnatpmptoclean_proto = 0; +#endif + +#ifdef USE_PF +const char * queue = 0; +const char * tag = 0; +#endif + +int n_lan_addr = 0; +struct lan_addr_s lan_addr[MAX_LAN_ADDR]; + +/* UPnP-A/V [DLNA] */ +sqlite3 *db; +char media_dir[256]; diff --git a/upnpglobalvars.h b/upnpglobalvars.h new file mode 100644 index 0000000..4d32f9f --- /dev/null +++ b/upnpglobalvars.h @@ -0,0 +1,95 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __UPNPGLOBALVARS_H__ +#define __UPNPGLOBALVARS_H__ + +#include +#include "miniupnpdtypes.h" +#include "config.h" + +#include + +/* name of the network interface used to acces internet */ +extern const char * ext_if_name; + +/* file to store all leases */ +#ifdef ENABLE_LEASEFILE +extern const char * lease_file; +#endif + +/* forced ip address to use for this interface + * when NULL, getifaddr() is used */ +extern const char * use_ext_ip_addr; + +/* parameters to return to upnp client when asked */ +extern unsigned long downstream_bitrate; +extern unsigned long upstream_bitrate; + +/* statup time */ +extern time_t startup_time; + +/* runtime boolean flags */ +extern int runtime_flags; +#define LOGPACKETSMASK 0x0001 +#define SYSUPTIMEMASK 0x0002 +#ifdef ENABLE_NATPMP +#define ENABLENATPMPMASK 0x0004 +#endif +#define CHECKCLIENTIPMASK 0x0008 +#define SECUREMODEMASK 0x0010 + +#ifdef PF_ENABLE_FILTER_RULES +#define PFNOQUICKRULESMASK 0x0040 +#endif + +#define SETFLAG(mask) runtime_flags |= mask +#define GETFLAG(mask) runtime_flags & mask +#define CLEARFLAG(mask) runtime_flags &= ~mask + +extern const char * pidfilename; + +extern char uuidvalue[]; + +#define SERIALNUMBER_MAX_LEN (10) +extern char serialnumber[]; + +#define MODELNUMBER_MAX_LEN (48) +extern char modelnumber[]; + +#define PRESENTATIONURL_MAX_LEN (64) +extern char presentationurl[]; + +/* UPnP permission rules : */ +extern struct upnpperm * upnppermlist; +extern unsigned int num_upnpperm; + +#ifdef ENABLE_NATPMP +/* NAT-PMP */ +extern unsigned int nextnatpmptoclean_timestamp; +extern unsigned short nextnatpmptoclean_eport; +extern unsigned short nextnatpmptoclean_proto; +#endif + +#ifdef USE_PF +/* queue and tag for PF rules */ +extern const char * queue; +extern const char * tag; +#endif + +/* lan addresses */ +/* MAX_LAN_ADDR : maximum number of interfaces + * to listen to SSDP traffic */ +#define MAX_LAN_ADDR (4) +extern int n_lan_addr; +extern struct lan_addr_s lan_addr[]; + +/* UPnP-A/V [DLNA] */ +extern sqlite3 *db; +#define MEDIADIR_MAX_LEN (256) +extern char media_dir[]; + +#endif diff --git a/upnphttp.c b/upnphttp.c new file mode 100644 index 0000000..54432cf --- /dev/null +++ b/upnphttp.c @@ -0,0 +1,1226 @@ +/* $Id$ */ +/* Project : miniupnp + * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + * Copyright (c) 2005-2008 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file included in this distribution. + * */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "upnphttp.h" +#include "upnpdescgen.h" +#include "miniupnpdpath.h" +#include "upnpsoap.h" +#include "upnpevents.h" + +#include +#include +#include +#include +#include + +#include "upnpglobalvars.h" +#include +#include +#if 0 //JPEG_RESIZE +#include +#endif + +struct upnphttp * +New_upnphttp(int s) +{ + struct upnphttp * ret; + if(s<0) + return NULL; + ret = (struct upnphttp *)malloc(sizeof(struct upnphttp)); + if(ret == NULL) + return NULL; + memset(ret, 0, sizeof(struct upnphttp)); + ret->socket = s; + return ret; +} + +void +CloseSocket_upnphttp(struct upnphttp * h) +{ + if(close(h->socket) < 0) + { + syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); + } + h->socket = -1; + h->state = 100; +} + +void +Delete_upnphttp(struct upnphttp * h) +{ + if(h) + { + if(h->socket >= 0) + CloseSocket_upnphttp(h); + if(h->req_buf) + free(h->req_buf); + if(h->res_buf) + free(h->res_buf); + free(h); + } +} + +/* parse HttpHeaders of the REQUEST */ +static void +ParseHttpHeaders(struct upnphttp * h) +{ + char * line; + char * colon; + char * p; + int n; + line = h->req_buf; + /* TODO : check if req_buf, contentoff are ok */ + while(line < (h->req_buf + h->req_contentoff)) + { + colon = strchr(line, ':'); + if(colon) + { + if(strncasecmp(line, "Content-Length", 14)==0) + { + p = colon; + while(*p < '0' || *p > '9') + p++; + h->req_contentlen = atoi(p); + /*printf("*** Content-Lenght = %d ***\n", h->req_contentlen); + printf(" readbufflen=%d contentoff = %d\n", + h->req_buflen, h->req_contentoff);*/ + } + else if(strncasecmp(line, "SOAPAction", 10)==0) + { + p = colon; + n = 0; + while(*p == ':' || *p == ' ' || *p == '\t') + p++; + while(p[n]>=' ') + { + n++; + } + if((p[0] == '"' && p[n-1] == '"') + || (p[0] == '\'' && p[n-1] == '\'')) + { + p++; n -= 2; + } + h->req_soapAction = p; + h->req_soapActionLen = n; + } +#ifdef ENABLE_EVENTS + else if(strncasecmp(line, "Callback", 8)==0) + { + p = colon; + while(*p != '<' && *p != '\r' ) + p++; + n = 0; + while(p[n] != '>' && p[n] != '\r' ) + n++; + h->req_Callback = p + 1; + h->req_CallbackLen = MAX(0, n - 1); + } + else if(strncasecmp(line, "SID", 3)==0) + { + p = colon + 1; + while(isspace(*p)) + p++; + n = 0; + while(!isspace(p[n])) + n++; + h->req_SID = p; + h->req_SIDLen = n; + } + /* Timeout: Seconds-nnnn */ +/* TIMEOUT +Recommended. Requested duration until subscription expires, +either number of seconds or infinite. Recommendation +by a UPnP Forum working committee. Defined by UPnP vendor. + Consists of the keyword "Second-" followed (without an +intervening space) by either an integer or the keyword "infinite". */ + else if(strncasecmp(line, "Timeout", 7)==0) + { + p = colon + 1; + while(isspace(*p)) + p++; + if(strncasecmp(p, "Second-", 7)==0) { + h->req_Timeout = atoi(p+7); + } + } +#endif +#if 1 + // Range: bytes=xxx-yyy + else if(strncasecmp(line, "Range", 5)==0) + { + p = colon + 1; + while(isspace(*p)) + p++; + if(strncasecmp(p, "bytes=", 6)==0) { + h->reqflags |= FLAG_RANGE; + h->req_RangeEnd = atoll(index(p+6, '-')+1); + h->req_RangeStart = atoll(p+6); +printf("Range Start-End: %lld-%lld\n", h->req_RangeStart, h->req_RangeEnd); + } + } + else if(strncasecmp(line, "Host", 4)==0) + { + h->reqflags |= FLAG_HOST; + } + else if(strncasecmp(line, "Transfer-Encoding", 17)==0) + { + p = colon + 1; + while(isspace(*p)) + p++; + if(strncasecmp(p, "chunked", 7)==0) + { + h->reqflags |= FLAG_CHUNKED; + } + } + else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0) + { + p = colon + 1; + while(isspace(*p)) + p++; + if( strcmp(p, "1") != 0 ) + h->reqflags |= FLAG_INVALID_REQ; + } + else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0) + { + h->reqflags |= FLAG_TIMESEEK; + } + else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0) + { + h->reqflags |= FLAG_REALTIMEINFO; + } + else if(strncasecmp(line, "transferMode.dlna.org", 21)==0) + { + p = colon + 1; + while(isspace(*p)) + p++; + if(strncasecmp(p, "Streaming", 9)==0) + { + h->reqflags |= FLAG_XFERSTREAMING; + } + if(strncasecmp(p, "Interactive", 11)==0) + { + h->reqflags |= FLAG_XFERINTERACTIVE; + } + if(strncasecmp(p, "Background", 10)==0) + { + h->reqflags |= FLAG_XFERBACKGROUND; + } + } +#endif + } + while(!(line[0] == '\r' && line[1] == '\n')) + line++; + line += 2; + } + if( h->reqflags & FLAG_CHUNKED ) + { + if( h->req_buflen > h->req_contentoff ) + { + h->req_chunklen = strtol(line, NULL, 16); + while(!(line[0] == '\r' && line[1] == '\n')) + { + line++; + h->req_contentoff++; + } + h->req_contentoff += 2; + } + else + { + h->req_chunklen = -1; + } + } +} + +/* very minimalistic 400 error message */ +static void +Send400(struct upnphttp * h) +{ + static const char body400[] = + "400 Bad Request" + "

Bad Request

The request is invalid" + " for this HTTP version.\r\n"; + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 400, "Bad Request", + body400, sizeof(body400) - 1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + +/* very minimalistic 404 error message */ +static void +Send404(struct upnphttp * h) +{ +/* + static const char error404[] = "HTTP/1.1 404 Not found\r\n" + "Connection: close\r\n" + "Content-type: text/html\r\n" + "\r\n" + "404 Not Found" + "

Not Found

The requested URL was not found" + " on this server.\r\n"; + int n; + n = send(h->socket, error404, sizeof(error404) - 1, 0); + if(n < 0) + { + syslog(LOG_ERR, "Send404: send(http): %m"); + }*/ + static const char body404[] = + "404 Not Found" + "

Not Found

The requested URL was not found" + " on this server.\r\n"; + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 404, "Not Found", + body404, sizeof(body404) - 1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + +/* very minimalistic 404 error message */ +static void +Send406(struct upnphttp * h) +{ + static const char body406[] = + "406 Not Acceptable" + "

Not Acceptable

An unsupported operation " + " was requested.\r\n"; + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 406, "Not Acceptable", + body406, sizeof(body406) - 1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + +/* very minimalistic 404 error message */ +static void +Send416(struct upnphttp * h) +{ + static const char body416[] = + "416 Requested Range Not Satisfiable" + "

Requested Range Not Satisfiable

The requested range" + " was outside the file's size.\r\n"; + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable", + body416, sizeof(body416) - 1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + +/* very minimalistic 501 error message */ +static void +Send501(struct upnphttp * h) +{ +/* + static const char error501[] = "HTTP/1.1 501 Not Implemented\r\n" + "Connection: close\r\n" + "Content-type: text/html\r\n" + "\r\n" + "501 Not Implemented" + "

Not Implemented

The HTTP Method " + "is not implemented by this server.\r\n"; + int n; + n = send(h->socket, error501, sizeof(error501) - 1, 0); + if(n < 0) + { + syslog(LOG_ERR, "Send501: send(http): %m"); + } +*/ + static const char body501[] = + "501 Not Implemented" + "

Not Implemented

The HTTP Method " + "is not implemented by this server.\r\n"; + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 501, "Not Implemented", + body501, sizeof(body501) - 1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + +static const char * +findendheaders(const char * s, int len) +{ + while(len-->0) + { + if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') + return s; + s++; + } + return NULL; +} + +#ifdef HAS_DUMMY_SERVICE +static void +sendDummyDesc(struct upnphttp * h) +{ + static const char xml_desc[] = "\r\n" + "" + " " + " 1" + " 0" + " " + " " + " " + "\r\n"; + BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} +#endif + +/* Sends the description generated by the parameter */ +static void +sendXMLdesc(struct upnphttp * h, char * (f)(int *)) +{ + char * desc; + int len; + desc = f(&len); + if(!desc) + { + static const char error500[] = "Error 500" + "Internal Server Error\r\n"; + syslog(LOG_ERR, "Failed to generate XML description"); + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 500, "Internal Server Error", + error500, sizeof(error500)-1); + } + else + { + BuildResp_upnphttp(h, desc, len); + } + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); + free(desc); +} + +/* ProcessHTTPPOST_upnphttp() + * executes the SOAP query if it is possible */ +static void +ProcessHTTPPOST_upnphttp(struct upnphttp * h) +{ + if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) + { + if(h->req_soapAction) + { + /* we can process the request */ +//printf("__LINE %d__ SOAPAction: %s [%d]\n", __LINE__, h->req_soapAction, h->req_soapActionLen); +// syslog(LOG_INFO, "SOAPAction: %.*s", +// h->req_soapActionLen, h->req_soapAction); +//printf("__LINE %d__ SOAPAction: %.*s\n", __LINE__, h->req_soapActionLen, h->req_soapAction); + ExecuteSoapAction(h, + h->req_soapAction, + h->req_soapActionLen); + } + else + { + static const char err400str[] = + "Bad request"; + syslog(LOG_INFO, "No SOAPAction in HTTP headers"); + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 400, "Bad Request", + err400str, sizeof(err400str) - 1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); + } + } + else + { + /* waiting for remaining data */ + h->state = 1; + } +} + +#ifdef ENABLE_EVENTS +static void +ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) +{ + const char * sid; + syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path); + syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d", + h->req_CallbackLen, h->req_Callback, h->req_Timeout); + syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); + if(!h->req_Callback && !h->req_SID) { + /* Missing or invalid CALLBACK : 412 Precondition Failed. + * If CALLBACK header is missing or does not contain a valid HTTP URL, + * the publisher must respond with HTTP error 412 Precondition Failed*/ + BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); + } else { + /* - add to the subscriber list + * - respond HTTP/x.x 200 OK + * - Send the initial event message */ +/* Server:, SID:; Timeout: Second-(xx|infinite) */ + if(h->req_Callback) { + sid = upnpevents_addSubscriber(path, h->req_Callback, + h->req_CallbackLen, h->req_Timeout); + h->respflags = FLAG_TIMEOUT; + if(sid) { + syslog(LOG_DEBUG, "generated sid=%s", sid); + h->respflags |= FLAG_SID; + h->req_SID = sid; + h->req_SIDLen = strlen(sid); + } + BuildResp_upnphttp(h, 0, 0); + } else { + /* subscription renew */ + /* Invalid SID +412 Precondition Failed. If a SID does not correspond to a known, +un-expired subscription, the publisher must respond +with HTTP error 412 Precondition Failed. */ + if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) { + BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); + } else { + /* A DLNA device must enforce a 5 minute timeout */ + h->respflags = FLAG_TIMEOUT; + h->req_Timeout = 300; + BuildResp_upnphttp(h, 0, 0); + } + } + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); + } +} + +static void +ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) +{ + syslog(LOG_DEBUG, "ProcessHTTPUnSubscribe %s", path); + syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); + /* Remove from the list */ + if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) { + BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); + } else { + BuildResp_upnphttp(h, 0, 0); + } + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} +#endif + +/* Parse and process Http Query + * called once all the HTTP headers have been received. */ +static void +ProcessHttpQuery_upnphttp(struct upnphttp * h) +{ + char HttpCommand[16]; + char HttpUrl[128]; + char * HttpVer; + char * p; + int i; + p = h->req_buf; + if(!p) + return; + for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) + HttpCommand[i] = *(p++); + HttpCommand[i] = '\0'; + while(*p==' ') + p++; + if(strncmp(p, "http://", 7) == 0) + { + p = p+7; + while(*p!='/') + p++; + } + for(i = 0; i<127 && *p != ' ' && *p != '\r'; i++) + HttpUrl[i] = *(p++); + HttpUrl[i] = '\0'; + while(*p==' ') + p++; + HttpVer = h->HttpVer; + for(i = 0; i<15 && *p != '\r'; i++) + HttpVer[i] = *(p++); + HttpVer[i] = '\0'; + syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)", + HttpCommand, HttpUrl, HttpVer); + ParseHttpHeaders(h); + + if( (h->reqflags & FLAG_CHUNKED) && (h->req_chunklen > (h->req_buflen - h->req_contentoff) || h->req_chunklen < 0) ) + { + /* waiting for remaining data */ + printf("*** %d < %d\n", (h->req_buflen - h->req_contentoff), h->req_contentlen); + printf("Chunked request [%ld]. Need more input.\n", h->req_chunklen); + h->state = 2; + } + else if(strcmp("POST", HttpCommand) == 0) + { + h->req_command = EPost; + ProcessHTTPPOST_upnphttp(h); + } + else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0)) + { + if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) ) + { + syslog(LOG_NOTICE, "No Host specified in HTTP headers, responding ERROR 400"); + Send400(h); + } + else if( h->reqflags & FLAG_TIMESEEK ) + { + syslog(LOG_NOTICE, "DLNA TimeSeek requested, responding ERROR 406"); + Send406(h); + } + else if(strcmp("GET", HttpCommand) == 0) + { + h->req_command = EGet; + } + else + { + h->req_command = EHead; + } + if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) + { + sendXMLdesc(h, genRootDesc); + } + else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0) + { + sendXMLdesc(h, genContentDirectory); + } + else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0) + { + sendXMLdesc(h, genConnectionManager); + } + else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0) + { + sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); + } +#ifdef HAS_DUMMY_SERVICE + else if(strcmp(DUMMY_PATH, HttpUrl) == 0) + { + sendDummyDesc(h); + } +#endif + else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) + { + SendResp_dlnafile(h, HttpUrl+12); + CloseSocket_upnphttp(h); + } + else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0) + { + SendResp_thumbnail(h, HttpUrl+12); + CloseSocket_upnphttp(h); + } +#if 0 //JPEG_RESIZE + else if(strncmp(HttpUrl, "/Resized/", 7) == 0) + { + SendResp_resizedimg(h, HttpUrl+7); + CloseSocket_upnphttp(h); + } +#endif + else + { + syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); + Send404(h); + } + } +#ifdef ENABLE_EVENTS + else if(strcmp("SUBSCRIBE", HttpCommand) == 0) + { + h->req_command = ESubscribe; + ProcessHTTPSubscribe_upnphttp(h, HttpUrl); + } + else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0) + { + h->req_command = EUnSubscribe; + ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl); + } +#else + else if(strcmp("SUBSCRIBE", HttpCommand) == 0) + { + syslog(LOG_NOTICE, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled"); + Send501(h); + } +#endif + else + { + syslog(LOG_NOTICE, "Unsupported HTTP Command %s", HttpCommand); + Send501(h); + } +} + + +void +Process_upnphttp(struct upnphttp * h) +{ + char buf[2048]; + int n; + if(!h) + return; + switch(h->state) + { + case 0: + n = recv(h->socket, buf, 2048, 0); + if(n<0) + { + syslog(LOG_ERR, "recv (state0): %m"); + h->state = 100; + } + else if(n==0) + { + syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); + h->state = 100; + } + else + { + const char * endheaders; + /* if 1st arg of realloc() is null, + * realloc behaves the same as malloc() */ + h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1); + memcpy(h->req_buf + h->req_buflen, buf, n); + h->req_buflen += n; + h->req_buf[h->req_buflen] = '\0'; + /* search for the string "\r\n\r\n" */ + endheaders = findendheaders(h->req_buf, h->req_buflen); + if(endheaders) + { + h->req_contentoff = endheaders - h->req_buf + 4; + ProcessHttpQuery_upnphttp(h); + } + } + break; + case 1: + case 2: + n = recv(h->socket, buf, 2048, 0); + if(n<0) + { + syslog(LOG_ERR, "recv (state1): %m"); + h->state = 100; + } + else if(n==0) + { + syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); + h->state = 100; + } + else + { + /*fwrite(buf, 1, n, stdout);*/ /* debug */ + h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); + memcpy(h->req_buf + h->req_buflen, buf, n); + h->req_buflen += n; + if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) + { + if( h->state == 1 ) + ProcessHTTPPOST_upnphttp(h); + else if( h->state == 2 ) + ProcessHttpQuery_upnphttp(h); + } + } + break; + default: + syslog(LOG_WARNING, "Unexpected state: %d", h->state); + } +} + +static const char httpresphead[] = + "%s %d %s\r\n" + /*"Content-Type: text/xml; charset=\"utf-8\"\r\n"*/ + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Content-Length: %d\r\n" + /*"Server: miniupnpd/1.0 UPnP/1.0\r\n"*/ +// "Accept-Ranges: bytes\r\n" +// "DATE: Wed, 24 Sep 2008 05:57:19 GMT\r\n" + //"Server: " MINIUPNPD_SERVER_STRING "\r\n" + ; /*"\r\n";*/ +/* + "\n" + "" + "" + + "" + ""; +*/ +/* with response code and response message + * also allocate enough memory */ + +void +BuildHeader_upnphttp(struct upnphttp * h, int respcode, + const char * respmsg, + int bodylen) +{ + int templen; + if(!h->res_buf) + { + templen = sizeof(httpresphead) + 128 + bodylen; + h->res_buf = (char *)malloc(templen); + h->res_buf_alloclen = templen; + } + h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, + //httpresphead, h->HttpVer, + httpresphead, "HTTP/1.1", + respcode, respmsg, + (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", + bodylen); + /* Additional headers */ +#ifdef ENABLE_EVENTS + if(h->respflags & FLAG_TIMEOUT) { + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Timeout: Second-"); + if(h->req_Timeout) { + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "%d\r\n", h->req_Timeout); + } else { + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "300\r\n"); + //JM DLNA must force to 300 - "infinite\r\n"); + } + } + if(h->respflags & FLAG_SID) { + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "SID: %s\r\n", h->req_SID); + } +#endif +#if 0 // DLNA + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Server: Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0\r\n"); + char szTime[30]; + time_t curtime = time(NULL); + strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Date: %s\r\n", szTime); +// h->res_buflen += snprintf(h->res_buf + h->res_buflen, +// h->res_buf_alloclen - h->res_buflen, +// "contentFeatures.dlna.org: \r\n"); +// h->res_buflen += snprintf(h->res_buf + h->res_buflen, +// h->res_buf_alloclen - h->res_buflen, +// "EXT:\r\n"); +#endif + h->res_buf[h->res_buflen++] = '\r'; + h->res_buf[h->res_buflen++] = '\n'; + if(h->res_buf_alloclen < (h->res_buflen + bodylen)) + { + h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); + h->res_buf_alloclen = h->res_buflen + bodylen; + } +} + +void +BuildResp2_upnphttp(struct upnphttp * h, int respcode, + const char * respmsg, + const char * body, int bodylen) +{ + BuildHeader_upnphttp(h, respcode, respmsg, bodylen); + if( h->req_command == EHead ) + return; + if(body) + memcpy(h->res_buf + h->res_buflen, body, bodylen); + h->res_buflen += bodylen; +} + +/* responding 200 OK ! */ +void +BuildResp_upnphttp(struct upnphttp * h, + const char * body, int bodylen) +{ + BuildResp2_upnphttp(h, 200, "OK", body, bodylen); +} + +void +SendResp_upnphttp(struct upnphttp * h) +{ + int n; +printf("HTTP RESPONSE:\n%.*s\n", h->res_buflen, h->res_buf); + n = send(h->socket, h->res_buf, h->res_buflen, 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } +} + +void +SendResp_thumbnail(struct upnphttp * h, char * object) +{ + char header[1500]; + char sql_buf[256]; + char **result; + char date[30]; + time_t curtime = time(NULL); + int n; + ExifData *ed; + ExifLoader *l; + + memset(header, 0, 1500); + + if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) + { + syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); + Send406(h); + return; + } + + sprintf(sql_buf, "SELECT PATH from OBJECTS where OBJECT_ID = '%s'", object); + sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); + printf("Serving up thumbnail for ObjectId: %s [%s]\n", object, result[1]); + + if( access(result[1], F_OK) == 0 ) + { + strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); + + l = exif_loader_new(); + exif_loader_write_file(l, result[1]); + ed = exif_loader_get_data(l); + exif_loader_unref(l); + + if( !ed->size ) + { + Send404(h); + goto error; + } + sprintf(header, "HTTP/1.1 200 OK\r\n" + "Content-Type: image/jpeg\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "Date: %s\r\n" + "EXT:\r\n" + "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" + "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA_TN/1.0\r\n", + ed->size, date); + + if( h->reqflags & FLAG_XFERBACKGROUND ) + { + strcat(header, "transferMode.dlna.org: Background\r\n"); + } + else //if( h->reqflags & FLAG_XFERINTERACTIVE ) + { + strcat(header, "transferMode.dlna.org: Interactive\r\n"); + } + strcat(header, "\r\n"); + + n = send(h->socket, header, strlen(header), 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } + + if( h->req_command == EHead ) + { + exif_data_unref(ed); + goto error; + } + + n = send(h->socket, ed->data, ed->size, 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } + exif_data_unref(ed); + } + error: + sqlite3_free_table(result); +} + +#if 0 //JPEG_RESIZE +void +SendResp_resizedimg(struct upnphttp * h, char * object) +{ + char header[1500]; + char sql_buf[256]; + char **result; + char date[30]; + time_t curtime = time(NULL); + int n; + FILE *imgfile; + gdImagePtr imsrc = 0, imdst = 0; + int dstw, dsth, srcw, srch, size; + char * data; + + memset(header, 0, 1500); + + if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) + { + syslog(LOG_NOTICE, "You can't specify transferMode as Streaming with a resized image!"); + Send406(h); + return; + } + + sprintf(sql_buf, "SELECT o.PATH, d.WIDTH, d.HEIGHT from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); + printf("Serving up resized image for ObjectId: %s [%s]\n", object, result[1]); + + if( access(result[3], F_OK) == 0 ) + { + strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); + + imgfile = fopen(result[3], "r"); + imsrc = gdImageCreateFromJpeg(imgfile); + imdst = gdImageCreateTrueColor(dstw, dsth); + srcw = atoi(result[4]); + srch = atoi(result[5]); + dstw = 640; + dsth = ((((640<<10)/srcw)*srch)>>10); + + if( !imsrc ) + { + Send404(h); + goto error; + } + if( dsth > 480 ) + { + dsth = 480; + dstw = (((480<<10)/srch) * srcw>>10); + } + gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, srcw, srch); + data = (char *)gdImageJpegPtr(imdst, &size, -1); + sprintf(header, "%s 200 OK\r\n" + "Content-Type: image/jpeg\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "Date: %s\r\n" + "EXT:\r\n" + "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" + "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA_TN/1.0\r\n", + h->HttpVer, size, date); + + if( h->reqflags & FLAG_XFERINTERACTIVE ) + { + strcat(header, "transferMode.dlna.org: Interactive\r\n"); + } + else if( h->reqflags & FLAG_XFERBACKGROUND ) + { + strcat(header, "transferMode.dlna.org: Background\r\n"); + } + strcat(header, "\r\n"); + + n = send(h->socket, header, strlen(header), 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } + + if( h->req_command == EHead ) + { + goto error; + } + + n = send(h->socket, data, size, 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } + gdFree(data); + } + error: + gdImageDestroy(imsrc); + gdImageDestroy(imdst); + sqlite3_free_table(result); +} +#endif + +void +SendResp_dlnafile(struct upnphttp * h, char * object) +{ + char header[1500]; + char hdr_buf[512]; + char sql_buf[256]; + char **result; + int rows; + char date[30]; + time_t curtime = time(NULL); + off_t total; + char *path, *mime, *dlna; + + memset(header, 0, 1500); + + sprintf(sql_buf, "SELECT o.PATH, d.MIME, d.DLNA_PN from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); + if( !rows ) + { + syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object); + Send404(h); + goto error; + } + + path = result[3]; + mime = result[4]; + dlna = result[5]; + printf("ObjectId: %s [%s]\n", object, path); + + if( h->reqflags & FLAG_XFERSTREAMING ) + { + if( strncmp(mime, "imag", 4) == 0 ) + { + syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); + Send406(h); + goto error; + } + } + if( h->reqflags & FLAG_XFERINTERACTIVE ) + { + if( h->reqflags & FLAG_REALTIMEINFO ) + { + syslog(LOG_NOTICE, "Bad realTimeInfo flag with Interactive request!"); + Send400(h); + goto error; + } + if( strncmp(mime, "imag", 4) != 0 ) + { + syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Interactive without an image!"); + Send406(h); + goto error; + } + } + + strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); + off_t offset = h->req_RangeStart; + int sendfh = open(path, O_RDONLY); + if( sendfh < 0 ) { + printf("Error opening %s\n", result[2]); + goto error; + } + off_t size = lseek(sendfh, 0, SEEK_END); + lseek(sendfh, 0, SEEK_SET); + + if( h->reqflags & FLAG_RANGE ) + { + if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) ) + { + syslog(LOG_NOTICE, "Specified range was invalid!"); + Send400(h); + close(sendfh); + goto error; + } + if( h->req_RangeEnd > size ) + { + syslog(LOG_NOTICE, "Specified range was outside file boundaries!"); + Send416(h); + close(sendfh); + goto error; + } + + sprintf(hdr_buf, "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n", mime); + strcpy(header, hdr_buf); + if( h->req_RangeEnd && (h->req_RangeEnd < size) ) + { + total = h->req_RangeEnd - h->req_RangeStart + 1; + sprintf(hdr_buf, "Content-Length: %llu\r\n" + "Content-Range: bytes %lld-%lld/%llu\r\n", + total, h->req_RangeStart, h->req_RangeEnd, size); + } + else + { + h->req_RangeEnd = size; + total = size - h->req_RangeStart; + sprintf(hdr_buf, "Content-Length: %llu\r\n" + "Content-Range: bytes %lld-%llu/%llu\r\n", + total, h->req_RangeStart, size-1, size); + } + } + else + { + h->req_RangeEnd = size; + total = size; + sprintf(hdr_buf, "%s 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %llu\r\n", + "HTTP/1.1", mime, total); + //h->HttpVer, mime, total); + } + strcat(header, hdr_buf); + + if( h->reqflags & FLAG_XFERSTREAMING ) + { + strcat(header, "transferMode.dlna.org: Streaming\r\n"); + } + else if( h->reqflags & FLAG_XFERBACKGROUND ) + { + if( strncmp(mime, "imag", 4) == 0 ) + strcat(header, "transferMode.dlna.org: Background\r\n"); + } + else //if( h->reqflags & FLAG_XFERINTERACTIVE ) + { + strcat(header, "transferMode.dlna.org: Interactive\r\n"); + } + + sprintf(hdr_buf, "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "Date: %s\r\n" + "EXT:\r\n" + "contentFeatures.dlna.org: DLNA.ORG_PN=%s\r\n" + "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA/1.0\r\n\r\n", + date, dlna); + strcat(header, hdr_buf); + + int n; + n = send(h->socket, header, strlen(header), 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } + + if( h->req_command == EHead ) + { + close(sendfh); + } + else if( sendfh > 0 ) + { + while( offset < h->req_RangeEnd ) { + int ret = sendfile(h->socket, sendfh, &offset, (h->req_RangeEnd - offset + 1)); + if( ret == -1 ) { + printf("sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); + if( errno == 32 || errno == 9 || errno == 54 || errno == 104 ) + break; + } + else { + printf("sent %d bytes to %d. offset is now %d.\n", ret, h->socket, (int)offset); + } + } + close(sendfh); + } + error: + sqlite3_free_table(result); +} diff --git a/upnphttp.h b/upnphttp.h new file mode 100644 index 0000000..ffb5a60 --- /dev/null +++ b/upnphttp.h @@ -0,0 +1,133 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __UPNPHTTP_H__ +#define __UPNPHTTP_H__ + +#include +#include + +#include "config.h" + +/* server: HTTP header returned in all HTTP responses : */ +#define MINIUPNPD_SERVER_STRING OS_VERSION " UPnP/1.0 miniupnpd/1.0" + +/* + states : + 0 - waiting for data to read + 1 - waiting for HTTP Post Content. + ... + >= 100 - to be deleted +*/ +enum httpCommands { + EUnknown = 0, + EGet, + EPost, + EHead, + ESubscribe, + EUnSubscribe +}; + +struct upnphttp { + int socket; + struct in_addr clientaddr; /* client address */ + int state; + char HttpVer[16]; + /* request */ + char * req_buf; + int req_buflen; + int req_contentlen; + int req_contentoff; /* header length */ + enum httpCommands req_command; + const char * req_soapAction; + int req_soapActionLen; +#ifdef ENABLE_EVENTS + const char * req_Callback; /* For SUBSCRIBE */ + int req_CallbackLen; + int req_Timeout; + const char * req_SID; /* For UNSUBSCRIBE */ + int req_SIDLen; +#endif + long long int req_RangeStart; + long long int req_RangeEnd; + long int req_chunklen; + int reqflags; + int respflags; + /* response */ + char * res_buf; + int res_buflen; + int res_buf_alloclen; + /*int res_contentlen;*/ + /*int res_contentoff;*/ /* header length */ + LIST_ENTRY(upnphttp) entries; +}; + +#define FLAG_TIMEOUT 0x01 +#define FLAG_SID 0x02 +#define FLAG_RANGE 0x04 +#define FLAG_HOST 0x08 + +#define FLAG_HTML 0x80 +#define FLAG_INVALID_REQ 0x10 + +#define FLAG_CHUNKED 0x0100 +#define FLAG_TIMESEEK 0x0200 +#define FLAG_REALTIMEINFO 0x0400 +#define FLAG_XFERSTREAMING 0x1000 +#define FLAG_XFERINTERACTIVE 0x2000 +#define FLAG_XFERBACKGROUND 0x4000 + +/* New_upnphttp() */ +struct upnphttp * +New_upnphttp(int); + +/* CloseSocket_upnphttp() */ +void +CloseSocket_upnphttp(struct upnphttp *); + +/* Delete_upnphttp() */ +void +Delete_upnphttp(struct upnphttp *); + +/* Process_upnphttp() */ +void +Process_upnphttp(struct upnphttp *); + +/* BuildHeader_upnphttp() + * build the header for the HTTP Response + * also allocate the buffer for body data */ +void +BuildHeader_upnphttp(struct upnphttp * h, int respcode, + const char * respmsg, + int bodylen); + +/* BuildResp_upnphttp() + * fill the res_buf buffer with the complete + * HTTP 200 OK response from the body passed as argument */ +void +BuildResp_upnphttp(struct upnphttp *, const char *, int); + +/* BuildResp2_upnphttp() + * same but with given response code/message */ +void +BuildResp2_upnphttp(struct upnphttp * h, int respcode, + const char * respmsg, + const char * body, int bodylen); + +/* SendResp_upnphttp() */ +void +SendResp_upnphttp(struct upnphttp *); + +void +SendResp_resizedimg(struct upnphttp *, char * url); +void +SendResp_thumbnail(struct upnphttp *, char * url); +/* SendResp_dlnafile() + * send the actual file data for a UPnP-A/V or DLNA request. */ +void +SendResp_dlnafile(struct upnphttp *, char * url); +#endif + diff --git a/upnpreplyparse.c b/upnpreplyparse.c new file mode 100644 index 0000000..b19915a --- /dev/null +++ b/upnpreplyparse.c @@ -0,0 +1,127 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include + +#include "upnpreplyparse.h" +#include "minixml.h" + +static void +NameValueParserStartElt(void * d, const char * name, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + if(l>511) + l = 511; + memcpy(data->curelt, name, l); + data->curelt[l] = '\0'; +} + +static void +NameValueParserGetData(void * d, const char * datas, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + struct NameValue * nv; + nv = malloc(sizeof(struct NameValue)); + if(l>511) + l = 511; + strncpy(nv->name, data->curelt, 512); + nv->name[511] = '\0'; + memcpy(nv->value, datas, l); + nv->value[l] = '\0'; + LIST_INSERT_HEAD( &(data->head), nv, entries); +} + +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data) +{ + struct xmlparser parser; + LIST_INIT(&(data->head)); + /* init xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = NameValueParserStartElt; + parser.endeltfunc = 0; + parser.datafunc = NameValueParserGetData; + parser.attfunc = 0; + parsexml(&parser); +} + +void +ClearNameValueList(struct NameValueParserData * pdata) +{ + struct NameValue * nv; + while((nv = pdata->head.lh_first) != NULL) + { + LIST_REMOVE(nv, entries); + free(nv); + } +} + +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + if(strcmp(nv->name, Name) == 0) + p = nv->value; + } + return p; +} + +#if 0 +/* useless now that minixml ignores namespaces by itself */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + char * pname; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + pname = strrchr(nv->name, ':'); + if(pname) + pname++; + else + pname = nv->name; + if(strcmp(pname, Name)==0) + p = nv->value; + } + return p; +} +#endif + +/* debug all-in-one function + * do parsing then display to stdout */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize) +{ + struct NameValueParserData pdata; + struct NameValue * nv; + ParseNameValue(buffer, bufsize, &pdata); + for(nv = pdata.head.lh_first; + nv != NULL; + nv = nv->entries.le_next) + { + printf("%s = %s\n", nv->name, nv->value); + } + ClearNameValueList(&pdata); +} +#endif + diff --git a/upnpreplyparse.h b/upnpreplyparse.h new file mode 100644 index 0000000..8ddb04a --- /dev/null +++ b/upnpreplyparse.h @@ -0,0 +1,57 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __UPNPREPLYPARSE_H__ +#define __UPNPREPLYPARSE_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct NameValue { + LIST_ENTRY(NameValue) entries; + char name[64]; + char value[512]; +}; + +struct NameValueParserData { + LIST_HEAD(listhead, NameValue) head; + char curelt[64]; +}; + +/* ParseNameValue() */ +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data); + +/* ClearNameValueList() */ +void +ClearNameValueList(struct NameValueParserData * pdata); + +/* GetValueFromNameValueList() */ +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name); + +/* GetValueFromNameValueListIgnoreNS() */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name); + +/* DisplayNameValueList() */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/upnpsoap.c b/upnpsoap.c new file mode 100644 index 0000000..a037a40 --- /dev/null +++ b/upnpsoap.c @@ -0,0 +1,787 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "upnpglobalvars.h" +#include "upnphttp.h" +#include "upnpsoap.h" +#include "upnpreplyparse.h" +#include "getifaddr.h" + +#include +#include "metadata.h" +#include + +static void +BuildSendAndCloseSoapResp(struct upnphttp * h, + const char * body, int bodylen) +{ + static const char beforebody[] = + "\r\n" + "" + ""; + + static const char afterbody[] = + "" + "\r\n"; + + BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1 + + sizeof(afterbody) - 1 + bodylen ); + + memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1); + h->res_buflen += sizeof(beforebody) - 1; + + memcpy(h->res_buf + h->res_buflen, body, bodylen); + h->res_buflen += bodylen; + + memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1); + h->res_buflen += sizeof(afterbody) - 1; + + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + +static void +GetStatusInfo(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "Connected" + "ERROR_NONE" + "%ld" + ""; + + char body[512]; + int bodylen; + time_t uptime; + + uptime = (time(NULL) - startup_time); + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:WANIPConnection:1", + (long)uptime, action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void +GetSystemUpdateID(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "%d" + ""; + + char body[512]; + int bodylen; + + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:ContentDirectory:1", + 1, action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void +IsAuthorizedValidated(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "%d" + ""; + + char body[512]; + int bodylen; + + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", + 1, action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void +GetProtocolInfo(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "" + /*"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_CI=1," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01,"*/ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," + "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," + "http-get:*:audio/x-ms-wma:*," + "http-get:*:audio/wav:*," + "http-get:*:audio/mp4:*," + "http-get:*:audio/x-aiff:*," + "http-get:*:audio/x-flac:*," + "http-get:*:application/ogg:*," + "http-get:*:image/jpeg:*," + "http-get:*:image/gif:*," + "http-get:*:audio/x-mpegurl:*," + "http-get:*:video/mpeg:*," + "http-get:*:video/x-msvideo:*," + "http-get:*:video/avi:*," + "http-get:*:video/mpeg2:*," + "http-get:*:video/dvd:*," + "http-get:*:video/x-ms-wmv:*" + "" + "" + ""; + + + char body[1536]; + int bodylen; + + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:ConnectionManager:1", + action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void +GetSortCapabilities(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "" + ""; + + char body[512]; + int bodylen; + + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:ContentDirectory:1", + action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void +GetSearchCapabilities(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "dc:title,dc:creator,upnp:class,upnp:artist,upnp:album,@refID" + ""; + + char body[512]; + int bodylen; + + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:ContentDirectory:1", + action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void +GetCurrentConnectionIDs(struct upnphttp * h, const char * action) +{ + /* TODO: Use real data. - JM */ + static const char resp[] = + "" + "-1" + ""; + + char body[512]; + int bodylen; + + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:ConnectionManager:1", + action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void +GetCurrentConnectionInfo(struct upnphttp * h, const char * action) +{ + /* TODO: Use real data. - JM */ + static const char resp[] = + "" + "-1" + "-1" + "" + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," + "" + "0" + "-1" + "0" + "0" + ""; + + char body[sizeof(resp)+128]; + int bodylen; + + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:ConnectionManager:1", + action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static int callback(void *args, int argc, char **argv, char **azColName) +{ + struct Response { char *resp; int returned; int requested; int total; char *filter; } *passed_args = (struct Response *)args; + char *id = argv[1], *parent = argv[2], *refID = argv[3], *class = argv[4], *name = argv[7], *size = argv[9], + *title = argv[10], *duration = argv[11], *bitrate = argv[12], *sampleFrequency = argv[13], + *artist = argv[14], *album = argv[15], *genre = argv[16], *comment = argv[17], *nrAudioChannels = argv[18], + *track = argv[19], *date = argv[20], *width = argv[21], *height = argv[22], *tn = argv[23], + *creator = argv[24], *dlna_pn = argv[25], *mime = argv[26]; + char dlna_buf[64]; + char str_buf[4096]; + //char * str_buf = malloc(4096); + char **result; + int ret; + + passed_args->total++; + + if( passed_args->requested && (passed_args->returned >= passed_args->requested) ) + return 0; + //if( (strncmp(class, "item", 4) == 0) && !mime ) // Useless listing if there is no MIME type + // return 0; + passed_args->returned++; + + if( dlna_pn ) + //sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01", dlna_pn); + sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn); + else + strcpy(dlna_buf, "*"); + + /*for(i=0; iresp, str_buf); + if( refID && (!passed_args->filter || strstr(passed_args->filter, "@refID")) ) { + sprintf(str_buf, " refID=\"%s\"", refID); + strcat(passed_args->resp, str_buf); + } + sprintf(str_buf, ">" + "<dc:title>%s</dc:title>" + "<upnp:class>object.%s</upnp:class>", + title?title:name, class); + strcat(passed_args->resp, str_buf); + if( comment && (!passed_args->filter || strstr(passed_args->filter, "dc:description")) ) { + sprintf(str_buf, "<dc:description>%s</dc:description>", comment); + strcat(passed_args->resp, str_buf); + } + if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) { + sprintf(str_buf, "<dc:creator>%s</dc:creator>", creator); + strcat(passed_args->resp, str_buf); + } + if( date && (!passed_args->filter || strstr(passed_args->filter, "dc:date")) ) { + sprintf(str_buf, "<dc:date>%s</dc:date>", date); + strcat(passed_args->resp, str_buf); + } + if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) { + sprintf(str_buf, "<upnp:artist>%s</upnp:artist>", artist); + strcat(passed_args->resp, str_buf); + } + if( album && (!passed_args->filter || strstr(passed_args->filter, "upnp:album")) ) { + sprintf(str_buf, "<upnp:album>%s</upnp:album>", album); + strcat(passed_args->resp, str_buf); + } + if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) { + sprintf(str_buf, "<upnp:genre>%s</upnp:genre>", genre); + strcat(passed_args->resp, str_buf); + } + if( track && atoi(track) && (!passed_args->filter || strstr(passed_args->filter, "upnp:originalTrackNumber")) ) { + sprintf(str_buf, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track); + strcat(passed_args->resp, str_buf); + } + if( !passed_args->filter || strstr(passed_args->filter, "res") ) { + strcat(passed_args->resp, "<res "); + if( size && (!passed_args->filter || strstr(passed_args->filter, "res@size")) ) { + sprintf(str_buf, "size=\"%s\" ", size); + strcat(passed_args->resp, str_buf); + } + if( duration && (!passed_args->filter || strstr(passed_args->filter, "res@duration")) ) { + sprintf(str_buf, "duration=\"%s\" ", duration); + strcat(passed_args->resp, str_buf); + } + if( bitrate && (!passed_args->filter || strstr(passed_args->filter, "res@bitrate")) ) { + sprintf(str_buf, "bitrate=\"%s\" ", bitrate); + strcat(passed_args->resp, str_buf); + } + if( sampleFrequency && (!passed_args->filter || strstr(passed_args->filter, "res@sampleFrequency")) ) { + sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency); + strcat(passed_args->resp, str_buf); + } + if( nrAudioChannels && (!passed_args->filter || strstr(passed_args->filter, "res@nrAudioChannels")) ) { + sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels); + strcat(passed_args->resp, str_buf); + } + if( width && height && (!passed_args->filter || strstr(passed_args->filter, "res@resolution")) ) { + sprintf(str_buf, "resolution=\"%sx%s\" ", width, height); + strcat(passed_args->resp, str_buf); + } + sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" + "http://%s:5555/MediaItems/%s" + "</res>", + mime, dlna_buf, lan_addr[0].str, id); + #if 0 //JPEG_RESIZE + if( dlna_pn && (strncmp(dlna_pn, "JPEG_LRG", 8) == 0) ) { + strcat(passed_args->resp, str_buf); + sprintf(str_buf, "<res " + "protocolInfo=\"http-get:*:%s:%s\">" + "http://%s:5555/Resized/%s" + "</res>", + mime, "DLNA.ORG_PN=JPEG_SM", lan_addr[0].str, id); + } + #endif + if( tn && atoi(tn) && dlna_pn ) { + strcat(passed_args->resp, str_buf); + strcat(passed_args->resp, "<res "); + sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" + "http://%s:5555/Thumbnails/%s" + "</res>", + mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, id); + } + strcat(passed_args->resp, str_buf); + } + strcpy(str_buf, "</item>"); + } + else if( strncmp(class, "container", 9) == 0 ) + { + sprintf(str_buf, "SELECT count(*) from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '%s' order by d.TRACK, d.TITLE, o.NAME;", id); + ret = sqlite3_get_table(db, str_buf, &result, 0, 0, 0); + sprintf(str_buf, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent); + strcat(passed_args->resp, str_buf); + if( !passed_args->filter || strstr(passed_args->filter, "@childCount")) { + sprintf(str_buf, "childCount=\"%s\"", result[1]); + strcat(passed_args->resp, str_buf); + } + sprintf(str_buf, ">" + "<dc:title>%s</dc:title>" + "<upnp:class>object.%s</upnp:class>" + "</container>", + name, class); + sqlite3_free_table(result); + } + strcat(passed_args->resp, str_buf); + + return 0; +} + +static void +BrowseContentDirectory(struct upnphttp * h, const char * action) +{ + static const char resp0[] = + "" + "" + "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n"; + static const char resp1[] = "</DIDL-Lite>"; + static const char resp2[] = "0"; + + char *resp = calloc(1, 1048576); + strcpy(resp, resp0); + + char str_buf[4096]; + char str_buf2[4096]; + memset(str_buf, '\0', sizeof(str_buf)); + memset(str_buf2, '\0', sizeof(str_buf2)); + char *zErrMsg = 0; + int ret; + char sql_buf[4096]; + struct Response { char *resp; int returned; int requested; int total; char *filter; } args; + + struct NameValueParserData data; + ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); + int RequestedCount = atoi( GetValueFromNameValueList(&data, "RequestedCount") ); + int StartingIndex = atoi( GetValueFromNameValueList(&data, "StartingIndex") ); + char * ObjectId = GetValueFromNameValueList(&data, "ObjectID"); + char * Filter = GetValueFromNameValueList(&data, "Filter"); + char * BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag"); + char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); + if( !ObjectId ) + ObjectId = GetValueFromNameValueList(&data, "ContainerID"); + + memset(&args, 0, sizeof(args)); + args.total = 0; + args.returned = 0; + args.requested = RequestedCount; + args.resp = NULL; + args.filter = NULL; + printf("Asked for ObjectID: %s\n", ObjectId); + printf("Asked for Count: %d\n", RequestedCount); + printf("Asked for StartingIndex: %d\n", StartingIndex); + printf("Asked for BrowseFlag: %s\n", BrowseFlag); + printf("Asked for Filter: %s\n", Filter); + if( SortCriteria ) printf("Asked for SortCriteria: %s\n", SortCriteria); + + if( !Filter ) + { + ClearNameValueList(&data); + SoapError(h, 402, "Invalid Args"); + return; + } + if( strlen(Filter) > 1 ) + args.filter = Filter; + + args.resp = resp; + if( strcmp(BrowseFlag, "BrowseMetadata") == 0 ) + { + args.requested = 1; + sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s';", ObjectId); + ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg); + } + else + { + sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " where PARENT_ID = '%s' order by d.TRACK, d.TITLE, o.NAME limit %d, -1;", + ObjectId, StartingIndex); + ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg); + } + if( ret != SQLITE_OK ){ + printf("SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + strcat(resp, resp1); + sprintf(str_buf, "\n%u\n%u\n", args.returned, args.total); + strcat(resp, str_buf); + strcat(resp, resp2); + BuildSendAndCloseSoapResp(h, resp, strlen(resp)); + ClearNameValueList(&data); + free(resp); +} + +static void +SearchContentDirectory(struct upnphttp * h, const char * action) +{ + static const char resp0[] = + "" + "" + "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n"; + static const char resp1[] = "</DIDL-Lite>"; + static const char resp2[] = "0"; + + char *resp = calloc(8, 16384); + strcpy(resp, resp0); + + char str_buf[4096]; + char str_buf2[4096]; + memset(str_buf, '\0', sizeof(str_buf)); + memset(str_buf2, '\0', sizeof(str_buf2)); + struct Response { char *resp; int returned; int requested; int total; char *filter; } args; + + struct NameValueParserData data; + ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); + int RequestedCount = atoi( GetValueFromNameValueList(&data, "RequestedCount") ); + int StartingIndex = atoi( GetValueFromNameValueList(&data, "StartingIndex") ); + char * ContainerID = GetValueFromNameValueList(&data, "ContainerID"); + char * Filter = GetValueFromNameValueList(&data, "Filter"); + char * SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria"); + char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); + + memset(&args, 0, sizeof(args)); + args.total = 0; + args.returned = 0; + args.requested = RequestedCount; + args.resp = NULL; + args.filter = NULL; + printf("Asked for ContainerID: %s\n", ContainerID); + printf("Asked for Count: %d\n", RequestedCount); + printf("Asked for StartingIndex: %d\n", StartingIndex); + printf("Asked for SearchCriteria: %s\n", SearchCriteria); + printf("Asked for Filter: %s\n", Filter); + if( SortCriteria ) printf("Asked for SortCriteria: %s\n", SortCriteria); + + if( !Filter ) + { + ClearNameValueList(&data); + SoapError(h, 402, "Invalid Args"); + return; + } + if( strlen(Filter) > 1 ) + args.filter = Filter; + if( strcmp(ContainerID, "0") == 0 ) + *ContainerID = '%'; + if( !SearchCriteria ) + { + asprintf(&SearchCriteria, "1 = 1"); + } + else + { + SearchCriteria = modifyString(SearchCriteria, """, "\"", 0); + SearchCriteria = modifyString(SearchCriteria, "'", "'", 0); + SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1); + SearchCriteria = modifyString(SearchCriteria, "contains", "like", 1); + SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0); + SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0); + SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0); + SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0); + SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0); + SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0); + SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0); + SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0); + SearchCriteria = modifyString(SearchCriteria, "object.", "", 0); + } + printf("Asked for SearchCriteria: %s\n", SearchCriteria); + + char *zErrMsg = 0; + int ret; + char sql_buf[4096]; + args.resp = resp; + sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " where OBJECT_ID like '%s$%%' and (%s) order by d.TRACK, d.TITLE, o.NAME limit %d, -1;", + ContainerID, SearchCriteria, StartingIndex); + printf("Search SQL: %s\n", sql_buf); + ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg); + if( ret != SQLITE_OK ){ + printf("SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + strcat(resp, resp1); + sprintf(str_buf, "\n%u\n%u\n", args.returned, args.total); + strcat(resp, str_buf); + strcat(resp, resp2); + BuildSendAndCloseSoapResp(h, resp, strlen(resp)); + ClearNameValueList(&data); + free(resp); +} + +static void +GetExternalIPAddress(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "%s" + ""; + + char body[512]; + int bodylen; + char ext_ip_addr[INET_ADDRSTRLEN]; + +#ifndef MULTIPLE_EXTERNAL_IP + if(use_ext_ip_addr) + { + strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN); + } + else if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN) < 0) + { + syslog(LOG_ERR, "Failed to get ip address for interface %s", + ext_if_name); + strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); + } +#else + int i; + strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); + for(i = 0; iclientaddr.s_addr & lan_addr[i].mask.s_addr) + == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) + { + strncpy(ext_ip_addr, lan_addr[i].ext_ip_str, INET_ADDRSTRLEN); + break; + } + } +#endif + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:WANIPConnection:1", + ext_ip_addr, action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +/* +If a control point calls QueryStateVariable on a state variable that is not +buffered in memory within (or otherwise available from) the service, +the service must return a SOAP fault with an errorCode of 404 Invalid Var. + +QueryStateVariable remains useful as a limited test tool but may not be +part of some future versions of UPnP. +*/ +static void +QueryStateVariable(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "" + "%s" + ""; + + char body[512]; + int bodylen; + struct NameValueParserData data; + const char * var_name; + + ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); + /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */ + /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/ + var_name = GetValueFromNameValueList(&data, "varName"); + + /*syslog(LOG_INFO, "QueryStateVariable(%.40s)", var_name); */ + + if(!var_name) + { + SoapError(h, 402, "Invalid Args"); + } + else if(strcmp(var_name, "ConnectionStatus") == 0) + { + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:control-1-0", + "Connected", action); + BuildSendAndCloseSoapResp(h, body, bodylen); + } +#if 0 + /* not usefull */ + else if(strcmp(var_name, "ConnectionType") == 0) + { + bodylen = snprintf(body, sizeof(body), resp, "IP_Routed"); + BuildSendAndCloseSoapResp(h, body, bodylen); + } + else if(strcmp(var_name, "LastConnectionError") == 0) + { + bodylen = snprintf(body, sizeof(body), resp, "ERROR_NONE"); + BuildSendAndCloseSoapResp(h, body, bodylen); + } +#endif + else + { + syslog(LOG_NOTICE, "%s: Unknown: %s", action, var_name?var_name:""); + SoapError(h, 404, "Invalid Var"); + } + + ClearNameValueList(&data); +} + +static const struct +{ + const char * methodName; + void (*methodImpl)(struct upnphttp *, const char *); +} +soapMethods[] = +{ + { "GetExternalIPAddress", GetExternalIPAddress}, + { "QueryStateVariable", QueryStateVariable}, + { "GetStatusInfo", GetStatusInfo}, + { "Browse", BrowseContentDirectory}, + { "Search", SearchContentDirectory}, + { "GetSearchCapabilities", GetSearchCapabilities}, + { "GetSortCapabilities", GetSortCapabilities}, + { "GetSystemUpdateID", GetSystemUpdateID}, + { "GetProtocolInfo", GetProtocolInfo}, + { "GetCurrentConnectionIDs", GetCurrentConnectionIDs}, + { "GetCurrentConnectionInfo", GetCurrentConnectionInfo}, + { "IsAuthorized", IsAuthorizedValidated}, + { "IsValidated", IsAuthorizedValidated}, + { 0, 0 } +}; + +void +ExecuteSoapAction(struct upnphttp * h, const char * action, int n) +{ + char * p; + char * p2; + int i, len, methodlen; + + i = 0; + p = strchr(action, '#'); + + if(p) + { + p++; + p2 = strchr(p, '"'); + if(p2) + methodlen = p2 - p; + else + methodlen = n - (p - action); + /*syslog(LOG_DEBUG, "SoapMethod: %.*s", methodlen, p);*/ + while(soapMethods[i].methodName) + { + len = strlen(soapMethods[i].methodName); + if(strncmp(p, soapMethods[i].methodName, len) == 0) + { + soapMethods[i].methodImpl(h, soapMethods[i].methodName); + return; + } + i++; + } + + syslog(LOG_NOTICE, "SoapMethod: Unknown: %.*s", methodlen, p); + } + + SoapError(h, 401, "Invalid Action"); +} + +/* Standard Errors: + * + * errorCode errorDescription Description + * -------- ---------------- ----------- + * 401 Invalid Action No action by that name at this service. + * 402 Invalid Args Could be any of the following: not enough in args, + * too many in args, no in arg by that name, + * one or more in args are of the wrong data type. + * 403 Out of Sync Out of synchronization. + * 501 Action Failed May be returned in current state of service + * prevents invoking that action. + * 600-699 TBD Common action errors. Defined by UPnP Forum + * Technical Committee. + * 700-799 TBD Action-specific errors for standard actions. + * Defined by UPnP Forum working committee. + * 800-899 TBD Action-specific errors for non-standard actions. + * Defined by UPnP vendor. +*/ +void +SoapError(struct upnphttp * h, int errCode, const char * errDesc) +{ + static const char resp[] = + "" + "" + "" + "s:Client" + "UPnPError" + "" + "" + "%d" + "%s" + "" + "" + "" + "" + ""; + + char body[2048]; + int bodylen; + + syslog(LOG_INFO, "Returning UPnPError %d: %s", errCode, errDesc); + bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc); + BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + diff --git a/upnpsoap.h b/upnpsoap.h new file mode 100644 index 0000000..ae5b031 --- /dev/null +++ b/upnpsoap.h @@ -0,0 +1,22 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __UPNPSOAP_H__ +#define __UPNPSOAP_H__ + +/* ExecuteSoapAction(): + * this method executes the requested Soap Action */ +void +ExecuteSoapAction(struct upnphttp *, const char *, int); + +/* SoapError(): + * sends a correct SOAP error with an UPNPError code and + * description */ +void +SoapError(struct upnphttp * h, int errCode, const char * errDesc); + +#endif +