Initial checkin
This commit is contained in:
433
Changelog.txt
Normal file
433
Changelog.txt
Normal 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
14
INSTALL
Normal 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
346
LICENCE
Normal 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
26
LICENCE.miniupnpd
Normal 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
101
Makefile
Normal 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
25
README
Normal 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
35
commonrdr.h
Normal 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
55
config.h
Normal 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
129
daemonize.c
Normal 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
34
daemonize.h
Normal 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
179
genconfig.sh
Executable 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
55
getifaddr.c
Normal 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
18
getifaddr.h
Normal 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
|
||||
|
40
linux/miniupnpd.init.d.script
Normal file
40
linux/miniupnpd.init.d.script
Normal 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
406
metadata.c
Normal 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;", 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
title = name;
|
||||
}
|
||||
artist = taglib_tag_artist(tag);
|
||||
if( strlen(artist) )
|
||||
{
|
||||
artist = trim(artist);
|
||||
if( index(artist, '&') )
|
||||
{
|
||||
free_flags |= FLAG_ARTIST;
|
||||
artist = modifyString(strdup(artist), "&", "&amp;", 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
artist = NULL;
|
||||
}
|
||||
album = taglib_tag_album(tag);
|
||||
if( strlen(album) )
|
||||
{
|
||||
album = trim(album);
|
||||
if( index(album, '&') )
|
||||
{
|
||||
album = modifyString(strdup(album), "&", "&amp;", 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
album = NULL;
|
||||
}
|
||||
genre = taglib_tag_genre(tag);
|
||||
if( strlen(genre) )
|
||||
{
|
||||
genre = trim(genre);
|
||||
if( index(genre, '&') )
|
||||
{
|
||||
genre = modifyString(strdup(genre), "&", "&amp;", 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
genre = NULL;
|
||||
}
|
||||
comment = taglib_tag_comment(tag);
|
||||
if( strlen(comment) )
|
||||
{
|
||||
comment = trim(comment);
|
||||
if( index(comment, '&') )
|
||||
{
|
||||
comment = modifyString(strdup(comment), "&", "&amp;", 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
comment = NULL;
|
||||
}
|
||||
|
||||
|
||||
if( 1 ) // Switch on audio file type
|
||||
{
|
||||
strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01");
|
||||
strcpy(mime, "audio/mpeg");
|
||||
}
|
||||
|
||||
sql = sqlite3_mprintf( "INSERT into DETAILS"
|
||||
" (SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE,"
|
||||
" TITLE, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) "
|
||||
"VALUES"
|
||||
" (%d, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');",
|
||||
size, duration, taglib_audioproperties_channels(properties),
|
||||
taglib_audioproperties_bitrate(properties)*1024,
|
||||
taglib_audioproperties_samplerate(properties),
|
||||
strlen(date) ? date : NULL,
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
genre,
|
||||
comment,
|
||||
taglib_tag_track(tag),
|
||||
dlna_pn, mime);
|
||||
|
||||
taglib_tag_free_strings();
|
||||
taglib_file_free(audio_file);
|
||||
|
||||
if( free_flags & FLAG_ARTIST )
|
||||
free(artist);
|
||||
|
||||
//DEBUG printf("SQL: %s\n", sql);
|
||||
if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK )
|
||||
{
|
||||
fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg);
|
||||
if (zErrMsg)
|
||||
sqlite3_free(zErrMsg);
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = sqlite3_last_insert_rowid(db);
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sqlite_int64
|
||||
GetImageMetadata(const char * path, char * name)
|
||||
{
|
||||
ExifData *ed;
|
||||
ExifEntry *e = NULL;
|
||||
ExifTag tag;
|
||||
int width=0, height=0, thumb=0;
|
||||
size_t size;
|
||||
char date[64], make[32], model[64], dlna_pn[64];
|
||||
char b[1024];
|
||||
struct stat file;
|
||||
sqlite_int64 ret;
|
||||
char *sql;
|
||||
char *zErrMsg = NULL;
|
||||
|
||||
date[0] = '\0';
|
||||
model[0] = '\0';
|
||||
dlna_pn[0] = '\0';
|
||||
|
||||
//DEBUG printf("Parsing %s...\n", path);
|
||||
if ( stat(path, &file) == 0 )
|
||||
size = file.st_size;
|
||||
else
|
||||
return 0;
|
||||
strip_ext(name);
|
||||
//DEBUG printf(" * size: %d\n", size);
|
||||
|
||||
ExifLoader * l = exif_loader_new();
|
||||
exif_loader_write_file(l, path);
|
||||
ed = exif_loader_get_data(l);
|
||||
exif_loader_unref(l);
|
||||
|
||||
tag = EXIF_TAG_PIXEL_X_DIMENSION;
|
||||
e = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], tag);
|
||||
if( e )
|
||||
width = atoi( exif_entry_get_value(e, b, sizeof(b)) );
|
||||
|
||||
tag = EXIF_TAG_PIXEL_Y_DIMENSION;
|
||||
e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tag);
|
||||
if( e )
|
||||
height = atoi( exif_entry_get_value(e, b, sizeof(b)) );
|
||||
//DEBUG printf(" * resolution: %dx%d\n", width, height);
|
||||
|
||||
tag = EXIF_TAG_DATE_TIME_ORIGINAL;
|
||||
e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tag);
|
||||
if( e ) {
|
||||
strncpy(date, exif_entry_get_value(e, b, sizeof(b)), sizeof(date));
|
||||
if( strlen(date) > 10 )
|
||||
{
|
||||
date[4] = '-';
|
||||
date[7] = '-';
|
||||
date[10] = 'T';
|
||||
}
|
||||
else {
|
||||
strcpy(date, "0000-00-00");
|
||||
}
|
||||
}
|
||||
else {
|
||||
strcpy(date, "0000-00-00");
|
||||
}
|
||||
//DEBUG printf(" * date: %s\n", date);
|
||||
|
||||
model[0] = '\0';
|
||||
tag = EXIF_TAG_MAKE;
|
||||
e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tag);
|
||||
if( e )
|
||||
{
|
||||
strncpy(make, exif_entry_get_value(e, b, sizeof(b)), sizeof(make));
|
||||
tag = EXIF_TAG_MODEL;
|
||||
e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tag);
|
||||
if( e )
|
||||
{
|
||||
strncpy(model, exif_entry_get_value(e, b, sizeof(b)), sizeof(model));
|
||||
if( !strcasestr(model, make) )
|
||||
snprintf(model, sizeof(model), "%s %s", make, exif_entry_get_value(e, b, sizeof(b)));
|
||||
}
|
||||
}
|
||||
if( !strlen(model) )
|
||||
strcpy(model, "Unknown");
|
||||
//DEBUG printf(" * model: %s\n", model);
|
||||
|
||||
if( ed->size )
|
||||
thumb = 1;
|
||||
else
|
||||
thumb = 0;
|
||||
//DEBUG printf(" * thumbnail: %d\n", thumb);
|
||||
|
||||
exif_data_unref(ed);
|
||||
|
||||
if( width <= 640 && height <= 480 )
|
||||
strcpy(dlna_pn, "JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
|
||||
else if( width <= 1024 && height <= 768 )
|
||||
strcpy(dlna_pn, "JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
|
||||
else if( width <= 4096 && height <= 4096 )
|
||||
strcpy(dlna_pn, "JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
|
||||
else
|
||||
strcpy(dlna_pn, "JPEG_XL");
|
||||
|
||||
sql = sqlite3_mprintf( "INSERT into DETAILS"
|
||||
" (TITLE, SIZE, DATE, WIDTH, HEIGHT, THUMBNAIL, CREATOR, DLNA_PN, MIME) "
|
||||
"VALUES"
|
||||
" ('%q', %d, '%s', %d, %d, %d, '%q', '%s', '%s');",
|
||||
name, size, date, width, height, thumb, model, dlna_pn, "image/jpeg");
|
||||
//DEBUG printf("SQL: %s\n", sql);
|
||||
if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK )
|
||||
{
|
||||
fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg);
|
||||
if (zErrMsg)
|
||||
sqlite3_free(zErrMsg);
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = sqlite3_last_insert_rowid(db);
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sqlite_int64
|
||||
GetVideoMetadata(const char * path, char * name)
|
||||
{
|
||||
size_t size = 0;
|
||||
struct stat file;
|
||||
char *sql;
|
||||
char *zErrMsg = NULL;
|
||||
int ret;
|
||||
|
||||
//DEBUG printf("Parsing %s...\n", path);
|
||||
if ( stat(path, &file) == 0 )
|
||||
size = file.st_size;
|
||||
strip_ext(name);
|
||||
//DEBUG printf(" * size: %d\n", size);
|
||||
|
||||
sql = sqlite3_mprintf( "INSERT into DETAILS"
|
||||
" (TITLE, SIZE, MIME) "
|
||||
"VALUES"
|
||||
" ('%q', %d, %Q);",
|
||||
name, size, "video/mpeg");
|
||||
//DEBUG printf("SQL: %s\n", sql);
|
||||
if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK )
|
||||
{
|
||||
fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg);
|
||||
if (zErrMsg)
|
||||
sqlite3_free(zErrMsg);
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = sqlite3_last_insert_rowid(db);
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
return ret;
|
||||
}
|
25
metadata.h
Normal file
25
metadata.h
Normal 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
872
minidlna.c
Normal 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
473
minissdp.c
Normal 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
43
minissdp.h
Normal 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
73
miniupnpd.1
Normal 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
73
miniupnpd.conf
Normal 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
47
miniupnpdpath.h
Normal 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
23
miniupnpdtypes.h
Normal 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
191
minixml.c
Normal 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
36
minixml.h
Normal 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
173
options.c
Normal 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
69
options.h
Normal 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
510
scanner.c
Normal 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;", 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
21
scanner.h
Normal 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
53
sql.c
Normal 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
21
sql.h
Normal 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
121
testupnpdescgen.c
Normal 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
933
upnpdescgen.c
Normal 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
944
upnpdescgen.c.dlna
Normal 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
94
upnpdescgen.h
Normal 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
38
upnpdescstrings.h
Normal 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
478
upnpevents.c
Normal 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(¬ifylist, obj, entries);
|
||||
return;
|
||||
error:
|
||||
if(obj->s >= 0)
|
||||
close(obj->s);
|
||||
free(obj);
|
||||
}
|
||||
|
||||
static void
|
||||
upnp_event_notify_connect(struct upnp_event_notify * obj)
|
||||
{
|
||||
int i;
|
||||
const char * p;
|
||||
unsigned short port;
|
||||
struct sockaddr_in addr;
|
||||
if(!obj)
|
||||
return;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
i = 0;
|
||||
if(obj->sub == NULL) {
|
||||
obj->state = EError;
|
||||
return;
|
||||
}
|
||||
p = obj->sub->callback;
|
||||
p += 7; /* http:// */
|
||||
while(*p != '/' && *p != ':')
|
||||
obj->addrstr[i++] = *(p++);
|
||||
obj->addrstr[i] = '\0';
|
||||
if(*p == ':') {
|
||||
obj->portstr[0] = *p;
|
||||
i = 1;
|
||||
p++;
|
||||
port = (unsigned short)atoi(p);
|
||||
while(*p != '/') {
|
||||
if(i<7) obj->portstr[i++] = *p;
|
||||
p++;
|
||||
}
|
||||
obj->portstr[i] = 0;
|
||||
} else {
|
||||
port = 80;
|
||||
obj->portstr[0] = '\0';
|
||||
}
|
||||
obj->path = p;
|
||||
addr.sin_family = AF_INET;
|
||||
inet_aton(obj->addrstr, &addr.sin_addr);
|
||||
addr.sin_port = htons(port);
|
||||
syslog(LOG_DEBUG, "%s: '%s' %hu '%s'", "upnp_event_notify_connect",
|
||||
obj->addrstr, port, obj->path);
|
||||
obj->state = EConnecting;
|
||||
if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
|
||||
syslog(LOG_ERR, "%s: connect(): %m", "upnp_event_notify_connect");
|
||||
obj->state = EError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void upnp_event_prepare(struct upnp_event_notify * obj)
|
||||
{
|
||||
static const char notifymsg[] =
|
||||
"NOTIFY %s HTTP/1.1\r\n"
|
||||
"Host: %s%s\r\n"
|
||||
"Content-Type: text/xml; charset=\"utf-8\"\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"NT: upnp:event\r\n"
|
||||
"NTS: upnp:propchange\r\n"
|
||||
"SID: %s\r\n"
|
||||
"SEQ: %u\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"\r\n"
|
||||
"%.*s\r\n";
|
||||
char * xml;
|
||||
int l;
|
||||
if(obj->sub == NULL) {
|
||||
obj->state = EError;
|
||||
return;
|
||||
}
|
||||
switch(obj->sub->service) {
|
||||
case EContentDirectory:
|
||||
xml = getVarsContentDirectory(&l);
|
||||
break;
|
||||
case EConnectionManager:
|
||||
xml = getVarsConnectionManager(&l);
|
||||
break;
|
||||
default:
|
||||
xml = NULL;
|
||||
l = 0;
|
||||
}
|
||||
obj->buffersize = 1536;
|
||||
obj->buffer = malloc(obj->buffersize);
|
||||
/*if(!obj->buffer) {
|
||||
}*/
|
||||
obj->tosend = snprintf(obj->buffer, obj->buffersize, notifymsg,
|
||||
obj->path, obj->addrstr, obj->portstr, l+2,
|
||||
obj->sub->uuid, obj->sub->seq,
|
||||
l, xml);
|
||||
if(xml) {
|
||||
free(xml);
|
||||
xml = NULL;
|
||||
}
|
||||
//DEBUG printf("Preparing buffer:\n%s\n", obj->buffer);
|
||||
obj->state = ESending;
|
||||
}
|
||||
|
||||
static void upnp_event_send(struct upnp_event_notify * obj)
|
||||
{
|
||||
int i;
|
||||
i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
|
||||
if(i<0) {
|
||||
syslog(LOG_NOTICE, "%s: send(): %m", "upnp_event_send");
|
||||
obj->state = EError;
|
||||
return;
|
||||
}
|
||||
else if(i != (obj->tosend - obj->sent))
|
||||
syslog(LOG_NOTICE, "%s: %d bytes send out of %d",
|
||||
"upnp_event_send", i, obj->tosend - obj->sent);
|
||||
obj->sent += i;
|
||||
if(obj->sent == obj->tosend)
|
||||
obj->state = EWaitingForResponse;
|
||||
}
|
||||
|
||||
static void upnp_event_recv(struct upnp_event_notify * obj)
|
||||
{
|
||||
int n;
|
||||
n = recv(obj->s, obj->buffer, obj->buffersize, 0);
|
||||
if(n<0) {
|
||||
syslog(LOG_ERR, "%s: recv(): %m", "upnp_event_recv");
|
||||
obj->state = EError;
|
||||
return;
|
||||
}
|
||||
syslog(LOG_DEBUG, "%s: (%dbytes) %.*s", "upnp_event_recv",
|
||||
n, n, obj->buffer);
|
||||
obj->state = EFinished;
|
||||
if(obj->sub)
|
||||
obj->sub->seq++;
|
||||
}
|
||||
|
||||
static void
|
||||
upnp_event_process_notify(struct upnp_event_notify * obj)
|
||||
{
|
||||
switch(obj->state) {
|
||||
case EConnecting:
|
||||
/* now connected or failed to connect */
|
||||
upnp_event_prepare(obj);
|
||||
upnp_event_send(obj);
|
||||
break;
|
||||
case ESending:
|
||||
upnp_event_send(obj);
|
||||
break;
|
||||
case EWaitingForResponse:
|
||||
upnp_event_recv(obj);
|
||||
break;
|
||||
case EFinished:
|
||||
close(obj->s);
|
||||
obj->s = -1;
|
||||
break;
|
||||
default:
|
||||
syslog(LOG_ERR, "upnp_event_process_notify: unknown state");
|
||||
}
|
||||
}
|
||||
|
||||
void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd)
|
||||
{
|
||||
struct upnp_event_notify * obj;
|
||||
for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
|
||||
syslog(LOG_DEBUG, "upnpevents_selectfds: %p %d %d",
|
||||
obj, obj->state, obj->s);
|
||||
if(obj->s >= 0) {
|
||||
switch(obj->state) {
|
||||
case ECreated:
|
||||
upnp_event_notify_connect(obj);
|
||||
if(obj->state != EConnecting)
|
||||
break;
|
||||
case EConnecting:
|
||||
case ESending:
|
||||
FD_SET(obj->s, writeset);
|
||||
if(obj->s > *max_fd)
|
||||
*max_fd = obj->s;
|
||||
break;
|
||||
case EFinished:
|
||||
case EError:
|
||||
case EWaitingForResponse:
|
||||
FD_SET(obj->s, readset);
|
||||
if(obj->s > *max_fd)
|
||||
*max_fd = obj->s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void upnpevents_processfds(fd_set *readset, fd_set *writeset)
|
||||
{
|
||||
struct upnp_event_notify * obj;
|
||||
struct upnp_event_notify * next;
|
||||
struct subscriber * sub;
|
||||
struct subscriber * subnext;
|
||||
time_t curtime;
|
||||
for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
|
||||
syslog(LOG_DEBUG, "%s: %p %d %d %d %d",
|
||||
"upnpevents_processfds", obj, obj->state, obj->s,
|
||||
FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset));
|
||||
if(obj->s >= 0) {
|
||||
if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset))
|
||||
upnp_event_process_notify(obj);
|
||||
}
|
||||
}
|
||||
obj = notifylist.lh_first;
|
||||
while(obj != NULL) {
|
||||
next = obj->entries.le_next;
|
||||
if(obj->state == EError || obj->state == EFinished) {
|
||||
if(obj->s >= 0) {
|
||||
close(obj->s);
|
||||
}
|
||||
if(obj->sub)
|
||||
obj->sub->notify = NULL;
|
||||
/* remove also the subscriber from the list if there was an error */
|
||||
if(obj->state == EError && obj->sub) {
|
||||
LIST_REMOVE(obj->sub, entries);
|
||||
free(obj->sub);
|
||||
}
|
||||
if(obj->buffer) {
|
||||
free(obj->buffer);
|
||||
}
|
||||
LIST_REMOVE(obj, entries);
|
||||
free(obj);
|
||||
}
|
||||
obj = next;
|
||||
}
|
||||
/* remove timeouted subscribers */
|
||||
curtime = time(NULL);
|
||||
for(sub = subscriberlist.lh_first; sub != NULL; ) {
|
||||
subnext = sub->entries.le_next;
|
||||
if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
|
||||
LIST_REMOVE(sub, entries);
|
||||
free(sub);
|
||||
}
|
||||
sub = subnext;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_MINIUPNPDCTL
|
||||
void write_events_details(int s) {
|
||||
int n;
|
||||
char buff[80];
|
||||
struct upnp_event_notify * obj;
|
||||
struct subscriber * sub;
|
||||
write(s, "Events details\n", 15);
|
||||
for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
|
||||
n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n",
|
||||
obj, obj->sub, obj->state, obj->s);
|
||||
write(s, buff, n);
|
||||
}
|
||||
write(s, "Subscribers :\n", 14);
|
||||
for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
|
||||
n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n",
|
||||
sub, sub->timeout, sub->seq, sub->service);
|
||||
write(s, buff, n);
|
||||
n = snprintf(buff, sizeof(buff), " notify=%p %s\n",
|
||||
sub->notify, sub->uuid);
|
||||
write(s, buff, n);
|
||||
n = snprintf(buff, sizeof(buff), " %s\n",
|
||||
sub->callback);
|
||||
write(s, buff, n);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
40
upnpevents.h
Normal file
40
upnpevents.h
Normal 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
81
upnpglobalvars.c
Normal 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
95
upnpglobalvars.h
Normal 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
1226
upnphttp.c
Normal file
File diff suppressed because it is too large
Load Diff
133
upnphttp.h
Normal file
133
upnphttp.h
Normal 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
127
upnpreplyparse.c
Normal 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
57
upnpreplyparse.h
Normal 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
787
upnpsoap.c
Normal 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, "<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, ">"
|
||||
"<dc:title>%s</dc:title>"
|
||||
"<upnp:class>object.%s</upnp:class>",
|
||||
title?title:name, class);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
if( comment && (!passed_args->filter || strstr(passed_args->filter, "dc:description")) ) {
|
||||
sprintf(str_buf, "<dc:description>%s</dc:description>", comment);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
|
||||
sprintf(str_buf, "<dc:creator>%s</dc:creator>", creator);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( date && (!passed_args->filter || strstr(passed_args->filter, "dc:date")) ) {
|
||||
sprintf(str_buf, "<dc:date>%s</dc:date>", date);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
|
||||
sprintf(str_buf, "<upnp:artist>%s</upnp:artist>", artist);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( album && (!passed_args->filter || strstr(passed_args->filter, "upnp:album")) ) {
|
||||
sprintf(str_buf, "<upnp:album>%s</upnp:album>", album);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
|
||||
sprintf(str_buf, "<upnp:genre>%s</upnp:genre>", genre);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( track && atoi(track) && (!passed_args->filter || strstr(passed_args->filter, "upnp:originalTrackNumber")) ) {
|
||||
sprintf(str_buf, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( !passed_args->filter || strstr(passed_args->filter, "res") ) {
|
||||
strcat(passed_args->resp, "<res ");
|
||||
if( size && (!passed_args->filter || strstr(passed_args->filter, "res@size")) ) {
|
||||
sprintf(str_buf, "size=\"%s\" ", size);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( duration && (!passed_args->filter || strstr(passed_args->filter, "res@duration")) ) {
|
||||
sprintf(str_buf, "duration=\"%s\" ", duration);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( bitrate && (!passed_args->filter || strstr(passed_args->filter, "res@bitrate")) ) {
|
||||
sprintf(str_buf, "bitrate=\"%s\" ", bitrate);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( sampleFrequency && (!passed_args->filter || strstr(passed_args->filter, "res@sampleFrequency")) ) {
|
||||
sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( nrAudioChannels && (!passed_args->filter || strstr(passed_args->filter, "res@nrAudioChannels")) ) {
|
||||
sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
if( width && height && (!passed_args->filter || strstr(passed_args->filter, "res@resolution")) ) {
|
||||
sprintf(str_buf, "resolution=\"%sx%s\" ", width, height);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">"
|
||||
"http://%s:5555/MediaItems/%s"
|
||||
"</res>",
|
||||
mime, dlna_buf, lan_addr[0].str, id);
|
||||
#if 0 //JPEG_RESIZE
|
||||
if( dlna_pn && (strncmp(dlna_pn, "JPEG_LRG", 8) == 0) ) {
|
||||
strcat(passed_args->resp, str_buf);
|
||||
sprintf(str_buf, "<res "
|
||||
"protocolInfo=\"http-get:*:%s:%s\">"
|
||||
"http://%s:5555/Resized/%s"
|
||||
"</res>",
|
||||
mime, "DLNA.ORG_PN=JPEG_SM", lan_addr[0].str, id);
|
||||
}
|
||||
#endif
|
||||
if( tn && atoi(tn) && dlna_pn ) {
|
||||
strcat(passed_args->resp, str_buf);
|
||||
strcat(passed_args->resp, "<res ");
|
||||
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">"
|
||||
"http://%s:5555/Thumbnails/%s"
|
||||
"</res>",
|
||||
mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, id);
|
||||
}
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
strcpy(str_buf, "</item>");
|
||||
}
|
||||
else if( strncmp(class, "container", 9) == 0 )
|
||||
{
|
||||
sprintf(str_buf, "SELECT count(*) from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '%s' order by d.TRACK, d.TITLE, o.NAME;", id);
|
||||
ret = sqlite3_get_table(db, str_buf, &result, 0, 0, 0);
|
||||
sprintf(str_buf, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
if( !passed_args->filter || strstr(passed_args->filter, "@childCount")) {
|
||||
sprintf(str_buf, "childCount=\"%s\"", result[1]);
|
||||
strcat(passed_args->resp, str_buf);
|
||||
}
|
||||
sprintf(str_buf, ">"
|
||||
"<dc:title>%s</dc:title>"
|
||||
"<upnp:class>object.%s</upnp:class>"
|
||||
"</container>",
|
||||
name, class);
|
||||
sqlite3_free_table(result);
|
||||
}
|
||||
strcat(passed_args->resp, str_buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
BrowseContentDirectory(struct upnphttp * h, const char * action)
|
||||
{
|
||||
static const char resp0[] =
|
||||
"<u:BrowseResponse "
|
||||
"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
|
||||
"<Result>"
|
||||
"<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n";
|
||||
static const char resp1[] = "</DIDL-Lite></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>"
|
||||
"<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n";
|
||||
static const char resp1[] = "</DIDL-Lite></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, """, "\"", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "'", "'", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1);
|
||||
SearchCriteria = modifyString(SearchCriteria, "contains", "like", 1);
|
||||
SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0);
|
||||
SearchCriteria = modifyString(SearchCriteria, "object.", "", 0);
|
||||
}
|
||||
printf("Asked for SearchCriteria: %s\n", SearchCriteria);
|
||||
|
||||
char *zErrMsg = 0;
|
||||
int ret;
|
||||
char sql_buf[4096];
|
||||
args.resp = resp;
|
||||
sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
|
||||
" where OBJECT_ID like '%s$%%' and (%s) order by d.TRACK, d.TITLE, o.NAME limit %d, -1;",
|
||||
ContainerID, SearchCriteria, StartingIndex);
|
||||
printf("Search SQL: %s\n", sql_buf);
|
||||
ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg);
|
||||
if( ret != SQLITE_OK ){
|
||||
printf("SQL error: %s\n", zErrMsg);
|
||||
sqlite3_free(zErrMsg);
|
||||
}
|
||||
strcat(resp, resp1);
|
||||
sprintf(str_buf, "\n<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
22
upnpsoap.h
Normal 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
|
||||
|
Reference in New Issue
Block a user