From f557f8ea77fe88259fc374e1318e74200e51c21c Mon Sep 17 00:00:00 2001 From: Justin Maggard Date: Thu, 23 Oct 2008 17:30:45 +0000 Subject: [PATCH] Initial checkin --- Changelog.txt | 433 ++++++++++++ INSTALL | 14 + LICENCE | 346 ++++++++++ LICENCE.miniupnpd | 26 + Makefile | 101 +++ README | 25 + commonrdr.h | 35 + config.h | 55 ++ daemonize.c | 129 ++++ daemonize.h | 34 + genconfig.sh | 179 +++++ getifaddr.c | 55 ++ getifaddr.h | 18 + linux/miniupnpd.init.d.script | 40 ++ metadata.c | 406 +++++++++++ metadata.h | 25 + minidlna.c | 872 +++++++++++++++++++++++ minissdp.c | 473 +++++++++++++ minissdp.h | 43 ++ miniupnpd.1 | 73 ++ miniupnpd.conf | 73 ++ miniupnpdpath.h | 47 ++ miniupnpdtypes.h | 23 + minixml.c | 191 +++++ minixml.h | 36 + options.c | 173 +++++ options.h | 69 ++ scanner.c | 510 ++++++++++++++ scanner.h | 21 + sql.c | 53 ++ sql.h | 21 + testupnpdescgen.c | 121 ++++ upnpdescgen.c | 933 +++++++++++++++++++++++++ upnpdescgen.c.dlna | 944 +++++++++++++++++++++++++ upnpdescgen.h | 94 +++ upnpdescstrings.h | 38 + upnpevents.c | 478 +++++++++++++ upnpevents.h | 40 ++ upnpglobalvars.c | 81 +++ upnpglobalvars.h | 95 +++ upnphttp.c | 1226 +++++++++++++++++++++++++++++++++ upnphttp.h | 133 ++++ upnpreplyparse.c | 127 ++++ upnpreplyparse.h | 57 ++ upnpsoap.c | 787 +++++++++++++++++++++ upnpsoap.h | 22 + 46 files changed, 9775 insertions(+) create mode 100644 Changelog.txt create mode 100644 INSTALL create mode 100644 LICENCE create mode 100644 LICENCE.miniupnpd create mode 100644 Makefile create mode 100644 README create mode 100644 commonrdr.h create mode 100644 config.h create mode 100644 daemonize.c create mode 100644 daemonize.h create mode 100755 genconfig.sh create mode 100644 getifaddr.c create mode 100644 getifaddr.h create mode 100644 linux/miniupnpd.init.d.script create mode 100644 metadata.c create mode 100644 metadata.h create mode 100644 minidlna.c create mode 100644 minissdp.c create mode 100644 minissdp.h create mode 100644 miniupnpd.1 create mode 100644 miniupnpd.conf create mode 100644 miniupnpdpath.h create mode 100644 miniupnpdtypes.h create mode 100644 minixml.c create mode 100644 minixml.h create mode 100644 options.c create mode 100644 options.h create mode 100644 scanner.c create mode 100644 scanner.h create mode 100644 sql.c create mode 100644 sql.h create mode 100644 testupnpdescgen.c create mode 100644 upnpdescgen.c create mode 100644 upnpdescgen.c.dlna create mode 100644 upnpdescgen.h create mode 100644 upnpdescstrings.h create mode 100644 upnpevents.c create mode 100644 upnpevents.h create mode 100644 upnpglobalvars.c create mode 100644 upnpglobalvars.h create mode 100644 upnphttp.c create mode 100644 upnphttp.h create mode 100644 upnpreplyparse.c create mode 100644 upnpreplyparse.h create mode 100644 upnpsoap.c create mode 100644 upnpsoap.h 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 +