Initial checkin

This commit is contained in:
Justin Maggard
2008-10-23 17:30:45 +00:00
commit f557f8ea77
46 changed files with 9775 additions and 0 deletions

433
Changelog.txt Normal file
View File

@ -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

14
INSTALL Normal file
View File

@ -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. :)

346
LICENCE Normal file
View File

@ -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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

26
LICENCE.miniupnpd Normal file
View File

@ -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.

101
Makefile Normal file
View File

@ -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

25
README Normal file
View File

@ -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

35
commonrdr.h Normal file
View File

@ -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

55
config.h Normal file
View File

@ -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

129
daemonize.c Normal file
View File

@ -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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <signal.h>
#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;
}

34
daemonize.h Normal file
View File

@ -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

179
genconfig.sh Executable file
View File

@ -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

55
getifaddr.c Normal file
View File

@ -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 <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#if defined(sun)
#include <sys/sockio.h>
#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;
}

18
getifaddr.h Normal file
View File

@ -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

View File

@ -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

406
metadata.c Normal file
View File

@ -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 <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sqlite3.h>
#include <taglib/tag_c.h>
#include <libexif/exif-loader.h>
#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;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;amp;", 0);
}
}
else
{
artist = NULL;
}
album = taglib_tag_album(tag);
if( strlen(album) )
{
album = trim(album);
if( index(album, '&') )
{
album = modifyString(strdup(album), "&", "&amp;amp;", 0);
}
}
else
{
album = NULL;
}
genre = taglib_tag_genre(tag);
if( strlen(genre) )
{
genre = trim(genre);
if( index(genre, '&') )
{
genre = modifyString(strdup(genre), "&", "&amp;amp;", 0);
}
}
else
{
genre = NULL;
}
comment = taglib_tag_comment(tag);
if( strlen(comment) )
{
comment = trim(comment);
if( index(comment, '&') )
{
comment = modifyString(strdup(comment), "&", "&amp;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;
}

25
metadata.h Normal file
View File

@ -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

872
minidlna.c Normal file
View File

@ -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 <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/file.h>
#include <syslog.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <sys/param.h>
#if defined(sun)
#include <kstat.h>
#else
/* for BSD's sysctl */
#include <sys/sysctl.h>
#endif
#include <sqlite3.h>
/* 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; i<argc; i++)
{
if(0 == strcmp(argv[i-1], "-f"))
{
optionsfile = argv[i];
options_flag = 1;
break;
}
}
/*v->n_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; i<num_options; i++)
{
switch(ary_options[i].id)
{
case UPNPEXT_IFNAME:
ext_if_name = ary_options[i].value;
break;
case UPNPEXT_IP:
use_ext_ip_addr = ary_options[i].value;
break;
case UPNPLISTENING_IP:
if(n_lan_addr < MAX_LAN_ADDR)/* if(v->n_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; i<argc; i++)
{
if(argv[i][0]!='-')
{
fprintf(stderr, "Unknown option: %s\n", argv[i]);
}
else switch(argv[i][1])
{
case 'o':
if(i+1 < argc)
use_ext_ip_addr = argv[++i];
else
fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]);
break;
case 't':
if(i+1 < argc)
v->notify_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+2<argc)
{
downstream_bitrate = strtoul(argv[++i], 0, 0);
upstream_bitrate = strtoul(argv[++i], 0, 0);
}
else
fprintf(stderr, "Option -%c takes two arguments.\n", argv[i][1]);
break;
case 'a':
if(i+1 < argc)
{
int address_already_there = 0;
int j;
i++;
for(j=0; j<n_lan_addr; j++)/* for(j=0; j<v->n_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<n_lan_addr; i++)/* for(i=0; i<v.n_lan_addr; i++)*/
close(snotify[i]);
sqlite3_close(db);
if(unlink(pidfilename) < 0)
{
syslog(LOG_ERR, "Failed to remove pidfile %s: %m", pidfilename);
}
closelog();
freeoptions();
return 0;
}

473
minissdp.c Normal file
View File

@ -0,0 +1,473 @@
/* $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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>
#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<n_lan_addr; i++)
{
sockets[i] = OpenAndConfSSDPNotifySocket(lan_addr[i].addr.s_addr);
if(sockets[i] < 0)
{
for(j=0; j<i; j++)
{
close(sockets[j]);
sockets[j] = -1;
}
return -1;
}
}
return 0;
}
/*
* response from a LiveBox (Wanadoo)
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Thu, 01 Jan 1970 04:03:23 GMT
EXT:
LOCATION: http://192.168.0.1:49152/gatedesc.xml
SERVER: Linux/2.4.17, UPnP/1.0, Intel SDK for UPnP devices /1.2
ST: upnp:rootdevice
USN: uuid:75802409-bccb-40e7-8e6c-fa095ecce13e::upnp:rootdevice
* response from a Linksys 802.11b :
HTTP/1.1 200 OK
Cache-Control:max-age=120
Location:http://192.168.5.1:5678/rootDesc.xml
Server:NT/5.0 UPnP/1.0
ST:upnp:rootdevice
USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice
EXT:
*/
/* not really an SSDP "announce" as it is the response
* to a SSDP "M-SEARCH" */
static void
SendSSDPAnnounce2(int s, struct sockaddr_in sockname,
const char * st, int st_len, const char * suffix,
const char * host, unsigned short port)
{
int l, n;
char buf[512];
/* TODO :
* follow guideline from document "UPnP Device Architecture 1.0"
* put in uppercase.
* DATE: is recommended
* SERVER: OS/ver UPnP/1.0 miniupnpd/1.0
* - check what to put in the 'Cache-Control' header
* */
char szTime[30];
time_t tTime = time(NULL);
strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&tTime));
l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
"CACHE-CONTROL: max-age=1810\r\n"
"DATE: %s\r\n"
"Ext:\r\n"
"ST: %.*s%s\r\n"
"USN: %s::%.*s%s\r\n"
"EXT:\r\n"
"SERVER: " MINIUPNPD_SERVER_STRING "\r\n"
"LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
"Content-Length: 0\r\n"
"\r\n",
szTime,
st_len, st, suffix,
uuidvalue, st_len, st, suffix,
host, (unsigned int)port);
n = sendto(s, buf, l, 0,
(struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
if(n < 0)
{
syslog(LOG_ERR, "sendto(udp): %m");
}
}
static const char * const known_service_types[] =
{
"upnp:rootdevice",
"urn:schemas-upnp-org:device:MediaServer:",
"urn:schemas-upnp-org:service:ContentDirectory:",
"urn:schemas-upnp-org:service:ConnectionManager:",
"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:",
uuidvalue,
0
};
static void
SendSSDPNotifies(int s, const char * host, unsigned short port,
unsigned int lifetime)
{
struct sockaddr_in sockname;
int l, n, dup, i=0;
char bufr[512];
memset(&sockname, 0, sizeof(struct sockaddr_in));
sockname.sin_family = AF_INET;
sockname.sin_port = htons(SSDP_PORT);
sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
for( dup=0; dup<2; dup++ )
{
if( dup )
usleep(200000);
i=0;
while(known_service_types[i])
{
l = snprintf(bufr, sizeof(bufr),
"NOTIFY * HTTP/1.1\r\n"
"HOST:%s:%d\r\n"
"CACHE-CONTROL:max-age=%u\r\n"
"LOCATION:http://%s:%d" ROOTDESC_PATH"\r\n"
"SERVER: " MINIUPNPD_SERVER_STRING "\r\n"
"NT:%s%s\r\n"
"USN:%s::%s%s\r\n"
"NTS:ssdp:alive\r\n"
"\r\n",
SSDP_MCAST_ADDR, SSDP_PORT,
lifetime,
host, port,
known_service_types[i], (i==0?"":"1"),
uuidvalue, known_service_types[i], (i==0?"":"1") );
if(l>=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<n_lan_addr; i++)
{
SendSSDPNotifies(sockets[i], lan_addr[i].str, port, lifetime);
}
}
/* ProcessSSDPRequest()
* process SSDP M-SEARCH requests and responds to them */
void
ProcessSSDPRequest(int s, unsigned short port)
/*ProcessSSDPRequest(int s, struct lan_addr_s * lan_addr, int n_lan_addr,
unsigned short port)*/
{
int n;
char bufr[1500];
socklen_t len_r;
struct sockaddr_in sendername;
int i, l;
int lan_addr_index = 0;
char * st = 0;
int st_len = 0;
len_r = sizeof(struct sockaddr_in);
n = recvfrom(s, bufr, sizeof(bufr), 0,
(struct sockaddr *)&sendername, &len_r);
if(n < 0)
{
syslog(LOG_ERR, "recvfrom(udp): %m");
return;
}
if(memcmp(bufr, "NOTIFY", 6) == 0)
{
/* ignore NOTIFY packets. We could log the sender and device type */
return;
}
else if(memcmp(bufr, "M-SEARCH", 8) == 0)
{
i = 0;
while(i < n)
{
while(bufr[i] != '\r' || bufr[i+1] != '\n')
i++;
i += 2;
if(strncasecmp(bufr+i, "st:", 3) == 0)
{
st = bufr+i+3;
st_len = 0;
while(*st == ' ' || *st == '\t') st++;
while(st[st_len]!='\r' && st[st_len]!='\n') st_len++;
/*syslog(LOG_INFO, "ST: %.*s", st_len, st);*/
/*j = 0;*/
/*while(bufr[i+j]!='\r') j++;*/
/*syslog(LOG_INFO, "%.*s", j, bufr+i);*/
}
}
/*syslog(LOG_INFO, "SSDP M-SEARCH packet received from %s:%d",
inet_ntoa(sendername.sin_addr),
ntohs(sendername.sin_port) );*/
if( ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900 )
{
syslog(LOG_INFO, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]",
inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
}
else if(st)
{
/* TODO : doesnt answer at once but wait for a random time */
syslog(LOG_INFO, "SSDP M-SEARCH from %s:%d ST: %.*s",
inet_ntoa(sendername.sin_addr),
ntohs(sendername.sin_port),
st_len, st);
/* find in which sub network the client is */
for(i = 0; i<n_lan_addr; i++)
{
if( (sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr)
== (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
{
lan_addr_index = i;
break;
}
}
/* Responds to request with a device as ST header */
for(i = 0; known_service_types[i]; i++)
{
l = (int)strlen(known_service_types[i]);
if(l<=st_len && (0 == memcmp(st, known_service_types[i], l)))
{
SendSSDPAnnounce2(s, sendername,
st, st_len, "",
lan_addr[lan_addr_index].str, port);
break;
}
}
/* Responds to request with ST: ssdp:all */
/* strlen("ssdp:all") == 8 */
if(st_len==8 && (0 == memcmp(st, "ssdp:all", 8)))
{
for(i=0; known_service_types[i]; i++)
{
l = (int)strlen(known_service_types[i]);
SendSSDPAnnounce2(s, sendername,
known_service_types[i], l, i==0?"":"1",
lan_addr[lan_addr_index].str, port);
}
}
/* responds to request by UUID value */
l = (int)strlen(uuidvalue);
if(l==st_len && (0 == memcmp(st, uuidvalue, l)))
{
SendSSDPAnnounce2(s, sendername, st, st_len, "",
lan_addr[lan_addr_index].str, port);
}
}
else
{
syslog(LOG_INFO, "Invalid SSDP M-SEARCH from %s:%d",
inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
}
}
else
{
syslog(LOG_NOTICE, "Unknown udp packet received from %s:%d",
inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
}
}
/* This will broadcast ssdp:byebye notifications to inform
* the network that UPnP is going down. */
int
SendSSDPGoodbye(int * sockets, int n_sockets)
{
struct sockaddr_in sockname;
int n, l;
int i, j;
char bufr[512];
memset(&sockname, 0, sizeof(struct sockaddr_in));
sockname.sin_family = AF_INET;
sockname.sin_port = htons(SSDP_PORT);
sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
for(j=0; j<n_sockets; j++)
{
for(i=0; known_service_types[i]; i++)
{
l = snprintf(bufr, sizeof(bufr),
"NOTIFY * HTTP/1.1\r\n"
"HOST:%s:%d\r\n"
"NT:%s%s\r\n"
"USN:%s::%s%s\r\n"
"NTS:ssdp:byebye\r\n"
"\r\n",
SSDP_MCAST_ADDR, SSDP_PORT,
known_service_types[i], (i==0?"":"1"),
uuidvalue, known_service_types[i], (i==0?"":"1"));
n = sendto(sockets[j], bufr, l, 0,
(struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
if(n < 0)
{
syslog(LOG_ERR, "sendto(udp_shutdown=%d): %m", sockets[j]);
return -1;
}
}
}
return 0;
}

43
minissdp.h Normal file
View File

@ -0,0 +1,43 @@
/* MiniUPnP project
* 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 */
#ifndef __MINISSDP_H__
#define __MINISSDP_H__
/*#include "miniupnpdtypes.h"*/
int
OpenAndConfSSDPReceiveSocket();
/* OpenAndConfSSDPReceiveSocket(int n_lan_addr, struct lan_addr_s * lan_addr);*/
/*int
OpenAndConfSSDPNotifySocket(const char * addr);*/
int
OpenAndConfSSDPNotifySockets(int * sockets);
/*OpenAndConfSSDPNotifySockets(int * sockets,
struct lan_addr_s * lan_addr, int n_lan_addr);*/
/*void
SendSSDPNotifies(int s, const char * host, unsigned short port,
unsigned int lifetime);*/
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);*/
void
ProcessSSDPRequest(int s, unsigned short port);
/*ProcessSSDPRequest(int s, struct lan_addr_s * lan_addr, int n_lan_addr,
unsigned short port);*/
int
SendSSDPGoodbye(int * sockets, int n);
#endif

73
miniupnpd.1 Normal file
View File

@ -0,0 +1,73 @@
.TH miniupnpd 1
.SH NAME
miniupnpd \- UPnP Internet Gateway Device Daemon
.SH SYNOPSIS
.B miniupnpd
[-f file] [-i interface] [-o address]
[-a address] [-p port] [-d] [-L] [-U]
[-u uuid] [-s serial] [-m model_number]
[-q queue]
[-t interval] [-P file]
[-B down up] [-w url]
.SH DESCRIPTION
miniupnpd act as a UPnP Internet Gateway Device. It is designed
to run on the gateway between the internet and a NAT'ed LAN. It provides
an interface, as defined in the UPnP standard, for enabling
clients on the LAN to ask for port redirections.
.SH OPTIONS
.TP
.B \-f file
load the config from file. default is /etc/miniupnpd.conf.
.TP
.B \-i interface
interface used to connect to the internet.
.TP
.B \-o address
address used to connect to the internet.
default address of the interface will be used if not specified.
.TP
.B \-a address
address on the LAN. -a option can by used multiple time if LAN is
subdivised in several subnetworks.
.TP
.B \-p port
port used for HTTP.
.TP
.B \-d
debug mode : do not go to background, output messages on console
and do not filter out low priority messages.
.TP
.B \-L
set packet log in pf on
.TP
.B \-q queue
set ALTQ queue in pf. filter rules must be enabled for this option
to have any effect.
.TP
.B \-U
report system uptime instead of daemon uptime to clients.
.TP
.B \-u uuid
set the uuid of the UPnP Internet Gateway Device.
.TP
.B \-s serial
serial number for the UPnP Internet Gateway Device.
.TP
.B \-m number
model number for the UPnP Internet Gateway Device.
.TP
.B \-t interval
SSDP notify interval in seconds :
SSDP announce messages will be broadcasted at this interval.
.TP
.B \-P file
pid file. default is /var/run/miniupnpd.pid
.TP
.B \-B down up
download and upload bitrates reported to clients.
.TP
.B \-w url
presentation url. default is first address on LAN, port 80.
.SH "SEE ALSO"
minissdpd(1) miniupnpc(3)
.SH BUGS

73
miniupnpd.conf Normal file
View File

@ -0,0 +1,73 @@
# WAN network interface
ext_ifname=eth0
# if the WAN interface has several IP addresses, you
# can specify the one to use below
#ext_ip=
# LAN network interfaces IPs / networks
# there can be multiple listening ips for SSDP traffic.
# should be under the form nnn.nnn.nnn.nnn/nn
# HTTP is available on all interfaces
#listening_ip=192.168.0.1/24
#listening_ip=10.1.11.65/24
#listening_ip=192.168.10.112
#listening_ip=
# port for HTTP (descriptions and SOAP) traffic
port=5555
# enable UPNP support (default is yes)
enable_upnp=yes
media_dir=/opt
#media_dir=/c/DLNA
# lease file location
#lease_file=/var/log/upnp.leases
# bitrates reported by daemon in bits per second
bitrate_up=1000000
bitrate_down=10000000
# "secure" mode : UPnP client are allowed to add mappings only
# to their IP
secure_mode=no
# default presentation url is http address on port 80
#presentation_url=http://www.mylan/index.php
# report system uptime instead of daemon uptime
system_uptime=no
# notify interval in seconds. default is 30 seconds.
notify_interval=900
# unused rules cleaning.
# never remove any rule before this threshold for the number
# of redirections is exceeded. default to 20
#clean_ruleset_threshold=10
# clean process work interval in seconds. default to 0 (disabled).
# a 600 seconds (10 minutes) interval makes sense
clean_ruleset_interval=600
# log packets in pf
#packet_log=no
# ALTQ queue in pf
# filter rules must be used for this to be used.
# compile with PF_ENABLE_FILTER_RULES (see config.h file)
#queue=queue_name1
# tag name in pf
#tag=tag_name1
# make filter rules in pf quick or not. default is yes
# active when compiled with PF_ENABLE_FILTER_RULES (see config.h file)
#quickrules=no
# uuid : generate your own with "make genuuid"
uuid=fc4ec57e-b051-11db-88f8-0060085db3f6
# serial and model number the daemon will report to clients
# in its XML description
serial=12345678
model_number=1

47
miniupnpdpath.h Normal file
View File

@ -0,0 +1,47 @@
/* 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 __MINIUPNPDPATH_H__
#define __MINIUPNPDPATH_H__
#include "config.h"
/* Paths and other URLs in the miniupnpd http server */
#define ROOTDESC_PATH "/rootDesc.xml"
#ifdef HAS_DUMMY_SERVICE
#define DUMMY_PATH "/dummy.xml"
#endif
#define WANCFG_PATH "/WANCfg.xml"
#define WANCFG_CONTROLURL "/ctl/CmnIfCfg"
#define WANCFG_EVENTURL "/evt/CmnIfCfg"
#define WANIPC_PATH "/WANIPCn.xml"
#define WANIPC_CONTROLURL "/ctl/IPConn"
#define WANIPC_EVENTURL "/evt/IPConn"
#define CONTENTDIRECTORY_PATH "/ContentDir.xml"
#define CONTENTDIRECTORY_CONTROLURL "/ctl/ContentDir"
#define CONTENTDIRECTORY_EVENTURL "/evt/ContentDir"
#define CONNECTIONMGR_PATH "/ConnectionMgr.xml"
#define CONNECTIONMGR_CONTROLURL "/ctl/ConnectionMgr"
#define CONNECTIONMGR_EVENTURL "/evt/ConnectionMgr"
#define X_MS_MEDIARECEIVERREGISTRAR_PATH "/X_MS_MediaReceiverRegistrar.xml"
#define X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL "/ctl/X_MS_MediaReceiverRegistrar"
#define X_MS_MEDIARECEIVERREGISTRAR_EVENTURL "/evt/X_MS_MediaReceiverRegistrar"
#ifdef ENABLE_L3F_SERVICE
#define L3F_PATH "/L3F.xml"
#define L3F_CONTROLURL "/ctl/L3F"
#define L3F_EVENTURL "/evt/L3F"
#endif
#endif

23
miniupnpdtypes.h Normal file
View File

@ -0,0 +1,23 @@
/* MiniUPnP project
* 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 */
#ifndef __MINIUPNPDTYPES_H__
#define __MINIUPNPDTYPES_H__
#include "config.h"
#include <netinet/in.h>
/* 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

191
minixml.c Normal file
View File

@ -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);
}

36
minixml.h Normal file
View File

@ -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

173
options.c Normal file
View File

@ -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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <syslog.h>
#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<sizeof(optionids)/sizeof(optionids[0]); i++)
{
/*printf("%2d %2d %s %s\n", i, optionids[i].id, name,
optionids[i].name); */
if(0 == strcmp(name, optionids[i].name))
{
id = optionids[i].id;
break;
}
}
if(id == UPNP_INVALID)
{
fprintf(stderr, "parsing error file %s line %d : %s=%s\n",
fname, linenum, name, value);
}
else
{
num_options += 1;
ary_options = (struct option *) realloc(ary_options, num_options * sizeof(struct option));
ary_options[num_options-1].id = id;
strncpy(ary_options[num_options-1].value, value, MAX_OPTION_VALUE_LEN);
}
}
fclose(hfile);
return 0;
}
void
freeoptions(void)
{
if(ary_options)
{
free(ary_options);
ary_options = NULL;
num_options = 0;
}
}

69
options.h Normal file
View File

@ -0,0 +1,69 @@
/* 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 */
#ifndef __OPTIONS_H__
#define __OPTIONS_H__
#include "config.h"
/* enum of option available in the miniupnpd.conf */
enum upnpconfigoptions {
UPNP_INVALID = 0,
UPNPEXT_IFNAME = 1, /* 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 */
UPNPENABLENATPMP, /* enable_natpmp */
#ifdef USE_PF
UPNPQUEUE, /* queue */
UPNPTAG, /* tag */
#endif
#ifdef PF_ENABLE_FILTER_RULES
UPNPQUICKRULES, /* quickrules */
#endif
UPNPSECUREMODE, /* secure_mode */
#ifdef ENABLE_LEASEFILE
UPNPLEASEFILE, /* lease_file */
#endif
UPNPMEDIADIR, /* directory to search for UPnP-A/V content */
UPNPENABLE /* enable_upnp */
};
/* readoptionsfile()
* parse and store the option file values
* returns: 0 success, -1 failure */
int
readoptionsfile(const char * fname);
/* freeoptions()
* frees memory allocated to option values */
void
freeoptions(void);
#define MAX_OPTION_VALUE_LEN (80)
struct option
{
enum upnpconfigoptions id;
char value[MAX_OPTION_VALUE_LEN];
};
extern struct option * ary_options;
extern int num_options;
#endif

510
scanner.c Normal file
View File

@ -0,0 +1,510 @@
/* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <locale.h>
#include <sys/stat.h>
#include <sqlite3.h>
#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;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("..");
}

21
scanner.h Normal file
View File

@ -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

53
sql.c Normal file
View File

@ -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 <stdio.h>
#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;
}

21
sql.h Normal file
View File

@ -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 <sqlite3.h>
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

121
testupnpdescgen.c Normal file
View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#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;
}

933
upnpdescgen.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#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[] =
"<?xml version=\"1.0\"?>\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("</%s>\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, "<?xml version='1.0' encoding='UTF-8' ?>\r\n"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n"
//"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNACAP>av-upload,image-upload,create-child-container,audio-upload</dlna:X_DLNACAP><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n"
"<serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service></serviceList></device></root>");
//"</iconList><serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service><service><serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType><serviceId>763f907c-8cfb-11dd-a382-c9c0ad9eae41</serviceId><SCPDURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</SCPDURL><controlURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/control/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</controlURL><eventSubURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/events/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</eventSubURL></service></serviceList></device></root>\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,
"<specVersion><major>1</major><minor>0</minor></specVersion>");
i = 0;
str = strcat_str(str, len, &tmplen, "<actionList>");
while(acts[i].name)
{
str = strcat_str(str, len, &tmplen, "<action><name>");
str = strcat_str(str, len, &tmplen, acts[i].name);
str = strcat_str(str, len, &tmplen, "</name>");
/* argument List */
args = acts[i].args;
if(args)
{
str = strcat_str(str, len, &tmplen, "<argumentList>");
j = 0;
while(args[j].dir)
{
//JM str = strcat_str(str, len, &tmplen, "<argument><name>New");
str = strcat_str(str, len, &tmplen, "<argument><name>");
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, "</name><direction>");
str = strcat_str(str, len, &tmplen, args[j].dir==1?"in":"out");
str = strcat_str(str, len, &tmplen,
"</direction><relatedStateVariable>");
str = strcat_str(str, len, &tmplen, p);
str = strcat_str(str, len, &tmplen,
"</relatedStateVariable></argument>");
j++;
}
str = strcat_str(str, len, &tmplen,"</argumentList>");
}
str = strcat_str(str, len, &tmplen, "</action>");
/*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
i++;
}
str = strcat_str(str, len, &tmplen, "</actionList><serviceStateTable>");
i = 0;
while(vars[i].name)
{
str = strcat_str(str, len, &tmplen,
"<stateVariable sendEvents=\"");
#ifdef ENABLE_EVENTS
str = strcat_str(str, len, &tmplen, (vars[i].itype & 0x80)?"yes":"no");
#else
/* for the moment allways send no. Wait for SUBSCRIBE implementation
* before setting it to yes */
str = strcat_str(str, len, &tmplen, "no");
#endif
str = strcat_str(str, len, &tmplen, "\"><name>");
str = strcat_str(str, len, &tmplen, vars[i].name);
str = strcat_str(str, len, &tmplen, "</name><dataType>");
str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]);
str = strcat_str(str, len, &tmplen, "</dataType>");
if(vars[i].iallowedlist)
{
str = strcat_str(str, len, &tmplen, "<allowedValueList>");
for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++)
{
str = strcat_str(str, len, &tmplen, "<allowedValue>");
str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]);
str = strcat_str(str, len, &tmplen, "</allowedValue>");
}
str = strcat_str(str, len, &tmplen, "</allowedValueList>");
}
/*if(vars[i].defaultValue) */
if(vars[i].idefault)
{
str = strcat_str(str, len, &tmplen, "<defaultValue>");
/*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, "</defaultValue>");
}
str = strcat_str(str, len, &tmplen, "</stateVariable>");
/*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
i++;
}
str = strcat_str(str, len, &tmplen, "</serviceStateTable></scpd>");
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, "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\" xmlns:s=\"");
str = strcat_str(str, len, &tmplen, servns);
str = strcat_str(str, len, &tmplen, "\">");
while(v->name) {
if(v->itype & 0x80) {
str = strcat_str(str, len, &tmplen, "<e:property><");
str = strcat_str(str, len, &tmplen, v->name);
str = strcat_str(str, len, &tmplen, ">");
//printf("<e:property><s:%s>", 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, "</");
str = strcat_str(str, len, &tmplen, v->name);
str = strcat_str(str, len, &tmplen, "></e:property>");
//printf("</s:%s></e:property>\n", v->name);
}
v++;
}
str = strcat_str(str, len, &tmplen, "</e:propertyset>");
//printf("</e:propertyset>\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

944
upnpdescgen.c.dlna Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#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[] =
"<?xml version=\"1.0\"?>\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("</%s>\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,
"<specVersion><major>1</major><minor>0</minor></specVersion>");
i = 0;
str = strcat_str(str, len, &tmplen, "<actionList>");
while(acts[i].name)
{
str = strcat_str(str, len, &tmplen, "<action><name>");
str = strcat_str(str, len, &tmplen, acts[i].name);
str = strcat_str(str, len, &tmplen, "</name>");
/* argument List */
args = acts[i].args;
if(args)
{
str = strcat_str(str, len, &tmplen, "<argumentList>");
j = 0;
while(args[j].dir)
{
//JM str = strcat_str(str, len, &tmplen, "<argument><name>New");
str = strcat_str(str, len, &tmplen, "<argument><name>");
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, "</name><direction>");
str = strcat_str(str, len, &tmplen, args[j].dir==1?"in":"out");
str = strcat_str(str, len, &tmplen,
"</direction><relatedStateVariable>");
str = strcat_str(str, len, &tmplen, p);
str = strcat_str(str, len, &tmplen,
"</relatedStateVariable></argument>");
j++;
}
str = strcat_str(str, len, &tmplen,"</argumentList>");
}
str = strcat_str(str, len, &tmplen, "</action>");
/*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
i++;
}
str = strcat_str(str, len, &tmplen, "</actionList><serviceStateTable>");
i = 0;
while(vars[i].name)
{
str = strcat_str(str, len, &tmplen,
"<stateVariable sendEvents=\"");
#ifdef ENABLE_EVENTS
str = strcat_str(str, len, &tmplen, (vars[i].itype & 0x80)?"yes":"no");
#else
/* for the moment allways send no. Wait for SUBSCRIBE implementation
* before setting it to yes */
str = strcat_str(str, len, &tmplen, "no");
#endif
str = strcat_str(str, len, &tmplen, "\"><name>");
str = strcat_str(str, len, &tmplen, vars[i].name);
str = strcat_str(str, len, &tmplen, "</name><dataType>");
str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]);
str = strcat_str(str, len, &tmplen, "</dataType>");
if(vars[i].iallowedlist)
{
str = strcat_str(str, len, &tmplen, "<allowedValueList>");
for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++)
{
str = strcat_str(str, len, &tmplen, "<allowedValue>");
str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]);
str = strcat_str(str, len, &tmplen, "</allowedValue>");
}
str = strcat_str(str, len, &tmplen, "</allowedValueList>");
}
/*if(vars[i].defaultValue) */
if(vars[i].idefault)
{
str = strcat_str(str, len, &tmplen, "<defaultValue>");
/*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, "</defaultValue>");
}
str = strcat_str(str, len, &tmplen, "</stateVariable>");
/*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
i++;
}
str = strcat_str(str, len, &tmplen, "</serviceStateTable></scpd>");
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, "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\" xmlns:s=\"");
str = strcat_str(str, len, &tmplen, servns);
str = strcat_str(str, len, &tmplen, "\">");
while(v->name) {
if(v->itype & 0x80) {
str = strcat_str(str, len, &tmplen, "<e:property><s:");
str = strcat_str(str, len, &tmplen, v->name);
str = strcat_str(str, len, &tmplen, ">");
//printf("<e:property><s:%s>", 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, "</s:");
str = strcat_str(str, len, &tmplen, v->name);
str = strcat_str(str, len, &tmplen, "></e:property>");
//printf("</s:%s></e:property>\n", v->name);
}
v++;
}
str = strcat_str(str, len, &tmplen, "</e:propertyset>");
//printf("</e:propertyset>\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

94
upnpdescgen.h Normal file
View File

@ -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

38
upnpdescstrings.h Normal file
View File

@ -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

478
upnpevents.c Normal file
View File

@ -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 <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/queue.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#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(&notifylist, 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

40
upnpevents.h Normal file
View File

@ -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

81
upnpglobalvars.c Normal file
View File

@ -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 <sys/types.h>
#include <netinet/in.h>
#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];

95
upnpglobalvars.h Normal file
View File

@ -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 <time.h>
#include "miniupnpdtypes.h"
#include "config.h"
#include <sqlite3.h>
/* 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

1226
upnphttp.c Normal file

File diff suppressed because it is too large Load Diff

133
upnphttp.h Normal file
View File

@ -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 <netinet/in.h>
#include <sys/queue.h>
#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

127
upnpreplyparse.c Normal file
View File

@ -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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#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

57
upnpreplyparse.h Normal file
View File

@ -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 <sys/queue.h>
#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

787
upnpsoap.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <syslog.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include "config.h"
#include "upnpglobalvars.h"
#include "upnphttp.h"
#include "upnpsoap.h"
#include "upnpreplyparse.h"
#include "getifaddr.h"
#include <ctype.h>
#include "metadata.h"
#include <sqlite3.h>
static void
BuildSendAndCloseSoapResp(struct upnphttp * h,
const char * body, int bodylen)
{
static const char beforebody[] =
"<?xml version=\"1.0\"?>\r\n"
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>";
static const char afterbody[] =
"</s:Body>"
"</s:Envelope>\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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewConnectionStatus>Connected</NewConnectionStatus>"
"<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>"
"<NewUptime>%ld</NewUptime>"
"</u:%sResponse>";
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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<Id>%d</Id>"
"</u:%sResponse>";
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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<Result>%d</Result>"
"</u:%sResponse>";
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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<Source>"
/*"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:*"
"</Source>"
"<Sink></Sink>"
"</u:%sResponse>";
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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<SortCaps></SortCaps>"
"</u:%sResponse>";
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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<SearchCaps>dc:title,dc:creator,upnp:class,upnp:artist,upnp:album,@refID</SearchCaps>"
"</u:%sResponse>";
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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<ConnectionIDs>-1</ConnectionIDs>"
"</u:%sResponse>";
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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<RcsID>-1</RcsID>"
"<AVTransportID>-1</AVTransportID>"
"<ProtocolInfo>"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN,"
"</ProtocolInfo>"
"<PeerConnectionManager>0</PeerConnectionManager>"
"<PeerConnectionID>-1</PeerConnectionID>"
"<Direction>0</Direction>"
"<Status>0</Status>"
"</u:%sResponse>";
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; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i]);
}*/
//printf("\n");
if( strncmp(class, "item", 4) == 0 )
{
sprintf(str_buf, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
strcat(passed_args->resp, 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, "&gt;"
"&lt;dc:title&gt;%s&lt;/dc:title&gt;"
"&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
title?title:name, class);
strcat(passed_args->resp, str_buf);
if( comment && (!passed_args->filter || strstr(passed_args->filter, "dc:description")) ) {
sprintf(str_buf, "&lt;dc:description&gt;%s&lt;/dc:description&gt;", comment);
strcat(passed_args->resp, str_buf);
}
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
sprintf(str_buf, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
strcat(passed_args->resp, str_buf);
}
if( date && (!passed_args->filter || strstr(passed_args->filter, "dc:date")) ) {
sprintf(str_buf, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
strcat(passed_args->resp, str_buf);
}
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
sprintf(str_buf, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
strcat(passed_args->resp, str_buf);
}
if( album && (!passed_args->filter || strstr(passed_args->filter, "upnp:album")) ) {
sprintf(str_buf, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
strcat(passed_args->resp, str_buf);
}
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
sprintf(str_buf, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
strcat(passed_args->resp, str_buf);
}
if( track && atoi(track) && (!passed_args->filter || strstr(passed_args->filter, "upnp:originalTrackNumber")) ) {
sprintf(str_buf, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
strcat(passed_args->resp, str_buf);
}
if( !passed_args->filter || strstr(passed_args->filter, "res") ) {
strcat(passed_args->resp, "&lt;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\"&gt;"
"http://%s:5555/MediaItems/%s"
"&lt;/res&gt;",
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, "&lt;res "
"protocolInfo=\"http-get:*:%s:%s\"&gt;"
"http://%s:5555/Resized/%s"
"&lt;/res&gt;",
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, "&lt;res ");
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
"http://%s:5555/Thumbnails/%s"
"&lt;/res&gt;",
mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, id);
}
strcat(passed_args->resp, str_buf);
}
strcpy(str_buf, "&lt;/item&gt;");
}
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, "&lt;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, "&gt;"
"&lt;dc:title&gt;%s&lt;/dc:title&gt;"
"&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;"
"&lt;/container&gt;",
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[] =
"<u:BrowseResponse "
"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
"<Result>"
"&lt;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/\"&gt;\n";
static const char resp1[] = "&lt;/DIDL-Lite&gt;</Result>";
static const char resp2[] = "<UpdateID>0</UpdateID></u:BrowseResponse>";
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<NumberReturned>%u</NumberReturned>\n<TotalMatches>%u</TotalMatches>\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[] =
"<u:SearchResponse "
"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
"<Result>"
"&lt;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/\"&gt;\n";
static const char resp1[] = "&lt;/DIDL-Lite&gt;</Result>";
static const char resp2[] = "<UpdateID>0</UpdateID></u:SearchResponse>";
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, "&quot;", "\"", 0);
SearchCriteria = modifyString(SearchCriteria, "&apos;", "'", 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<NumberReturned>%u</NumberReturned>\n<TotalMatches>%u</TotalMatches>\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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewExternalIPAddress>%s</NewExternalIPAddress>"
"</u:%sResponse>";
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; i<n_lan_addr; i++)
{
if( (h->clientaddr.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[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<return>%s</return>"
"</u:%sResponse>";
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:Envelope "
"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>"
"<s:Fault>"
"<faultcode>s:Client</faultcode>"
"<faultstring>UPnPError</faultstring>"
"<detail>"
"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
"<errorCode>%d</errorCode>"
"<errorDescription>%s</errorDescription>"
"</UPnPError>"
"</detail>"
"</s:Fault>"
"</s:Body>"
"</s:Envelope>";
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);
}

22
upnpsoap.h Normal file
View File

@ -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