From 09f7f18d5b2459f70238011bbcca94986175a976 Mon Sep 17 00:00:00 2001 From: j Date: Sun, 11 Jul 2004 18:09:05 +0000 Subject: [PATCH] reimport icecast-kh to branches/kh/icecast; this time as a branch of trunk/icecast svn path=/icecast/branches/kh/icecast/; revision=7095 --- AUTHORS | 4 + COPYING | 340 +++++++++ HACKING | 48 ++ Makefile.am | 22 + NEWS | 416 +++++++++++ README | 21 + TODO | 89 +++ admin/Makefile.am | 8 + admin/listclients.xsl | 79 ++ admin/listmounts.xsl | 65 ++ admin/manageauth.xsl | 91 +++ admin/moveclients.xsl | 61 ++ admin/response.xsl | 54 ++ admin/stats.xsl | 101 +++ autogen.sh | 113 +++ conf/Makefile.am | 28 + conf/icecast.xml.in | 165 +++++ configure.in | 158 ++++ debian/Makefile.am | 8 + debian/README.Debian | 12 + debian/changelog | 198 +++++ debian/compat | 1 + debian/control | 22 + debian/copyright | 26 + debian/icecast2.1 | 24 + debian/icecast2.default | 19 + debian/icecast2.init | 67 ++ debian/icecast2.manpages | 1 + debian/icecast2.postinst | 43 ++ debian/icecast2.postrm | 29 + debian/icecast2.preinst | 27 + debian/rules | 34 + debian/watch | 3 + doc/Index.hhk | 9 + doc/Makefile.am | 13 + doc/icecast2.chm | Bin 0 -> 132247 bytes doc/icecast2.hhc | 62 ++ doc/icecast2.hhp | 23 + doc/icecast2_admin.html | 119 +++ doc/icecast2_basicsetup.html | 59 ++ doc/icecast2_config_file.html | 403 +++++++++++ doc/icecast2_faq.html | 85 +++ doc/icecast2_glossary.html | 37 + doc/icecast2_introduction.html | 43 ++ doc/icecast2_listenerauth.html | 54 ++ doc/icecast2_relay.html | 47 ++ doc/icecast2_stats.html | 83 +++ doc/icecast2_win32.html | 54 ++ doc/icecast2_yp.html | 36 + doc/index.html | 76 ++ doc/index_win32.html | 19 + doc/listener_auth1.jpg | Bin 0 -> 32628 bytes doc/listener_auth2.jpg | Bin 0 -> 30108 bytes doc/listener_auth3.jpg | Bin 0 -> 23185 bytes doc/stats1.jpg | Bin 0 -> 69066 bytes doc/style.css | 79 ++ doc/win32_section1.html | 34 + doc/win32_section2.html | 9 + doc/win32_section3.html | 9 + doc/windowtitle.jpg | Bin 0 -> 66677 bytes icecast.spec | 58 ++ src/Makefile.am | 31 + src/TODO | 1 + src/admin.c | 896 +++++++++++++++++++++++ src/admin.h | 21 + src/auth.c | 175 +++++ src/auth.h | 56 ++ src/cfgfile.c | 958 ++++++++++++++++++++++++ src/cfgfile.h | 176 +++++ src/client.c | 171 +++++ src/client.h | 81 +++ src/compat.h | 31 + src/configtest.c | 75 ++ src/connection.c | 1020 ++++++++++++++++++++++++++ src/connection.h | 61 ++ src/event.c | 67 ++ src/event.h | 21 + src/format.c | 212 ++++++ src/format.h | 72 ++ src/format_mp3.c | 572 +++++++++++++++ src/format_mp3.h | 41 ++ src/format_vorbis.c | 797 ++++++++++++++++++++ src/format_vorbis.h | 24 + src/fserve.c | 522 ++++++++++++++ src/fserve.h | 35 + src/global.c | 63 ++ src/global.h | 55 ++ src/logging.c | 122 ++++ src/logging.h | 100 +++ src/main.c | 494 +++++++++++++ src/md5.c | 277 +++++++ src/md5.h | 36 + src/net/.cvsignore | 6 - src/net/BUILDING | 3 - src/net/COPYING | 481 ------------- src/net/Makefile.am | 21 - src/net/README | 10 - src/net/TODO | 1 - src/net/resolver.c | 226 ------ src/net/resolver.h | 41 -- src/net/sock.c | 816 --------------------- src/net/sock.h | 142 ---- src/net/test_resolver.c | 17 - src/os.h | 30 + src/refbuf.c | 80 +++ src/refbuf.h | 51 ++ src/sighandler.c | 70 ++ src/sighandler.h | 21 + src/slave.c | 488 +++++++++++++ src/slave.h | 37 + src/source.c | 1238 ++++++++++++++++++++++++++++++++ src/source.h | 105 +++ src/stats.c | 858 ++++++++++++++++++++++ src/stats.h | 99 +++ src/thread/.cvsignore | 6 - src/thread/BUILDING | 20 - src/thread/COPYING | 481 ------------- src/thread/Makefile.am | 20 - src/thread/README | 9 - src/thread/TODO | 5 - src/thread/thread.c | 852 ---------------------- src/thread/thread.h | 191 ----- src/util.c | 622 ++++++++++++++++ src/util.h | 52 ++ src/xslt.c | 191 +++++ src/xslt.h | 40 ++ src/yp.c | 903 +++++++++++++++++++++++ src/yp.h | 53 ++ web/Makefile.am | 14 + web/auth.xsl | 56 ++ web/corner_bottomleft.jpg | Bin 0 -> 8164 bytes web/corner_bottomright.jpg | Bin 0 -> 8155 bytes web/corner_topleft.jpg | Bin 0 -> 8149 bytes web/corner_topright.jpg | Bin 0 -> 8151 bytes web/icecast.png | Bin 0 -> 5611 bytes web/key.gif | Bin 0 -> 1517 bytes web/status.xsl | 84 +++ web/status2.xsl | 12 + web/style.css | 198 +++++ win32/ConfigTab.cpp | 79 ++ win32/ConfigTab.h | 50 ++ win32/Icecast2win.clw | 152 ++++ win32/Icecast2win.cpp | 89 +++ win32/Icecast2win.dsp | 270 +++++++ win32/Icecast2win.dsw | 47 ++ win32/Icecast2win.h | 52 ++ win32/Icecast2win.rc | 301 ++++++++ win32/Icecast2winDlg.cpp | 1183 ++++++++++++++++++++++++++++++ win32/Icecast2winDlg.h | 124 ++++ win32/Makefile.am | 15 + win32/ResizableDialog.cpp | 442 ++++++++++++ win32/ResizableDialog.h | 173 +++++ win32/StatsTab.cpp | 149 ++++ win32/StatsTab.h | 55 ++ win32/Status.cpp | 160 +++++ win32/Status.h | 59 ++ win32/StdAfx.cpp | 8 + win32/StdAfx.h | 27 + win32/TRAYNOT.h | 35 + win32/TabCtrlSSL.cpp | 499 +++++++++++++ win32/TabCtrlSSL.h | 98 +++ win32/TabPageSSL.cpp | 76 ++ win32/TabPageSSL.h | 27 + win32/Traynot.cpp | 65 ++ win32/black.bmp | Bin 0 -> 28856 bytes win32/colors.h | 5 + win32/icecast.dsp | 344 +++++++++ win32/icecast.ico | Bin 0 -> 2238 bytes win32/icecast2.iss | 64 ++ win32/icecast2_console.dsp | 102 +++ win32/icecast2_console.dsw | 44 ++ win32/icecast2logo2.bmp | Bin 0 -> 16952 bytes win32/res/Icecast2win.rc2 | 13 + win32/res/Makefile.am | 6 + win32/resource.h | 76 ++ win32/running.bmp | Bin 0 -> 5336 bytes win32/stopped.bmp | Bin 0 -> 5336 bytes 177 files changed, 21242 insertions(+), 3348 deletions(-) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 HACKING create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100644 admin/Makefile.am create mode 100755 admin/listclients.xsl create mode 100755 admin/listmounts.xsl create mode 100755 admin/manageauth.xsl create mode 100755 admin/moveclients.xsl create mode 100755 admin/response.xsl create mode 100755 admin/stats.xsl create mode 100755 autogen.sh create mode 100644 conf/Makefile.am create mode 100644 conf/icecast.xml.in create mode 100644 configure.in create mode 100644 debian/Makefile.am create mode 100644 debian/README.Debian create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/icecast2.1 create mode 100644 debian/icecast2.default create mode 100644 debian/icecast2.init create mode 100644 debian/icecast2.manpages create mode 100644 debian/icecast2.postinst create mode 100644 debian/icecast2.postrm create mode 100644 debian/icecast2.preinst create mode 100755 debian/rules create mode 100644 debian/watch create mode 100644 doc/Index.hhk create mode 100644 doc/Makefile.am create mode 100644 doc/icecast2.chm create mode 100644 doc/icecast2.hhc create mode 100644 doc/icecast2.hhp create mode 100755 doc/icecast2_admin.html create mode 100755 doc/icecast2_basicsetup.html create mode 100755 doc/icecast2_config_file.html create mode 100755 doc/icecast2_faq.html create mode 100755 doc/icecast2_glossary.html create mode 100755 doc/icecast2_introduction.html create mode 100755 doc/icecast2_listenerauth.html create mode 100755 doc/icecast2_relay.html create mode 100755 doc/icecast2_stats.html create mode 100755 doc/icecast2_win32.html create mode 100755 doc/icecast2_yp.html create mode 100755 doc/index.html create mode 100644 doc/index_win32.html create mode 100644 doc/listener_auth1.jpg create mode 100644 doc/listener_auth2.jpg create mode 100644 doc/listener_auth3.jpg create mode 100644 doc/stats1.jpg create mode 100644 doc/style.css create mode 100644 doc/win32_section1.html create mode 100644 doc/win32_section2.html create mode 100644 doc/win32_section3.html create mode 100644 doc/windowtitle.jpg create mode 100644 icecast.spec create mode 100644 src/Makefile.am create mode 100644 src/TODO create mode 100644 src/admin.c create mode 100644 src/admin.h create mode 100644 src/auth.c create mode 100644 src/auth.h create mode 100644 src/cfgfile.c create mode 100644 src/cfgfile.h create mode 100644 src/client.c create mode 100644 src/client.h create mode 100644 src/compat.h create mode 100644 src/configtest.c create mode 100644 src/connection.c create mode 100644 src/connection.h create mode 100644 src/event.c create mode 100644 src/event.h create mode 100644 src/format.c create mode 100644 src/format.h create mode 100644 src/format_mp3.c create mode 100644 src/format_mp3.h create mode 100644 src/format_vorbis.c create mode 100644 src/format_vorbis.h create mode 100644 src/fserve.c create mode 100644 src/fserve.h create mode 100644 src/global.c create mode 100644 src/global.h create mode 100644 src/logging.c create mode 100644 src/logging.h create mode 100644 src/main.c create mode 100644 src/md5.c create mode 100644 src/md5.h delete mode 100644 src/net/.cvsignore delete mode 100644 src/net/BUILDING delete mode 100644 src/net/COPYING delete mode 100644 src/net/Makefile.am delete mode 100644 src/net/README delete mode 100644 src/net/TODO delete mode 100644 src/net/resolver.c delete mode 100644 src/net/resolver.h delete mode 100644 src/net/sock.c delete mode 100644 src/net/sock.h delete mode 100644 src/net/test_resolver.c create mode 100644 src/os.h create mode 100644 src/refbuf.c create mode 100644 src/refbuf.h create mode 100644 src/sighandler.c create mode 100644 src/sighandler.h create mode 100644 src/slave.c create mode 100644 src/slave.h create mode 100644 src/source.c create mode 100644 src/source.h create mode 100644 src/stats.c create mode 100644 src/stats.h delete mode 100644 src/thread/.cvsignore delete mode 100644 src/thread/BUILDING delete mode 100644 src/thread/COPYING delete mode 100644 src/thread/Makefile.am delete mode 100644 src/thread/README delete mode 100644 src/thread/TODO delete mode 100644 src/thread/thread.c delete mode 100644 src/thread/thread.h create mode 100644 src/util.c create mode 100644 src/util.h create mode 100644 src/xslt.c create mode 100644 src/xslt.h create mode 100644 src/yp.c create mode 100644 src/yp.h create mode 100644 web/Makefile.am create mode 100755 web/auth.xsl create mode 100644 web/corner_bottomleft.jpg create mode 100644 web/corner_bottomright.jpg create mode 100644 web/corner_topleft.jpg create mode 100644 web/corner_topright.jpg create mode 100644 web/icecast.png create mode 100644 web/key.gif create mode 100755 web/status.xsl create mode 100755 web/status2.xsl create mode 100644 web/style.css create mode 100644 win32/ConfigTab.cpp create mode 100644 win32/ConfigTab.h create mode 100755 win32/Icecast2win.clw create mode 100644 win32/Icecast2win.cpp create mode 100644 win32/Icecast2win.dsp create mode 100644 win32/Icecast2win.dsw create mode 100644 win32/Icecast2win.h create mode 100644 win32/Icecast2win.rc create mode 100644 win32/Icecast2winDlg.cpp create mode 100644 win32/Icecast2winDlg.h create mode 100644 win32/Makefile.am create mode 100644 win32/ResizableDialog.cpp create mode 100644 win32/ResizableDialog.h create mode 100644 win32/StatsTab.cpp create mode 100644 win32/StatsTab.h create mode 100644 win32/Status.cpp create mode 100644 win32/Status.h create mode 100644 win32/StdAfx.cpp create mode 100644 win32/StdAfx.h create mode 100644 win32/TRAYNOT.h create mode 100644 win32/TabCtrlSSL.cpp create mode 100644 win32/TabCtrlSSL.h create mode 100644 win32/TabPageSSL.cpp create mode 100644 win32/TabPageSSL.h create mode 100644 win32/Traynot.cpp create mode 100644 win32/black.bmp create mode 100644 win32/colors.h create mode 100644 win32/icecast.dsp create mode 100644 win32/icecast.ico create mode 100644 win32/icecast2.iss create mode 100644 win32/icecast2_console.dsp create mode 100644 win32/icecast2_console.dsw create mode 100644 win32/icecast2logo2.bmp create mode 100644 win32/res/Icecast2win.rc2 create mode 100644 win32/res/Makefile.am create mode 100644 win32/resource.h create mode 100644 win32/running.bmp create mode 100644 win32/stopped.bmp diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..feb09705 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Jack Moffitt +Michael Smith +oddsock +Karl Heyes diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..d60c31a9 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/HACKING b/HACKING new file mode 100644 index 00000000..891a7c1c --- /dev/null +++ b/HACKING @@ -0,0 +1,48 @@ +Note that these instructions are *not* necessary for distribution +tarballs; they have separate configure/build instructions. + +Building this package from CVS is mainly intended for developers. +General users should obtain official distribution packages; both +source and binary distributions are available at +http://www.icecast.org/ + +----- + +These are *brief* instructions on how to build this package from CVS. +Yes, there are details left out. + +There are generally four steps necessary when building from CVS (i.e., +a developer's copy): + +1. cvs checkout of the sources, or cvs update. RTFM from your + favorite flavor of CVS documentation; information on the xiph.org + CVS repository can be found at http://www.xiph.org/cvs.html. + +2. [re-]generate files such as "configure" and "Makefile.in" with the + GNU autoconf/automake tools. Run the "autogen.sh" script to + perform this step. + + *** IF YOU ARE NOT BUILDING WITH GNU MAKE *AND* GCC: you must set + the AUTOMAKE_FLAGS environment variable to "--include-deps" + before running autogen.sh. For example: + + csh% setenv AUTOMAKE_FLAGS --include-deps + csh% ./autogen.sh + or + sh% AUTOMAKE_FLAGS=--include-deps ./autogen.sh + +3. Run configure. There are several options available; see + "./configure --help" for more information. + +4. Run "make" to build the source. + +In general, steps 2 and 3 need to be re-run every time any of the +following files are modified (either manually or by a cvs update): + + configure.in + acinclude.m4 + +Running "make clean" after running steps 2 and 3 is generally also +advisable before running step 4. It isn't *always* necessary, but +unless you understand the workings of autoconf/automake, it's safest +to just do it. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..d3ae4d41 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,22 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = 1.6 foreign dist-zip +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src conf debian doc web admin win32 + +EXTRA_DIST = HACKING m4/acx_pthread.m4 m4/ogg.m4 m4/vorbis.m4 \ + m4/xiph_compiler.m4 m4/xiph_curl.m4 m4/xiph_net.m4 \ + m4/xiph_types.m4 m4/xiph_xml2.m4 icecast.spec + +docdir = $(datadir)/doc/$(PACKAGE) +doc_DATA = README AUTHORS COPYING NEWS TODO + +debug: + $(MAKE) all CFLAGS="@DEBUG@" + +profile: + $(MAKE) all CFLAGS="@PROFILE@" + +static: + $(MAKE) all LDFLAGS="${LDFLAGS} -all-static" diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..d772a3df --- /dev/null +++ b/NEWS @@ -0,0 +1,416 @@ +feature differences from CVS + +fast pre-buffering - burst a certain amount of data at connection time. +mp3 accepts artist and title separately on the url. +vorbis can accept url updates if module compiled in +program invocation at stream start and end, per mount based. +on-demand relays, activated on first listener, disconnected when listeners +falls to 0 +initial implementation of multiple Ogg codec streaming, theora, vorbis and +speex. more work needed to start at theora key frame. +Added URL and command based authentication + +kh47 +. per mount burst size was not being referred to properly +. added sync markers to queue for client starts, the buffer with a sync mark + from the burst point is where a client starts from. +. theora marks a sync marker on a page which has the start of a keyframe. +. vorbis and speex mark sync on all buffers unless theora is used. mp3 sync + marks on every buffer. +. some code re-org for queue management, allows old refbuf removal even + on socket read timeout +. place clients that can take more pending data at the beginning of the client + queue. This allows for reducing client scanning when there's a short timeout + +kh46 +. New listeners actually drop to fallback mount, silly inverse bug +. typo in mp3 metadata handling, affected error case, rare +. make short delay timeout work when reading from source, minor handling + case from previous update. +. make the ogg vorbis module use 1000 not 1024 as a factor for stats. +. if a YP server is up but not responding with an error message then show + a default one. +. fixed a fallback problem and improved the log messages for when fallback + is triggered. + +kh45 +. fix minor YP cleanup case when YP server fails and source disconnects +. cleanup from previous patch exposed a potential source reading bug, which + had shown up because sources could disconnect for no reason. + +kh44 +. fix segv case for non-auth streams, unchecked in last release. +. only send ICY 200 OK if realplayer else send HTTP/1.0 200 OK +. allow for setting username/password for auth URLs +. still apply timeout to recoverable source read + +kh43 +. add tags for , default none, both need to + be provided for auth to be attempted. +. add url based auth module. example in conf, you can state add and + remove urls to run for each client, each add is passed id, user, pass, ip, + user agent. The remove is passed id, user, pass, duration +. make sure the global client count is in sync when auth is used + +kh42 +. drop the use of thread conditional variables in the connection threads, + they we not working as they should +. minor cleanups, log messages etc +. re-work auth subsystem to allow for slow auth mechanisms +. added command auth, do that listener auth can be done externally. The + external command should take a mount, user and pass on separate lines and + return a status code of 0 (authenticated) or non-zero (reject) + +kh41 +. fixed 401 reporting on missing urls, should be 404. Handle a very rare + long lock held case as well + +kh40 +. make sure public is 0 in the stats tree +. move parse_audio_info back to source.c and make it thread safe +. add debug for YP ok case, debug is supposed to be rarely used after all +. up the per-listener max write threshold +. report 401 for failed auth connections +. add audio/x-mpeg mime type + +kh39 +. mp3 streams without metadata were not having their metadata initialised + properly, causing problems when clients connected with metadata capable + clients. +. send ice-audio-info to clients, provides YP info in relays situations + +kh38 +. re-sync with svn codebase, update listener auth and docs +. send ICY OK instead of HTTP OK for realplayer test +. minor cleanups + +kh37 +. add the previous vorbis specific handler back in, it allowed for updates + via url. enabled via configure --enable-vorbis-updates +. updated admin pages to those in the main tree +. plug some minor memory leaks in yp + +kh36 +. force yp_remove, log message when found, fixed a race as well +. fix mp3 segv when on-demand relays are used +. fix debug messaging in yp.c, helps to track any YP related problems +. emit icy-br instead of icy-bitrate, allow relays to be on YP listings, but + handle icy-bitrate as well +. (default 0) in prevents a mount from going on YP listings +. fixes to ice-audio-info handling. +. make yp thread wait longer than it does at shutdown, allows for final + yp_remove on closing streams + +kh35 +. handle YP error conditions better. +. moving clients via url allows on-demand relays to startup/shutdown +. segv bug fixed from previous release, http headers were being prepared + on mount switchover. + +kh34 +. minor fixes for YP, fix missing bits from rewrite + if any YP request fails go back to add + do a yp touch on inline mp3 updates as well as url metadata updates +. sync with main tree for minor patches. +. set default stream burst size to 64k (typical player prebuffer size) +. fix some missing web stats +. add (0 default) to , put new listeners + on next available fallback, 404 otherwise + +kh33 +. schedule a YP touch on admin metadata updates +. build fix relating to geturl files. + +kh32 +. minor compile cleanups from CVS +. make sure a failed script does exit +. report system message on initial log open failure +. update YP thread code, big change. + +kh31 +. fix for bug when adding clients from pending list. + +kh30 +. add make static for those who want it +. changed code on list processing for active and pending clients +. changed when format-specific client data gets freed up +. shuffle code around, OK response to source client only source_client_thread +. 404 sent to client on failed on-demand relay +. more re-sync work + various non-functional cleanups + +kh29 +. revert test harness code left from before which advertised private streams + and cleanup unwanted messages +. more cleanups, more sync work with CVS +. source_t wasn't going after source client disconnection + +kh28 +. more re-sync work +. fixed a yp deadlock case for stuffed yp servers. + +kh27 +. fixed 2 possible deadlock cases +. fixed race wrt to alias lookup after HUP change +. reset ogg stream type to "Ogg Vorbis" even if it isn't, so that YP + shows icons correctly. + +kh26 +. fixed YP related bugs, leaks + bad pointer access +. fixed rare segv case in format_mp3 +. more cleanup work in source shutdown +. Changed how client http headers are sent back +. Implement on demand relay, enabled with 1 in + +kh25 +. Changed Ogg module + - added Ogg multiplexing + - added theora and speex streaming + - URL updates removed from vorbis, due to multiple codec support, maybe added later. +. continue with more re-sync to CVS work +. fixed a few rare races that had shown up with the re-sync work. + +kh24 +. more re-sync work with CVS. +. Identified a couple more locking issues via the admin interface +. remove errno use from stream dump routines + +kh23 +. re-sync with CVS tree, fallback override, no-mount, initial work on + listener authentication. +. locking updates +. on-connect/disconnect scripts are started and not waited on. +. icy/ice headers cleanup +. initialise config correctly + +kh22 +. fix some rare lock races +. page samples fix on EOS flush, reset granulepos to 0 on new stream +. add /admin/streamlist.txt from beta3 +. big update of the relay code, supports master relay now +. added yp-log-headers tag in to disable YP header logging, it + can make for lots of noise in the logs + +kh21 +. only send a 200 OK for source connections that are from source clientts + and not relays +. on failed source init, some setup was not cleared and the source count + was not decremented +. Check that writes to clients have actual data available +. fix deadlock case with yp +. fix locking for url metdata updates generically +. fix use of non thread-safe function localtime in yp + +kh20 +. fixed slave/stats shutdown race +. re-applying avl fix for null free function +. fix for vorbis, API changes caused stalls when sending to client +. relay updating/restarting code fixed. +. changes to vorbis input to allow for url updates +. allow for stating artist and title on url + +kh19 +. fix a few more signed/unsigned problems, affecting mp3 mainly +. re-worked pthread configure option +. update various bits to bring in line with v2.0 beta2 + +kh18 +. another signed/unsigned int bug fixed +. added a short delay trigger for waiting clients, improves bursting +. added --disable-log-ip for the access log +. fix segv when adding clients to inactive relays + +kh17 +. cleanup some api bits +. fix mp3 bug causing glitches in the audio +. added pidfile support + +kh16 +. made outgoing serial numbers unique, helps in fallback situations +. revert date field to where it was in access.log. That way it conforms + to the common log format for analysers. +. changed interal api of the client write function so that handling of + burst limits is better. Also allow the queue to be more format specific. + +kh15 +. Updated vorbis input to rebuild stream, forcing pages to contain around + 1 second worth of audio. + +kh14 +. use localtime_r when available +. compile time switch for enabling/disabling logging IP in access log +. handle source counts better. +. relays retain the source struct so source clients don't steal inactive + relay mountpoints. + +kh13 +. update to latest CVS docs +. fixup source timeout, internally it's in milliseconds, and can be + stated per mount as well as globally. + +kh12 +. terminate stream if inline metadata does not contain "StreamTitle" +. Added docs by oddsock, and other updates from CVS +. wait for source start/stop run scripts to finish, linuxthreads + were leaving zombies. +. missing initialiser for relay connection, caused segv on failed + connections +. fix 3 mp3 metadata mis-alignment bugs on stalled links + +kh11 +. fix a bad memory reference from kh9 +. don't free finished clients too early, bad memory reference + +kh10 +. when reading from straight mp3 stream (no shoutcast metadata) + updates via url were not getting to clients. fixed + +kh9 +. per-mount queue and burst size options +. pre-mount on-connect/on-disconnect scripts +. more re-sync with cvs updates +. force relay re-check on xml update + +kh8 +. timeout value had multiple applied +. more dead code removal +. make initial mp3 metadata block blank +. fix potential leak in mp3 reading code. + +kh7 +. added stream type check for client moving +. various cleanups, dead code removal +. minor relay structure mem leak fix +. fix source count check. +. enable the fileserve thread again + +kh6 +. stream dumping added +. mp3 title update via url fixed up +. fixed up listclients, moveclients. +. re-implemented source fallbacks + +kh5 +. 2 lots of ;; caused compile problems on windows +. enable curl for YP access +. allow the format specific get buffer routine to return + NULL for retry, needs to set running to 0 now +. add vorbis header parsing for artist title stats +. add mp3 title parsing for the stats + +kh4 +. handle relay start and shutdown better. +. apply avl rwlock leak fix +. apply stats thread sleep avoidance fix +. update sock.c errno check +. enable stats/YP thread, add various stat triggers + +kh3 +. fix burst size larger than queue size case +. prevent relay connections initiating if connection is running +. send server package string from autoconf +. fixed a few segvs from previous update + +kh2 +. fix mp3 handling, metadata handling wasn't correct. +. fix flow control, locks/source counts with relay +. Added tag default 0 (in bytes) +. Use queue size field, was hardcoded before +. Don't filter refbuf if 0 length but have associated data +. handle EOF/error from socket in format_mp3 + +kh1 - core update +. removed many locks +. used single queue for source data. +. clients start at end of queue and allowed to repeat writes, thus + giving bursts at connection. capped at 8 writes +. many secondary things not working. stream dumping, YP, stats, + web interface +. mp3 incl shoutcast meta and ogg should work + +2003-10-12 + Added documentation + +2003-04-23 + Support aliases + +2003-03-09 + Support listening on multiple sockets. + +2003-03-08 + Support for shoutcast source protocol added. + +2003-03-08 + Started implementing generic admin interface. Supports (so far): + - dynamic configuration of mount fallbacks + /admin/fallbacks?mount=/mount&fallback=/fallback + - setting of mp3 metadata + /admin/metadata?mount=/mount&mode=updinfo&song=New%20Title + - dumping raw xml stats + /admin/rawstats + - listing all connected clients on a mountpoint: + /admin/listclients?mount=/mountname + +2003-03-05 + Implemented the ability to reread the config file on SIGHUP. For now, this + does not affect configuration for currently running sources (only new + sources and global parameters like max-listeners) + +2003-03-02 + More features: + -- per mountpoint listener maxima + -- static configuration of mountpoint fallbacks + -- stream dumping (write incoming stream to disk) + +2003-02-27 + Fix log buffering on win32 - previously, logs were never flushed, so they + only got output every few tens or hundreds of lines. + +2003-02-27 + Support new icy-audio-info header, to communicate various parameters to + clients and yp servers, including sample rate, quality, channels, bitrate + +2003-02-25 + Full support for relaying mp3 metadata (if turned on in config file) + +2003-02-25 + Allow configuration of maximum client queue length (in bytes) + +2003-02-14 + Finished full IPv6 support. + +2003-02-12 + Allow configuring local mountpoint seperately from remote mountpoint for + relays + +2003-02-12 + Per mountpoint usernames and passwords (for sources) + +2003-02-11 + Now that it's been officially assigned, use application/ogg instead of + application/x-ogg + +2003-02-07 + Allow relaying of mp3 streams from icecast 1.x and shoutcast + +2003-02-07 + Added ability to configure individual relays (rather than just all streams + from a single server). + +2003-02-03 + Added support for YP directory services listings + are only used by the yp listing routines + +2003-02-03 + Support command line parameter -b to run in the background (not supported + on win32) + +2002-12-31 + Implement configurable mountpoint fallbacks (on source exit, clients are + transferred to another mountpoint automatically, without disconnecting + them) + +2002-12-31 + Implemented full mp3 metadata support. + +(older stuff is missing from here) + diff --git a/README b/README new file mode 100644 index 00000000..d30909d5 --- /dev/null +++ b/README @@ -0,0 +1,21 @@ +Icecast2 Beta 1. + +This is an beta release. Not all functionality is fully implemented, though +we believe it to be very stable for all the currently available features. + +To build icecast on a Unix platform, perform the following : + +Run + ./configure + make + make install + +To build and install this release. + +A sample config file will be placed in /usr/local/etc (on UNIX) or in the current working directory (on Win32) and is called icecast.xml + +Documentation for icecast is available in the doc directory, by viewing doc/icecast2_TOC.html in a browser. + +Please email us at icecast@xiph.org or icecast-dev@xiph.org, or come and see +us at irc.freenode.net, channel #icecast, if you have any troubles. + diff --git a/TODO b/TODO new file mode 100644 index 00000000..86af2698 --- /dev/null +++ b/TODO @@ -0,0 +1,89 @@ +2.0 CRITICAL - These are the things without which 2.0 cannot be released +____________ + +- Should icecast automatically (i.e. without needing -c) look for the config + file in /etc/icecast.xml or something? + +- libshout 2.0 and ices 2.0 releases, also an ices 0.x release that works with + this. Without source clients, icecast isn't much use... + +- integrate/include all the documentation done by external groups. + +- generally we don't do proper checking for the correct versions of various + libraries (this is probably more of an issue with ices2, but it also affects + icecast) + +BUGS +---- +- stats get off? this needs testing more testing. + +- some stuff (like 'genre') isn't making it into the stats dump + +- logging - bytes send and time listening may both be broken? + +- slave servers don't work. relay user is not respected by the source (only + admin can read /admin/streamlist), and the slave can't parse the xml result + of streamlist anyway (it expects a simple mountpoint per line) + +FEATURES +-------- + +- pull out vorbis comments. and send to stats. This seems to be being + done, but it isn't working right. + +- directory server GUID checks + directory server does GET /GUID-asldjfasldfjalsdkfjasldkfj HTTP/1.0 + and either gets a 404 if it's wrong, or a 200 if it's correct. + +- adding new stats type, event. events don't modify the global stats tree, + ie, source /1234.ogg disconnected + +- support W3C Extended Logging (http://www.w3.org/TR/WD-logfile.html) + toggle between this and Apache Combined Log Format in the config file. + default to apache style. + +- allow using get_predata() stuff to send an "intro" to any newly-connected + user? + +- stats to list currently connected clients: ip and hostname + +- stream switching (drop clients to another stream on disconnect of source) + - a) fallbacks from named location to new mountpoint + - OR b) fallbacks for connected clients to new mountpoint (so newly-connecting + clients just get a 404 on the old path) + - OR c) combination - first one, plus generic alias ability? + +- /admin/* for all admin functionality + - configuring fallbacks + - mp3 metadata injection + - remote shutdown? + +- general registerable url-handlers in connection.c rather than hard-coded list + (already getting unmaintainable) + +- httpp - split out query string for further processing + +- option to use ipv6 (equiv to using ::, I think. + +- abstract all admin functionality to a set of commands, and command handlers. + Make /admin/* just parse according to a set of rules, and dispatch generic + commands through that. + Use this for alternative admin interfaces (GUI? telnet interface?) + +- listener authentication (per mountpoint?) + +- all timer-based functionality (yp updates, slave/relay checks) should have a + single timer thread which dispatches events through the normal event + mechanism (to worker threads from the main pool). This will reduce the + extraneous thread count. + +- atomic admin function to: set fallback from A->B, remove A, move mountpoint + B to A. Needs forced-source removal first. + +- race condition between avl_tree_unlock(pending_tree) and + thread_cond_wait(&fserv_cond) in fserv.c, it's a pain to fix but should be. + +- do we need to use locks on the avl client_trees in source.c and fserv.c? + + + diff --git a/admin/Makefile.am b/admin/Makefile.am new file mode 100644 index 00000000..3a298c24 --- /dev/null +++ b/admin/Makefile.am @@ -0,0 +1,8 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = foreign + +admindir = $(pkgdatadir)/admin +dist_admin_DATA = listclients.xsl listmounts.xsl moveclients.xsl response.xsl \ + stats.xsl manageauth.xsl + diff --git a/admin/listclients.xsl b/admin/listclients.xsl new file mode 100755 index 00000000..1a57dfa9 --- /dev/null +++ b/admin/listclients.xsl @@ -0,0 +1,79 @@ + + + + + +Icecast Streaming Media Server + + + +
+ + + +
+ List MountPoints | + Move Listeners | + Stats | + Status Page +
+
+ + + + + + +
+

List Connected Listeners

+
+
+ +
+
+ +

+ +()

+ + + +
+ Show Listeners | + Move Listeners | + Kill Source +
+

+ + + + + + + + + + + + + + + + +
IPConnected ForUser Agent
() secondskill
+

+

+
+
+
+ +
+
+



+
+
+Support icecast development at www.icecast.org
+ + +
+
diff --git a/admin/listmounts.xsl b/admin/listmounts.xsl new file mode 100755 index 00000000..82784eeb --- /dev/null +++ b/admin/listmounts.xsl @@ -0,0 +1,65 @@ + + + + + +Icecast Streaming Media Server + + + +
+ + + +
+ List MountPoints | + Move Listeners | + Stats | + Status Page +
+
+ + + + + + +
+

List Mountpoints

+
+
+ +
+
+ +

+ +() + +

+ + + +
+ Show Listeners | + Move Listeners | + Kill Source + | Manage Authentication +
+

+

Listener(s)

+

+
+
+
+ +
+
+



+
+
+Support icecast development at www.icecast.org
+ + +
+
diff --git a/admin/manageauth.xsl b/admin/manageauth.xsl new file mode 100755 index 00000000..bf18540e --- /dev/null +++ b/admin/manageauth.xsl @@ -0,0 +1,91 @@ + + + + + +Icecast Streaming Media Server + + + +
+ + + +
+ List MountPoints | + Move Listeners | + Stats | + Status Page +
+
+ + + + + + +
+

Show defined users

+
+
+ +
+
+ + + + +

+ +()

+ + + +
+ Show Listeners | + Move Listeners | + Kill Source +
+

+
+ + + + + + + + + + + + + + + + + + + + + +
User IdPassword
delete
+ + +
+

+

+
+
+
+ +
+
+



+
+
+Support icecast development at www.icecast.org
+ + +
+
diff --git a/admin/moveclients.xsl b/admin/moveclients.xsl new file mode 100755 index 00000000..c6b5ada1 --- /dev/null +++ b/admin/moveclients.xsl @@ -0,0 +1,61 @@ + + + + + + +Icecast Streaming Media Server + + + +
+ + + +
+ List MountPoints | + Move Listeners | + Stats | + Status Page +
+
+ + + + + + +
+ +

Move Clients from ()

+
+
+ +
+
+

Move to which mountpoint ?

+ + + + + + + +
Move from () to () ListenersMove Clients
+

+

+
+
+
+ +
+
+



+
+
+Support icecast development at www.icecast.org
+ + + +
+
diff --git a/admin/response.xsl b/admin/response.xsl new file mode 100755 index 00000000..b498c2d5 --- /dev/null +++ b/admin/response.xsl @@ -0,0 +1,54 @@ + + + + + +Icecast Streaming Media Server + + + +
+ + + +
+ List MountPoints | + Move Listeners | + Stats | + Status Page +
+
+ + + + + + +
+

Icecast Server Response

+
+
+ +
+
+

Response

+ +Message :

+Return Code:

+
+

+

+
+
+ +
+
+



+
+
+Support icecast development at www.icecast.org
+ + + +
+
diff --git a/admin/stats.xsl b/admin/stats.xsl new file mode 100755 index 00000000..04b9a17c --- /dev/null +++ b/admin/stats.xsl @@ -0,0 +1,101 @@ + + + + + +Icecast Streaming Media Server + + + +
+ + + +
+ List Mountpoints | + Move Listeners | + Stats | + Status Page +
+
+ + + + + + +
+

Icecast Status Page

+
+
+ +
+
+

Global Server Stats

+ + + + + + + + + + + +
+
+
+ +
+
+

+

+ +
+
+ +
+
+ + +

+ +() + +

+ + + +
+ List Clients | + Move MountPoints | + Kill Source + | Manage Authentication +
+

+ + + + + + + +
+

+

+
+
+
+
+ +
+
+



+
+
+Support icecast development at www.icecast.org
+ + +
+
diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..7499e3aa --- /dev/null +++ b/autogen.sh @@ -0,0 +1,113 @@ +#!/bin/sh +# Run this to set up the build system: configure, makefiles, etc. +# (based on the version in enlightenment's cvs) + +package="icecast" + +olddir=`pwd` +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +cd "$srcdir" +DIE=0 + +(autoconf --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "You must have autoconf installed to compile $package." + echo "Download the appropriate package for your distribution," + echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" + DIE=1 +} +VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]\).*/\1/" + +# do we need automake? +if test -r Makefile.am; then + echo Checking for automake version + options=`fgrep AUTOMAKE_OPTIONS Makefile.am` + AM_NEEDED=`echo "$options" | $VERSIONGREP` + AM_PROGS=automake + AC_PROGS=aclocal + if test -n "$AM_NEEDED" && test "x$AM_NEEDED" != "x$options" + then + AM_PROGS="automake-$AM_NEEDED automake$AM_NEEDED $AM_PROGS" + AC_PROGS="aclocal-$AM_NEEDED aclocal$AM_NEEDED $AC_PROGS" + else + AM_NEEDED="" + fi + AM_PROGS="$AUTOMAKE $AM_PROGS" + AC_PROGS="$ACLOCAL $AC_PROGS" + for am in $AM_PROGS; do + ($am --version > /dev/null 2>&1) 2>/dev/null || continue + ver=`$am --version | head -1 | $VERSIONGREP` + AWK_RES=`echo $ver $AM_NEEDED | awk '{ if ( $1 >= $2 ) print "yes"; else print "no" }'` + if test "$AWK_RES" = "yes"; then + AUTOMAKE=$am + echo " found $AUTOMAKE" + break + fi + done + for ac in $AC_PROGS; do + ($ac --version > /dev/null 2>&1) 2>/dev/null || continue + ver=`$ac --version < /dev/null | head -1 | $VERSIONGREP` + AWK_RES=`echo $ver $AM_NEEDED | awk '{ if ( $1 >= $2 ) print "yes"; else print "no" }'` + if test "$AWK_RES" = "yes"; then + ACLOCAL=$ac + echo " found $ACLOCAL" + break + fi + done + test -z $AUTOMAKE || test -z $ACLOCAL && { + echo + if test -n "$AM_NEEDED"; then + echo "You must have automake version $AM_NEEDED installed" + echo "to compile $package." + else + echo "You must have automake installed to compile $package." + fi + echo "Download the appropriate package for your distribution," + echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" + DIE=1 + } +fi + +(libtoolize --version) > /dev/null 2>&1 || { + echo + echo "You must have libtool installed to compile $package." + echo "Download the appropriate package for your system," + echo "or get the source from one of the GNU ftp sites" + echo "listed in http://www.gnu.org/order/ftp.html" + DIE=1 +} + +if test "$DIE" -eq 1; then + exit 1 +fi + +echo "Generating configuration files for $package, please wait...." + +ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I m4" +if test -n "$ACLOCAL"; then + echo " $ACLOCAL $ACLOCAL_FLAGS" + $ACLOCAL $ACLOCAL_FLAGS +fi + +echo " autoheader" +autoheader + +echo " libtoolize --automake" +libtoolize --automake + +if test -n "$AUTOMAKE"; then + echo " $AUTOMAKE --add-missing" + $AUTOMAKE --add-missing +fi + +echo " autoconf" +autoconf + +if test -z "$*"; then + echo "I am going to run ./configure with no arguments - if you wish " + echo "to pass any to it, please specify them on the $0 command line." +fi +cd $olddir +$srcdir/configure "$@" && echo diff --git a/conf/Makefile.am b/conf/Makefile.am new file mode 100644 index 00000000..3d4bec06 --- /dev/null +++ b/conf/Makefile.am @@ -0,0 +1,28 @@ +## Process this with automake to create Makefile.in + +AUTOMAKE_OPTIONS = foreign + +EXTRA_DIST = icecast.xml.in +DISTCLEANFILES = icecast.xml.dist + +docdir = $(datadir)/$(PACKAGE)/doc +doc_DATA = icecast.xml.dist + +install-data-hook: + $(mkinstalldirs) $(DESTDIR)$(sysconfdir) + test -f $(DESTDIR)$(sysconfdir)/icecast.xml || \ + $(INSTALL_DATA) icecast.xml.dist $(DESTDIR)$(sysconfdir)/icecast.xml + +edit = sed -e 's,@pkgdatadir\@,$(pkgdatadir),g' \ + -e 's,@localstatedir\@,$(localstatedir),g' \ + -e 's,@PACKAGE\@,$(PACKAGE),g' + +icecast.xml.dist: $(srcdir)/icecast.xml.in + $(edit) $(srcdir)/icecast.xml.in > icecast.xml.dist + +debug: + $(MAKE) all CFLAGS="@DEBUG@" + +profile: + $(MAKE) all CFLAGS="@PROFILE@" + diff --git a/conf/icecast.xml.in b/conf/icecast.xml.in new file mode 100644 index 00000000..6b21d6b3 --- /dev/null +++ b/conf/icecast.xml.in @@ -0,0 +1,165 @@ + + + 100 + 2 + 5 + 102400 + 30 + 15 + 10 + + 65535 + + + + + hackme + + hackme + + + admin + hackme + + + + + + localhost + + + + + + + + 8000 + + + + + + + + + + + + + + + + + 1 + + + + @pkgdatadir@ + + + @localstatedir@/log/@PACKAGE@ + @pkgdatadir@/web + @pkgdatadir@/admin + + + + + + + + + + access.log + error.log + 4 + + + + 0 + + + diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..0c9a7e82 --- /dev/null +++ b/configure.in @@ -0,0 +1,158 @@ +AC_INIT([Icecast], [2.0-kh47], [karl@xiph.org]) + +AC_PREREQ(2.54) +AC_CONFIG_SRCDIR(src/main.c) +dnl Process this file with autoconf to produce a configure script. + +AM_INIT_AUTOMAKE +AM_CONFIG_HEADER(config.h) +AM_MAINTAINER_MODE + +AC_PROG_CC +AC_CANONICAL_HOST +AC_PROG_LIBTOOL + +dnl Set some options based on environment + +DEBUG="-g" +if test -z "$GCC"; then + XIPH_CPPFLAGS="-D_REENTRANT" + case $host in + *-*-irix*) + XIPH_CPPFLAGS="$XIPH_CPPFLAGS -w -signed" + PROFILE="-p -g3 -O2 -signed -D_REENTRANT" + ;; + *-*-solaris*) + XIPH_CFLAGS="-xO4 -xcg92" + XIPH_CPPFLAGS="$XIPH_CPPFLAGS -v -w -fsimple -fast" + PROFILE="-xpg -g -Dsuncc" + ;; + *) + XIPH_CFLAGS="-O" + PROFILE="-g -p" + ;; + esac + + case "$host" in + # for system header breakage + *bsd* | *irix*) + ;; + *) AC_DEFINE([_XOPEN_SOURCE], 600, [Define if you have POSIX and XPG specifications]) + ;; + esac + +else + XIPH_CPPFLAGS="-Wall -ffast-math -fsigned-char" + PROFILE="-pg -g" + AC_DEFINE([_GNU_SOURCE], 1, [Define to include GNU extensions to POSIX]) +fi + +dnl Checks for programs. + +dnl Checks for libraries. + +dnl Checks for header files. +AC_HEADER_STDC + +AC_CHECK_HEADERS([alloca.h]) +AC_CHECK_HEADERS(pwd.h, AC_DEFINE(CHUID, 1, [Define if you have pwd.h]),,) +AC_CHECK_HEADERS(unistd.h, AC_DEFINE(CHROOT, 1, [Define if you have unistd.h]),,) + +dnl Checks for typedefs, structures, and compiler characteristics. + +dnl Check for types + +dnl Checks for library functions. +AC_CHECK_FUNCS(localtime_r poll) +AC_SEARCH_LIBS(nanosleep, rt posix4, + AC_DEFINE(HAVE_NANOSLEEP, 1, [Define if you have nanosleep])) +XIPH_NET + +dnl -- configure options -- + +XIPH_PATH_XSLT +XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$XSLT_CFLAGS]) +XIPH_VAR_PREPEND([XIPH_LIBS],[$XSLT_LIBS]) + +XIPH_PATH_VORBIS(, AC_MSG_ERROR([must have Ogg Vorbis v1.0 installed!])) +XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$VORBIS_CFLAGS]) +XIPH_VAR_PREPEND([XIPH_LIBS],[$VORBIS_LIBS]) + +XIPH_PATH_THEORA(, AC_MSG_WARN([Theora support disabled!])) +XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$THEORA_CFLAGS]) +XIPH_VAR_PREPEND([XIPH_LIBS],[$THEORA_LIBS]) + +XIPH_PATH_SPEEX(, AC_MSG_WARN([Speex support disabled!])) +XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$SPEEX_CFLAGS]) +XIPH_VAR_PREPEND([XIPH_LIBS],[$SPEEX_LIBS]) + +ACX_PTHREAD(, AC_MSG_ERROR([POSIX threads missing])) +XIPH_VAR_APPEND([XIPH_CFLAGS],[$PTHREAD_CFLAGS]) +XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$PTHREAD_CPPFLAGS]) +XIPH_VAR_PREPEND([XIPH_LIBS],[$PTHREAD_LIBS]) + +XIPH_PATH_CURL([ + AC_CHECK_DECL([CURLOPT_NOSIGNAL], + [ AC_DEFINE([HAVE_AUTH_URL], 1, [Define to compile in auth URL support code]) + ICECAST_OPTIONAL="$ICECAST_OPTIONAL auth_url.o" + enable_curl="yes" + XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$CURL_CFLAGS]) + XIPH_VAR_PREPEND([XIPH_LIBS],[$CURL_LIBS]) + ], [ AC_MSG_NOTICE([Your curl dev files are too old (7.10 or above required)]) + ], [#include + ]) + ],[ AC_MSG_NOTICE([libcurl not found]) + ]) +dnl -- YP support -- +AC_ARG_ENABLE([yp], + AC_HELP_STRING([--disable-yp],[disable YP directory support]), + enable_yp="$enableval", + enable_yp="yes") +if test "x$enable_yp" = "xyes" -a "x$enable_curl" = xyes +then + AC_DEFINE([USE_YP], 1, [Define to compile in YP support code]) + ICECAST_OPTIONAL="$ICECAST_OPTIONAL yp.o" +else + AC_MSG_NOTICE([YP support disabled]) +fi +# don't log ip's in the access log +AC_ARG_ENABLE([log-ip], + AC_HELP_STRING([--disable-log-ip],[disable logging of IP's in access log]), + enable_logging_ip="$enableval", + enable_logging_ip="yes" + ) +if test x$enable_logging_ip = xyes; then + AC_DEFINE([HAVE_LOGGING_IP],,[Define to log IP to access log]) +fi + +# use the older format_vorbis with metadata updates +AC_ARG_ENABLE([vorbis-updates], + AC_HELP_STRING([--enable-vorbis-updates],[Allow for metadata via URL, only vorbis supported]), + vorbis_updates="$enableval", + vorbis_updates="no" + ) +if test x$vorbis_updates = xyes; then + ICECAST_OPTIONAL="$ICECAST_OPTIONAL format_vorbis.o" +else + ICECAST_OPTIONAL="$ICECAST_OPTIONAL format_ogg.o" +fi + +dnl Make substitutions + +AC_SUBST(XIPH_CPPFLAGS) +AC_SUBST(XIPH_CFLAGS) +AC_SUBST(XIPH_LIBS) +AC_SUBST(PTHREAD_CPPFLAGS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(LIBTOOL_DEPS) +AC_SUBST(LIBS) +AC_SUBST(DEBUG) +AC_SUBST(CFLAGS) +AC_SUBST(PROFILE) +AC_SUBST(ICECAST_OPTIONAL) + +AC_OUTPUT([Makefile conf/Makefile debian/Makefile src/Makefile src/avl/Makefile +src/httpp/Makefile src/thread/Makefile src/log/Makefile +src/net/Makefile src/timing/Makefile doc/Makefile web/Makefile +admin/Makefile win32/Makefile win32/res/Makefile]) diff --git a/debian/Makefile.am b/debian/Makefile.am new file mode 100644 index 00000000..da81405e --- /dev/null +++ b/debian/Makefile.am @@ -0,0 +1,8 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = 1.6 foreign + +EXTRA_DIST = README.Debian changelog compat control copyright \ + icecast2.1 icecast2.default icecast2.init icecast2.manpages \ + icecast2.postinst icecast2.postrm icecast2.preinst rules watch + diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 00000000..894553ae --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,12 @@ +icecast2 for Debian +------------------- + +It is recommended to run icecast under a dedicated user account, which only +has access to write the log files. The Debian package creates such an +account, named 'icecast', and uses it by default, but you are free to +reconfigure it and remove the account. + +Edit /etc/default/icecast2 to change the init-script configuration. + + -- Keegan Quinn + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..679c6e30 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,198 @@ +icecast2 (1.9+2.0alphasnap2+20030802-1) unstable; urgency=low + + * Added a 'watch' file to automate tracking of updates. + * Now uses dh-buildinfo to store information about the package build + environment. Added a Build-Dependancy to dh-buildinfo. + * Removed cdbs/autotools-vars.mk, from cdbs CVS, because a new release + was made. + * Enabled curl during configure stage, since --disable-curl was recently + broken upstream, and potentially broken YP support can be disabled at + runtime. + * Trimmed ancient upgrade nodes and other cruft, left over from + pre-Debian versions, from README.Debian. + * Removed some autotools build cruft, since bugs were fixed upstream. + * Normalized {preinst,postinst,postrm} filenames to + icecast2.{preinst,postinst,postrm}. Thanks to Emmanuel le Chevoir + for this suggestion. + * Fixed preinst; was stopping /usr/bin/icecast instead of + /usr/bin/icecast2. Thanks to Emmanuel le Chevoir for this suggestion. + * Removed prerm, since it was not serving any purpose. Thanks to + Emmanuel le Chevoir for this suggestion. + * Cleaned up postinst: removed old comments, fixed a path typo in the + configuration file location change message. + * Cleaned up postrm: removed old comments, fixed a typo in the group + removal test. + * Thanks to Jonas Smedegaard for sponsoring this package, and + providing many good suggestions. + + -- Keegan Quinn Sat, 2 Aug 2003 20:28:13 -0700 + +icecast2 (1.9+2.0alphasnap2+20030720-1.1) unstable; urgency=low + + * NMU by sponsor (still closes: Bug#178160). + + -- Jonas Smedegaard Wed, 23 Jul 2003 06:03:42 +0200 + +icecast2 (1.9+2.0alphasnap2+20030720-1) unstable; urgency=low + + * New daily snapshot build. + * Added Build-Dependancy to cdbs, and increased debhelper version + requirement as recommended by cdbs README. + * Corrected Standards-Version to 3.6.0. This package now generates + no lintian errors. + * Updated Recommends for ices to ices2; it was renamed. + * Added more information to the long description. + * Thanks to Jonas Smedegaard for sponsoring this package. + * This revision still closes: #178160 - the last was not uploaded. + + -- Keegan Quinn Mon, 21 Jul 2003 08:55:27 -0700 + +icecast2 (1.9+2.0alphasnap2+20030714-0.2) unstable; urgency=low + + * Sponsored upload. Closes: Bug#178160. + * Switch to cdbs (agreed with maintainer). + * Use cdbs autotools-vars.mk from CVS to avoid cross-compiling on same + host. + * Add build-dependency on libxml2-dev. + * Explicitly configure without curl support to avoid building broken + YP stuff. + * Hack src/Makefile.am to use AM_CFLAGS instead of CFLAGS (which is + overridden by cdbs), and add clean rule to avoid invoking automake. + * Disable daemon by default and hint about changing passwords before + enabling. + * Avoid moving config files from pre-Debian times - instead just print + a warning if config exists in old location (better mess as little as + possible with files not ever claimed to be ours). + * Update README.Debian to reflect the above, include note about YP + support not compiled in, and remove note regarding adoption. + * Strip paths from packaging scripts (they may move around in the + future, and if PATH is wrong then something else broken anyway). + * Avoid removing unused /usr/share/icecast2 on purge. + * Remove icecast group on purge only if empty. + * Standards-version 3.6 (no changes needed). + * Fix wrong escaping of sed vars in conf/Makefile. + * Symlink public files from /usr/share/icecast2 to /etc/icecast2 + (instead of pointing public root dirs below /etc). + * Use upstream config (paths are properly included now). + + -- Jonas Smedegaard Sun, 20 Jul 2003 20:19:30 +0200 + +icecast2 (1.9+2.0alphasnap2+20030714-0.1) unstable; urgency=low + + * New daily snapshot build. + * Updated versioning scheme to reflect (as well as possible) that the + source is a daily snapshot now, not CVS. + + -- Keegan Quinn Mon, 14 Jul 2003 19:39:58 -0700 + +icecast2 (1.9+2.0alphacvs030704-0.1) unstable; urgency=low + + * Constructed a build script to completely automate the construction + of the 'pristine' tarball from CVS. This doesn't really effect the + contents of the package, just makes it easier for me to rebuild. + * New CVS source. + * Removed Build-Dependancy on libcurl2-dev; packages built without this + library present will not have YP functionality, which is okay for + now since it's badly broken. + * Updated the default configuration file, including some new options + recently added upstream. + * Added a number of tweaks to clean up and rearrange new configuration + and documentation added to upstream install target. + * Moved the configuration file from /etc/icecast.xml to + /etc/icecast2/icecast.xml. See README.Debian. + * Nice ugly version number to reflect that upstream calls this the 2.0 + alpha branch, without potentially introducing the need for an epoch. + + -- Keegan Quinn Thu, 3 Jul 2003 23:46:56 -0700 + +icecast2 (0.00.cvs030529-0.1) unstable; urgency=low + + * New CVS source. + * Removed unnecessary debconf stuff. + * Added README.Debian. + * Path updates: + - /usr/share/icecast to /usr/share/icecast2, + - /var/log/icecast to /var/log/icecast2, + - /usr/bin/icecast to /usr/bin/icecast2, + - /usr/share/man/man8/icecast.8.gz to /usr/share/man/man8/icecast2.8.gz. + + -- Keegan Quinn Wed, 29 May 2003 22:53:21 -0700 + +icecast2 (0.00.cvs030403-0.2) unstable; urgency=low + + * Tried to make the default configuration more understandable. + + -- Keegan Quinn Fri, 4 Apr 2003 10:55:27 -0800 + +icecast2 (0.00.cvs030403-0.1) unstable; urgency=low + + * New CVS source. + * Minor changes to postrm. + + -- Keegan Quinn Thu, 3 Apr 2003 16:05:09 -0800 + +icecast2 (0.00.cvs030401-0.7) unstable; urgency=low + + * Minor changes to postinst. + * Added --background flag to initscript, since this version of icecast + does not yet run detached. + + -- Keegan Quinn Thu, 3 Apr 2003 14:24:19 -0800 + +icecast2 (0.00.cvs030401-0.6) unstable; urgency=low + + * Added Debianized configuration file. + * Created and set ownership of /var/log/icecast and /usr/share/icecast. + + -- Keegan Quinn Thu, 3 Apr 2003 14:15:11 -0800 + +icecast2 (0.00.cvs030401-0.5) unstable; urgency=low + + * Attempt at making debconf work properly. + + -- Keegan Quinn Thu, 3 Apr 2003 12:07:16 -0800 + +icecast2 (0.00.cvs030401-0.4) unstable; urgency=low + + * Minor edits to init.d script. + * Added bits to create and remove system accounts appropriately. + * Typo fix in the manual page. + + -- Keegan Quinn Thu, 3 Apr 2003 11:06:48 -0800 + +icecast2 (0.00.cvs030401-0.3) unstable; urgency=low + + * Finished init.d script and manual page. + * Updated postinst to handle rc*.d links. + * Package is now lintian/linda clean. + + -- Keegan Quinn Wed, 2 Apr 2003 16:29:18 -0800 + +icecast2 (0.00.cvs030401-0.2) unstable; urgency=low + + * Updated copyright (replacing dh_make template). + * Fixed duplicate conffiles. + + -- Keegan Quinn Wed, 2 Apr 2003 16:18:02 -0800 + +icecast2 (0.00.cvs030401-0.1) unstable; urgency=low + + * New CVS source. + * Lots of packaging cleanup. + * Initial stab at manual page and init.d script. + + -- Keegan Quinn Wed, 2 Apr 2003 10:25:56 -0800 + +icecast2 (0.00.cvs030320-0.1) unstable; urgency=low + + * New CVS source. + * Automated CVS original source creation. + + -- Keegan Quinn Thu, 20 Mar 2003 12:58:49 -0800 + +icecast2 (0.00.cvs030315-0.1) unstable; urgency=low + + * Initial Release. + + -- Keegan Quinn Sun, 16 Mar 2003 13:45:23 -0800 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..b8626c4c --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +4 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..1194b8c8 --- /dev/null +++ b/debian/control @@ -0,0 +1,22 @@ +Source: icecast2 +Section: sound +Priority: optional +Maintainer: Keegan Quinn +Build-Depends: cdbs, debhelper (>> 4.1.0), dh-buildinfo, libogg-dev (>> 1.0.0), libvorbis-dev (>> 1.0.0), libxslt1-dev, libxml2-dev +Uploaders: Jonas Smedegaard +Standards-Version: 3.6.0 + +Package: icecast2 +Architecture: any +Depends: ${shlibs:Depends} +Recommends: ices2 +Description: streaming Ogg Vorbis/MP3 media server + Icecast is an audio broadcasting system. It can stream music in both + MPEG 1 Layer 3 (MP3) and Ogg Vorbis formats, supports multiple + streams on a single port through the use of "mountpoints," includes + web-based status and management interfaces, and has many other + advanced features. + . + Many standard audio players can connect and listen to Icecast-hosted + streams, since it's based on Nullsoft's Shoutcast protocol and HTTP. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..56e74efa --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +This package was debianized by Keegan Quinn on +Sun, 16 Mar 2003 13:45:23 -0800. + +It was retrieved from http://www.xiph.org/~brendan/snapshots/icecast/ + +Upstream Authors: the icecast team + +Copyright (c) 1999, 2000 the icecast team + +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 latfer 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. + +On Debian systems, the complete text of the GNU General Public License +can be found in `/usr/share/common-licenses/GPL'. + diff --git a/debian/icecast2.1 b/debian/icecast2.1 new file mode 100644 index 00000000..dc1b9e90 --- /dev/null +++ b/debian/icecast2.1 @@ -0,0 +1,24 @@ +.\" Hey, EMACS: -*- nroff -*- +.TH ICECAST2 1 "July 28, 2003" +.SH NAME +icecast2 \- an MP3/Ogg Vorbis broadcast streaming media server +.SH SYNOPSIS +.B icecast2 +-c +.RI config.xml +.SH DESCRIPTION +\fBicecast2\fP is an audio broadcasting system that streams music in +Ogg Vorbis and/or MPEG 1 Layer III format. It accepts stream input +from sources like ices0 and ices2, and broadcasts to clients like xmms. +.SH OPTIONS +\fBicecast2\fP has no command line options, except to specify the location +of an XML configuration file. All operational aspects of the software +are controlled by this XML configuration file. +.SH SEE ALSO +The example configuration files, provided with the package documentation +are the only reliable source of information on this software. +It is still under very active development; +documentation will be written when it is possible to do so. +.SH AUTHOR +icecast2 was created by the icecast team . +This manual page was written by Keegan Quinn . diff --git a/debian/icecast2.default b/debian/icecast2.default new file mode 100644 index 00000000..bcd964ac --- /dev/null +++ b/debian/icecast2.default @@ -0,0 +1,19 @@ +# Defaults for icecast2 initscript +# sourced by /etc/init.d/icecast2 +# installed at /etc/default/icecast2 by the maintainer scripts + +# +# This is a POSIX shell fragment +# + +# Full path to the server configuration file +CONFIGFILE="/etc/icecast2/icecast.xml" + +# Name or ID of the user and group the daemon should run under +USERID=icecast +GROUPID=icecast + +# Edit /etc/icecast2/icecast.xml and change at least the passwords. +# Change this to true when done to enable the init.d script +ENABLE=false + diff --git a/debian/icecast2.init b/debian/icecast2.init new file mode 100644 index 00000000..a1302e96 --- /dev/null +++ b/debian/icecast2.init @@ -0,0 +1,67 @@ +#! /bin/sh +# +# icecast2 +# +# Written by Miquel van Smoorenburg . +# Modified for Debian +# by Ian Murdock . +# +# Further modified by Keegan Quinn +# for use with Icecast 2 +# + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/icecast2 +NAME=icecast2 +DESC=icecast2 + +test -x $DAEMON || exit 0 + +# Defaults +CONFIGFILE="/etc/icecast2/icecast.xml" +CONFIGDEFAULTFILE="/etc/default/icecast2" +USERID=icecast +GROUPID=icecast +ENABLE="false" + +# Reads config file (will override defaults above) +[ -r "$CONFIGDEFAULTFILE" ] && . $CONFIGDEFAULTFILE + +if [ "$ENABLE" != "true" ]; then + echo "$NAME daemon disabled - read $CONFIGDEFAULTFILE." + exit 0 +fi + +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet --chuid $USERID:$GROUPID \ + --background --exec $DAEMON -- -c $CONFIGFILE + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --stop --oknodo --quiet --exec $DAEMON + echo "$NAME." + ;; + reload|force-reload) + echo "Reloading $DESC configuration files." + start-stop-daemon --stop --signal 1 --quiet --exec $DAEMON + ;; + restart) + echo -n "Restarting $DESC: " + start-stop-daemon --stop --oknodo --quiet --exec $DAEMON + sleep 1 + start-stop-daemon --start --quiet --chuid $USERID:$GROUPID \ + --background --exec $DAEMON -- -c $CONFIGFILE + echo "$NAME." + ;; + *) + echo "Usage: $0 {start|stop|restart|reload|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/icecast2.manpages b/debian/icecast2.manpages new file mode 100644 index 00000000..69e59afa --- /dev/null +++ b/debian/icecast2.manpages @@ -0,0 +1 @@ +debian/icecast2.1 diff --git a/debian/icecast2.postinst b/debian/icecast2.postinst new file mode 100644 index 00000000..9278431d --- /dev/null +++ b/debian/icecast2.postinst @@ -0,0 +1,43 @@ +#! /bin/sh +# postinst script for icecast2 + +set -e + +case "$1" in + configure) + + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + exit 0 + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# Move configuration file to current location, if an old one exists +# and the init.d script configuration file was updated +if [ -f /etc/icecast.xml ] && grep -q /etc/icecast2/ /etc/default/icecast2; then + echo "It seems you have an old configuration lying around at" + echo "/etc/icecast.xml. You will need to manually merge with" + echo "the current configuration at /etc/icecast2/icecast.xml." + + echo "See /usr/share/doc/icecast2/examples for new configuration options." +fi + +# Check for an account named 'icecast' +if ! id icecast >/dev/null 2>&1; then + # Create the new system account + adduser --system --disabled-password --disabled-login \ + --home /usr/share/icecast2 --no-create-home --group icecast +fi + +chown -R icecast:icecast /var/log/icecast2 + +#DEBHELPER# + +exit 0 + diff --git a/debian/icecast2.postrm b/debian/icecast2.postrm new file mode 100644 index 00000000..6fbae817 --- /dev/null +++ b/debian/icecast2.postrm @@ -0,0 +1,29 @@ +#! /bin/sh +# postrm script for icecast2 + +set -e + +case "$1" in + purge) + rm -rf /var/log/icecast2 + + if id icecast >/dev/null 2>&1; then + deluser icecast + fi + + # Remove group only if empty + if getent group icecast | awk -F: ' { print $4 } ' | egrep -cq '^$'; then + groupdel icecast + fi + ;; + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + + ;; + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/icecast2.preinst b/debian/icecast2.preinst new file mode 100644 index 00000000..35e111eb --- /dev/null +++ b/debian/icecast2.preinst @@ -0,0 +1,27 @@ +#! /bin/sh +# preinst script for icecast2 + +set -e + +case "$1" in + install|upgrade) + if [ "$1" = "upgrade" ] + then + start-stop-daemon --stop --quiet --oknodo \ + --exec /usr/bin/icecast2 2>/dev/null || true + fi + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 + diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..3c9728bc --- /dev/null +++ b/debian/rules @@ -0,0 +1,34 @@ +#!/usr/bin/make -f +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/autotools.mk + +DEB_INSTALL_CHANGELOGS_ALL = NEWS +DEB_CONFIGURE_SYSCONFDIR = /etc/icecast2 +DEB_MAKE_INVOKE += PACKAGE=icecast2 docdir=/usr/share/doc/icecast2 pkgdatadir=/usr/share/icecast2 + +binary-post-install/icecast2:: + # Debian has a central copy of the GPL, no need to distribute again + rm -f $(DEB_DESTDIR)/usr/share/doc/icecast2/COPYING + + # Live peacefully with icecast 1 + mv $(DEB_DESTDIR)/usr/bin/icecast $(DEB_DESTDIR)/usr/bin/icecast2 + + # Move XSLT templates to /etc and replace with symlinks + for file in `cd $(DEB_DESTDIR)/usr/share && find icecast2 -type f -name *.xsl`; do \ + mkdir -p $(DEB_DESTDIR)/etc/`dirname $$file`; \ + mv $(DEB_DESTDIR)/usr/share/$$file $(DEB_DESTDIR)/etc/$$file; \ + ln -s /etc/$$file $(DEB_DESTDIR)/usr/share/$$file; \ + done + + # NEWS is ChangeLog - avoid original name + rm -f $(DEB_DESTDIR)/usr/share/doc/icecast2/NEWS + + mkdir -p $(CURDIR)/debian/icecast2/var/log/icecast2 + + # Store build information + dh_buildinfo + +clean:: + # Upstream forgot to clean this one it seems... + rm -f conf/icecast.xml.dist + diff --git a/debian/watch b/debian/watch new file mode 100644 index 00000000..63089e27 --- /dev/null +++ b/debian/watch @@ -0,0 +1,3 @@ +version=2 + +http://www.icecast.org/download.html files/icecast/icecast-(.*)\.tar\.gz debian uupdate diff --git a/doc/Index.hhk b/doc/Index.hhk new file mode 100644 index 00000000..c0ba08a9 --- /dev/null +++ b/doc/Index.hhk @@ -0,0 +1,9 @@ + + + + + + +
    +
+ diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..1a837f5f --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,13 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = foreign + +docdir = $(datadir)/doc/icecast +doc_DATA = index.html icecast2_admin.html icecast2_basicsetup.html \ + icecast2_config_file.html icecast2_faq.html icecast2_glossary.html \ + icecast2_introduction.html icecast2_relay.html icecast2_stats.html \ + icecast2_win32.html icecast2_yp.html + +EXTRA_DIST = Index.hhk icecast2.chm icecast2.hhc icecast2.hhp index_win32.html \ + stats1.jpg style.css win32_section1.html win32_section2.html \ + win32_section3.html windowtitle.jpg $(doc_DATA) diff --git a/doc/icecast2.chm b/doc/icecast2.chm new file mode 100644 index 0000000000000000000000000000000000000000..8f6c635ebb4ac5ecfb5f52ddb926185357c842e5 GIT binary patch literal 132247 zcmeFZRd5~6(k-~f%*@QpEQ^_$nVFfHnVBuLShARzC5xG5fyI)=SN^|q?)hWpPTZM? ziJ1pyNA0Z4m06X&qW42(bhM<3vKR;e05JF*K%Zk#LU0ksKq4uk zAt9m!6uZk@@L#5i&}Rjo5EKK6vWk+VoVYSjTsUN)_h+cGrm~8tEFjRXP~g*2kynru z{saS=0~J0^RVA6vc%Zlt{2;4;Ocfy+ptxY*K-|xz)FtIa&lI%2?W2JO8N| z?|%s>IN3XxI=Og$CVPS+{Rxpa^>nv)GWnl*SpO&If6H+Ng=4TZHZ?YMc44NsurLP7 zfQVN>{huyzi5HnErzwS_H<6Apa-8%+T>4AUIVb6%yk=fVqvmv$LU-=fBV}oOn8%|Dcw3E>89) zuEs8w_WvrZf;iUUzm@D{YGe4X+@LPxLi_&)IlCCTIRCS3P#JJK;(x0~MPB$HAQ30b z=D(%5TiUUFGV_OkpkF5G97zAE#PeS_9<&VKX=h^U@yVez&{y?9X&8q8+Kh?b z%E25cyiqhHTMGKq<@w2mv9mK!^bA52HX_64>L&K?E|xBz?mvOSl?ovduBhDHB8D!8 z$_|FcrVMh1wx7>{vkL&gOZngZ$}aX!hUTUWvdY5twhm6F&d#PL48rzyE~a)afMWXa zL1NnfTj5UjHh(q)Sm9FrZ{t5JvXitkvj-SKGW>6YRh$g%oXzZ=Y#IKmd|q<>{|71F z9Ad)EoLsDobX+1VOms|4BA+uMW)3<=MnOhlE+$bfVGd#6PtHGCwlg;Ua|eI^BbNU! zZ=q!R`M|3f8rhfv0>LdlH~cGq8TiYz5^ z^#2)@pX1*-#C!jW2>5jVH-RPm$B+>JN2`?mqls_+(bP)+Cw0nh;w5`u(krcTbEM`c1*dJYzPP9`qSPn`6>dMAI@`jhv4h~vM8!42aoYums- zOMUu*0my)V_XhO;eLe<&eC{CmPsf0e`Ljg>2B77nW&FH-sQ(%K>BIa_455gqAPWW{R${7@YgM*X z3O<*D$0#);E*5E|a=07YKdHmd201A=H zD@g$U99u74mycU5Ia@b6dXsG%-@%5WLWn>zV1Y&f$Oi>rpm;+ffd&u(`oKJdgfOSv zMZu)<4f+XZ4EGik+|pl4RFAPvx%OH0*uulgzvP+LyeZB0?`LfpdmaW%Pz)?P+s?v#OrLU-&$s6xqx(i&HJJDgK4aaC+L9(`3Jo#u*&c^Ctlry z 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�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 literal 0 HcmV?d00001 diff --git a/doc/icecast2.hhc b/doc/icecast2.hhc new file mode 100644 index 00000000..3f9618d3 --- /dev/null +++ b/doc/icecast2.hhc @@ -0,0 +1,62 @@ + + + + + + + + + +
    +
  • + + + +
      +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    • + + + +
    +
+ diff --git a/doc/icecast2.hhp b/doc/icecast2.hhp new file mode 100644 index 00000000..abaa413b --- /dev/null +++ b/doc/icecast2.hhp @@ -0,0 +1,23 @@ +[OPTIONS] +Auto Index=Yes +Compatibility=1.1 or later +Compiled file=icecast2.chm +Contents file=icecast2.hhc +Default Font=,8,0 +Default Window=Icecast 2 Documentation +Default topic=index.html +Display compile progress=No +Full-text search=Yes +Index file=Index.hhk +Language=0x409 English (United States) +Title=Icecast 2 + +[WINDOWS] +Icecast 2 Documentation=,"icecast2.hhc","Index.hhk","index.html",,,,,,0x42520,,0x3006,[26,28,794,605],,,,,,,0 + + +[FILES] +index.html + +[INFOTYPES] + diff --git a/doc/icecast2_admin.html b/doc/icecast2_admin.html new file mode 100755 index 00000000..1e610e5e --- /dev/null +++ b/doc/icecast2_admin.html @@ -0,0 +1,119 @@ + +
+ +

Icecast 2 Admin Interface

+
+
+
+
+

Overview

+

This section contains information about the admin interface of icecast. Through this interface the user can manipulate many server features. From it you can gather statistics, mov e listeners from mountpoint to mountpoint, disconnect connected sources, disconnect connected listeners, and many other activities. Each function is enumerated here as well as an example usage of the function.

+

Each of these functions requires authentication via the <admin-username> and <admin-password> specified in the icecast config file. It is also important to note that in all the examples 192.168.1.10 is used as the example host and 8000 is used as the example port for the icecast server.

+
+
+
+

Admin Functions (mount specific)

+

All these admin functions are mount specific in that they only apply to a particular mountpoint (as opposed to applying to the entire server). Each of these functions requires a mountpoint to be specified as input. +

Metadata Update

+

description

+
+This function provides the ability for either a source client or any external program to update the metadata information for a particular mountpoint. Currently the only metadata supported is song title and only for MP3 streams. +
+

example

+
+http://192.168.1.10:8000/admin/metadata?mount=/mystream&mode=updinfo&song=ACDC+Back+In+Black
+
+
+
+

Fallback Update

+

description

+
+This function provides the ability for either a source client or any external program to update the "fallback mountpoint" for a particular mountpoint. Fallback mounts are those that are used in the even of a source client disconnection. If a source client disconnects for some reason that all currently connected clients are sent immediately to the fallback mountpoint. +
+

example

+
+http://192.168.1.10:8000/admin/fallbacks?mount=/mystream.ogg&fallback=/myfallback.ogg
+
+
+
+

List Clients

+

description

+
+This function lists all the clients currently connected to a specific mountpoint. The results are sent back in XML form. +
+

example

+
+http://192.168.1.10:8000/admin/listclients?mount=/mystream.ogg
+
+
+
+

Move Clients (Listeners)

+

description

+
+This function provides the ability to migrate currently connected listeners from one mountpoint to another. This function requires 2 mountpoints to be passed in: mount (the *from* mountpoint) and destination (the *to* mountpoint). After processing this function all currently connected listeners on mount will be connected to destination. Note that the destination mountpoint must exist and have a sounce client already feeding it a stream. +
+

example

+
+http://192.168.1.10:8000/admin/moveclients?mount=/mystream.ogg&destination=/mynewstream.ogg
+
+
+
+

Kill Client (Listener)

+

description

+
+This function provides the ability to disconnect a specific listener of a currently connected mountpoint. Listeners are identified by a unique id that can be retrieved by via the "List Clients" admin function. This id must be passed in to the request. After processing this request, the listener will no longer be connected to the mountpoint. +
+

example

+
+http://192.168.1.10:8000/admin/killclient?mount=/mystream.ogg&id=21
+
+
+
+

Kill Source

+

description

+
+This function will provide the ability to disconnect a specific mountpoint from the server. The mountpoint to be disconnected is specified via the variable "mount". +
+

example

+
+http://192.168.1.10:8000/admin/killsource?mount=/mystream.ogg
+
+
+
+
+

Admin Functions (general)

+

Stats

+

description

+
+This admin function provides the ability to query the internal statistics kept by the icecast server. Almost all information about the internal workings of the server such as the mountpoints connected, how many client requests have been served, how many listeners for each mountpoint, etc, are available via this admin function.
+Note that this admin function can also be invoked via the http://server:port/admin/stats.xml syntax, however this syntax should not be used and will eventually become deprecated. +
+

example

+
+http://192.168.1.10:8000/admin/stats
+
+
+
+

List Mounts

+

description

+
+This admin function provides the ability to view all the currently connected mountpoints. +
+

example

+
+http://192.168.1.10:8000/admin/listmounts
+
+
+
+
+

Web-Based Admin Interface

+

As an alternative to manually invoking these URLs, a web-based admin interface was developed. This interface provides the same functions that were identified and described above but presents them in a little nicer way. The Web-Based Admin Interface to icecast is shipped with icecast provided in the "admin" directory and comes ready to use. All the user needs to do is set the path to this directory in the config file via the <adminroot> config variable.

+

The Web-Based Admin Interface is a series of XSLT files which are used to display all the XML obtained via the URL admin interface. This can be changed and modified to suit the user's need. Knowledge of XSLT and transformations from XML to HTML are required in order to make changes to these scripts.

+

The main URL for the Web-Based Admin Interface is

+
+http://192.168.1.10:8000/admin/stats.xsl
+
+

From this URL all of the other admin functions can be exercised.

+
+ + diff --git a/doc/icecast2_basicsetup.html b/doc/icecast2_basicsetup.html new file mode 100755 index 00000000..613a2a2b --- /dev/null +++ b/doc/icecast2_basicsetup.html @@ -0,0 +1,59 @@ + +
+ +

Icecast 2 Basic Setup

+
+
+
+
+

Basic Requirements

+

This section will describe the essential requirements in setting up a simple Internet radio station. It is by no means a complete list but should give you enough to get started.

+

There are two major components involved: the streaming server (icecast in this case) and the source client. The icecast server will be the place where all listeners of your station will connect. The source client (in general) runs on a separate machine than icecast, but does not necessarily need to. Source clients send the content to icecast and provide the stream data (encoded audio) that is then relayed out to listeners by icecast.

+

It is important to note that not all source clients work with icecast2. You will need to check to make sure that icecast2 is supported by your chosen source client.

+ +
+
+
+

The Basics

+

Each icecast server can house multiple broadcasts (or mountpoints) each containing a separate stream of content. A listener can only listen to a single mountpoint at a time. This means you can have a single icecast server contain either multiple broadcasts with different content, or possibly the same broadcast but with streams of different bitrates or qualities. In this case each broadcast or stream is a separate mountpoint.

+

At this point, the steps outlined here related to the Unix version or Win32 console version of icecast. Icecast is also available in a Win32 GUI version, and the steps are similar in setup, but not quite the same.

+

The first step in the process is to find and install the icecast2 server itself. How to do this is not contained within this documentation. After installation you should have and icecast binary and 3 directories

+
+ + + + +
confContains the icecast configuration file (icecast.xml) which defines all the configuration parameters for the server.
adminContains xslt files which are used by the icecast server to provide a web-based front end to the administration capabilities of the server.
logsThis is a blank directory which (if specified in the config file) will contain all the logs (there are 2) for icecast.
+
+

The next step is to edit the icecast.xml file and set the appropriate values. Most of the default values are fine as provided, and for a basic setup the following entries should be changed :

+

+<source-password> - will be used by the source client
+<admin-password> - will be used to access admin features of icecast
+<listen-socket> (both port and bind-address)
+<logdir> - directory where log files will be placed
+<webroot> - any static content can be placed here (file serving root)
+<adminroot> - directory containing admin xslt files
+
+

Once the configuration file is modified, you should be able to start the server with the following command

+
+icecast -c /path/to/icecast.xml
+
+

If no error messages are generated, then check the error.log file for the following message :

+
+[2003-10-31  13:04:49] INFO main/main.c icecast server started
+
+

You can also verify that it started by visiting the following URL : http://yourip:port/admin/stats.xml. You should be prompted for a username and password. Enter the username "admin" and the password you entered for <admin-password>. If all is well, you should see an small XML tree which represents icecast statistics (more about that later).

+

Now that the icecast server is started you must now configure your source client. The information you will need for the source client is the following :
+
+IP address and Port of the icecast server - both of these come from <listen-socket>
+source password - from <source-password>
+

Additionally, you will need to choose a mountpoint and specify this in the source client. Icecast does not need to know about each mount point (although you can configure settings for specific mountpoint - this is covered under Advanced configuration) there are, however, some points to mention regarding mountpoints. All Ogg Vorbis streams should have mountpoints that end in .ogg (i,e. /mystream.ogg). This is due to the lazy way most media players infer the type of stream. MP3 streams usually do not contain an extension (/mystream). Mount points also should not contain any spaces or odd characters (again due to the lazy way many of the media players are coded).

+

Once you have configured your source client, you should be able to connect it to the icecast server. Verify that it is connected by hitting the stats.xml URL that was mentioned above.

+

Now that you have the source connnected, listening to the stream involves simply opening the appropriate following URL in a browser: http://yourip:port/mounpointyouspecified.m3u. So, for instance, if you attached your source client to an icecast server located at 192.168.1.10:8000 with a mountpoint of /mystream.ogg, then you would open : http://192.168.1.10:8000/mystream.ogg.m3u. Note that the .m3u extention will serve up a link that opens most media players. Also it is important to note that m3u need not contain only MP3 stream, it can contain streams of arbitrary content-type and is used by icecast to serve a playlist that represents your broadcast to listening clients. Alternatively you can open up the stream URL directly within your media player (http://192.168.1.10:8000/mystream.ogg in this case)

+ +
+
+
+
+ + diff --git a/doc/icecast2_config_file.html b/doc/icecast2_config_file.html new file mode 100755 index 00000000..8d2c8aff --- /dev/null +++ b/doc/icecast2_config_file.html @@ -0,0 +1,403 @@ + +
+ +

Icecast 2 Config File

+
+

Overview

+

+This section will describe each section of the config file and is grouped into the following sections: +

+
  • Limits +
  • Authentication +
  • YP Directory Settings +
  • Misc Server settings +
  • Relay settings +
  • Mount Specific settings +
  • File path settings +
  • Logging +
  • Security + +
    +
    +
    + +

    Limits

    +
    +    <limits>
    +        <clients>100<clients>
    +        <sources>2<sources>
    +        <threadpool>5<threadpool>
    +        <queue-size>102400<queue-size>
    +        <client-timeout>30<client-timeout>
    +        <header-timeout>15<header-timeout>
    +        <source-timeout>10<source-timeout>
    +    <limits>
    +
    +

    This section contains server level settings that, in general, do not need to be changed. Only modify this section if you are know what you are doing. +

    +

    clients

    +
    +Total number of concurrent clients supported by the server. Listeners are considered clients, but so is accesses to any static content (i.e. fileserved content) and also any requests to gather stats. These are max *concurrent* connections for the entire server (not per mountpoint). +
    +

    sources

    +
    +Maximum number of connected sources supported by the server. +
    +

    threadpool

    +
    +This is the number of threads that are started to handle client connections. You may need to increase this value if you are running a high traffic stream. This recommended value is for a small to medium traffic server. +
    +

    queue-size

    +
    +This is the maximum size (in bytes) of a client (listener) queue. A listener may temporarily lag behind due to network congestion and in this case an internal queue is maintained for each listener. If the queue grows larger than this config value, then the listener will be removed from the stream. +
    +

    client-timeout

    +
    +This does not seem to be used. +
    +

    header-timeout

    +
    +The maximum time (in seconds) to wait for a request to come in once the client has made a connection to the server. In general this value should not need to be tweaked. +
    +

    source-timeout

    +
    +If a connected source does not send any data within this timeout period (in seconds), then the source connection will be removed from the server. +
    +
    +
    +
    + +

    Authentication

    +
    +    <authentication>
    +        <source-password>hackme<source-password>
    +        <relay-password>hackme<relay-password>
    +        <admin-user>admin<admin-user>
    +        <admin-password>hackme<admin-password>
    +    <authentication>
    +
    +

    This section contains all the users and passwords used for administration purposes or to connect sources and relays. +

    +

    source-password

    +
    +The unencrypted password used by sources to connect to icecast2. Currently, the username for all source connections must be 'source'. This is likely to change in the future. +
    +

    relay-password

    +
    +Currently not used. +
    +

    admin-user

    +

    admin-password

    +
    +The username/password used for all administration functions. This includes retrieving statistics, accessing the web-based administration screens, etc. A list of these functions can be found in the "Administration" section of the manual. +
    +
    +
    +
    + +

    YP Directory Settings

    +
    +    <directory>
    +        <yp-url-timeout>15<yp-url-timeout>
    +        <yp-url>http://dir.xiph.org/cgi-bin/yp-cgi<yp-url>
    +    <directory>
    +
    +

    This section contains all the settings for listing a stream on any of the Icecast2 YP Directory servers. Multiple occurances of this section can be specified in order to be listed on multiple directory servers. +

    +

    yp-url-timeout

    +
    +This value is the maximum time icecast2 will wait for a response from a particular directory server. The recommended value should be sufficient for most directory servers. +
    +

    yp-url

    +
    +The URL which icecast2 uses to communicate with the Directory server. The value for this setting is provided by the owner of the Directory server. +
    +
    +
    +
    + +

    Misc Server Settings

    +
    +    <hostname>localhost<hostname>
    +
    +    <-- You can use these two if you only want a single listener -->
    +    <-- <port>8000<port> -->
    +    <-- <bind-address>127.0.0.1<bind-address> -->
    +
    +    <-- You may have multiple <listener> elements -->
    +    <listen-socket>
    +        <port>8000<port>
    +        <bind-address>127.0.0.1<bind-address>
    +    <listen-socket>
    +
    +    <fileserve>1<fileserve>
    +
    +

    This section contains miscellaneous server settings. Note that multiple listen-socket sections may be configured in order to have icecast2 listen on multiple network interfaces. If a bind-address is not specified for a particular listen-socket, then the hostname parameter will be used to specify the address that will be bound. +

    +

    port

    +
    +The TCP port that will be used to accept client connections. +
    +

    bind-address

    +
    +And option IP address that can be used to bind to a specific network card. If not supplied, then <hostname> will be used. +
    +

    fileserve

    +
    +This flag turns on the icecast2 fileserver from which static files can be served. All files are served relative to the path specified in the <paths><webroot> configuration setting. +
    +
    +
    +
    + +

    Relay Settings

    +
    +    <master-server>127.0.0.1<master-server>
    +    <master-server-port>8001<master-server-port>
    +    <master-update-interval>120<master-update-interval>
    +    <master-password>hackme<master-password>
    +
    +    <relay>
    +        <server>127.0.0.1<server>
    +        <port>8001<port>
    +        <mount>example.ogg<mount>
    +        <local-mount>different.ogg<local-mount>
    +        <relay-shoutcast-metadata>0<relay-shoutcast-metadata>
    +    <relay>
    +
    +

    This section contains the server's relay settings. There are two types of relays: a "Master server relay" or a "Specific Mountpoint relay." A Master server relay is only supported between icecast2 servers and is used to relays all mountpoints on a remote icecast2 server. + +

    Master Relay

    +The following diagram shows the basics of doing a Master relay. Note that Server 1 is configured with the <master-server>, <master-server-port>, etc settings and Server 2 is the server from which Server 1 will pull all attached mountpoints and relay them. Using a Master Server relay, ALL mountpoints on Server 2 will be relayed. If only specific mountpoints need to be relayed, then you must configure Server 1 as a "Specific Mountpoint Relay". Both Master server relays and Specific Mountpoint relays begin their "relaying" when the Server is started. + +
    +      |-----|                       |-----|
    +      |     |  all mountpoints      |     | /mount1
    +      |     | <-------------------  |     | /mount2.ogg
    +      |-----|                       |-----| /mount3
    +      Icecast 2                     Icecast 2 
    +      Server 1                      Server 2
    +      (RELAY SERVER)                (MASTER SERVER)
    +
    +     configured with
    +     <master-server>
    +     settings 
    +
    +
    + +A server is configured as a Master Server relay by specifying the <master-server>, <master-server-port>,<master-update-interval>,<master-password> values in the config file. The server that is being relayed does not need any special configuration. + +

    +

    master-server

    +
    +This is the IP for the server which contains the mountpoints to be relayed (Master Server). +
    +

    master-server-port

    +
    +This is the TCP Port for the server which contains the mountpoints to be relayed (Master Server). +
    +

    master-update-interval

    +
    +The interval (in seconds) that the Relay Server will poll the Master Server for any new mountpoints to relay. +
    +

    master-password

    +
    +This is the admin password on the Master server. It is used to query the server for a list of mountpoints to relay. +
    +
    +

    Specific Mountpoint Relay

    +The following diagram shows the basics of doing a Specific Mountpoint relay. Note that Server 1 is configured with the <relay> settings and Server 2 is the server from which Server 1 will pull the specified mountpoint(s) and relay them. Using a Specific Mountpoint Relay, only those mountpoints specified on Server 1 will be relayed from Server 2. + +
    +      |-----|                       |-----|
    +      |     |      /mount3          |     | /mount1
    +      |     | <-------------------  |     | /mount2.ogg
    +      |-----|                       |-----| /mount3
    +      Icecast 2                     Icecast 2/Shoutcast/Icecast
    +      Server 1                      Server 2
    +      (RELAY SERVER)                (REMOTE SERVER)
    +
    +     configured with
    +     <relay>
    +     settings 
    +
    +
    + +Specific Mountpoint Relays can be configured to relay from an Icecast 2 server, as well as Icecast 1.x and Shoutcast. +A server is configured as a Specific Mountpoint Server relay by specifying a <relay> XML chunk in the config file for each mountpoint to be relayed. The server that is being relayed does not need any special configuration. + + <server>127.0.0.1<server> + <port>8001<port> + <mount>example.ogg<mount> + <local-mount>different.ogg<local-mount> + <relay-shoutcast-metadata>0<relay-shoutcast-metadata> + +

    +

    server

    +
    +This is the IP for the server which contains the mountpoint to be relayed. +
    +

    port

    +
    +This is the TCP Port for the server which contains the mountpoint to be relayed. +
    +

    mount

    +
    +The mountpoint located on the remote server. If you are relaying a shoutcast stream, this must be '/'. +
    +

    local-mount

    +
    +The name to use for the local mountpoint. This is what the mount will be named on the RELAY SERVER. +
    +

    relay-shoutcast-metadata

    +
    +If you are relaying a Shoutcast stream, you need to specify this indicator to also relay the metadata (song titles) that is part of the Shoutcast stream (1=enabled, 0=disabled). +
    +
    +
    +
    + +

    Mount Specific Settings

    +
    +    <mount>
    +        <mount-name>/example-complex.ogg<mount-name>
    +        <username>othersource<username>
    +        <password>hackmemore<password>
    +        <max-listeners>1<max-listeners>
    +        <dump-file>/tmp/dump-example1.ogg<dump-file>
    +        <fallback-mount>example2.ogg<fallback-mount>
    +        <authentication type="htpasswd">
    +                <option name="filename" value="myauth"/>
    +        </authentication>
    +
    +    <mount>
    +
    +

    This section contains settings which apply only to a specific mountpoint. Within this section you can reserve a specific mountpoint and set a source username/password for that mountpoint (not yet implemented) as well as specify individual settings which will apply only to the supplied mountpoint. +

    +

    mount-name

    +
    +The name of the mount point for which these settings apply. +
    +

    username

    +
    +An optional value which will set the username that a source must use to connect using this mountpoint. +
    +

    password

    +
    +An optional value which will set the password that a source must use to connect using this mountpoint. +
    +

    max-listeners

    +
    +An optional value which will set the maximum number of listeners that can be attached to this mountpoint. +
    +

    dump-file

    +
    +An optional value which will set the filename which will be a dump of the stream coming through on this mountpoint. +
    +

    fallback-mount

    +
    +This specifies a mountpoint that is used in the case of a source disconnect. If listeners are connected to the mount specified by the <mount-name> config value, then if the source is disconnected; all currently connected clients will be moved to the fallback-mount. +
    +

    authentication

    +
    +This specifies that the named mount point will require listener authentication. Currently, we only support a file-based authentication scheme (type=htpasswd). Users and encrypted password are placed in this file (separated by a :) and all requests for this mountpoint will require that a user and password be supplied for authentication purposes. These values are passed in via normal HTTP Basic Authentication means (i.e. http://user:password@stream:port/mountpoint.ogg). Users and Passwords are maintained via the web admin interface. A mountpoint configured with an authenticator will display a red key next to the mount point name on the admin screens. +
    +
    +
    +
    + +

    Path Settings

    +
    +    <paths>
    +        <basedir>./<basedir>
    +        <logdir>./logs<logdir>
    +        <pidfile>./icecast.pid<pidfile>
    +        <webroot>./web<webroot>
    +        <adminroot>./admin<adminroot>
    +        <alias source="/foo" dest="/bar"/>
    +    <paths>
    +
    +

    This section contains paths which are used for various things within icecast. All paths should not end in a '/'. +

    +

    basedir

    +
    +This path is used in conjunction with the chroot settings, and specified the base directory that is chrooted to when the server is started. This feature is not supported on win32. +
    +

    logdir

    +
    +This path specifies the base directory used for logging. Both the error.log and access.log will be created relative to this directory. +
    +

    pidfile

    +
    +This pathname specifies the file to write at startup and to remove at normal shutdown. The file contains the process id of the icecast process. This could be read and used for sending signals icecast. +
    +

    webroot

    +
    +This path specifies the base directory used for all static file requests. This directory can contain all standard file types (including mp3s and ogg vorbis files). For example, if webroot is set to /var/share/icecast2, and a request for http://server:port/mp3/stuff.mp3 comes in, then the file /var/share/icecast2/mp3/stuff.mp3 will be served. +
    +

    adminroot

    +
    +This path specifies the base directory used for all admin requests. More specifically, this is used to hold the XSLT scripts used for the web-based admin interface. The admin directory contained within the icecast distribution contains these files. +
    +

    alias source="/foo" dest="/bar"

    +
    +Aliases are used to provide a way to create multiple mountpoints that refer to the same mountpoint. +
    +
    +
    +
    + +

    Logging Settings

    +
    +    <logging>
    +        <accesslog>access.log</accesslog>
    +        <errorlog>error.log</errorlog>
    +      	<loglevel>4</loglevel> <-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
    +    </logging>
    +
    +

    This section contains information relating to logging within icecast. There are two logfiles currently generated by icecast, an error.log (where all log messages are placed) and an access.log (where all stream/admin/http requests are logged). +

    +

    Note that on non-win32 platforms, a HUP signal can be sent to icecast in which the log files are re-opened for appending giving the ability move/remove the log files. +

    accesslog

    +
    +Into this file, all requests made to the icecast2 will be logged. This file is relative to the path specified by the <logdir> config value. +
    +

    errorlog

    +
    +All icecast generated log messages will be written to this file. If the loglevel is set too high (Debug for instance) then this file can grow fairly large over time. Currently, there is no log-rotation implemented. +
    +

    loglevel

    +
    +Indicates what messages are logged by icecast. Log messages are categorized into one of 4 types, Debug, Info, Warn, and Error.

    The following mapping can be used to set the appropraite value : +
    +
    +
    +
  • loglevel = 4 - Debug, Info, Warn, Error messages are printed +
  • loglevel = 3 - Info, Warn, Error messages are printed +
  • loglevel = 2 - Warn, Error messages are printed +
  • loglevel = 1 - Error messages only are printed +
    + +

    Security Settings

    +
    +    <security>
    +        <chroot>0</chroot>
    +        <changeowner>
    +            <user>nobody</user>
    +            <group>nogroup</group>
    +	</changeowner>
    +    </security>
    +
    +

    This section contains configuration settings that can be used to secure the icecast server by performing a chroot to a secured location. This is currently not supported on win32. +

    +

    chroot

    +
    +An indicator which specifies whether a chroot() will be done when the server is started. The chrooted path is specified by the <basedir> configuration value. +
    +

    changeowner

    +
    +This section indicates the user and group that will own the icecast process when it is started. These need to be valid users on the system. +
    +
  • + + diff --git a/doc/icecast2_faq.html b/doc/icecast2_faq.html new file mode 100755 index 00000000..e78536bc --- /dev/null +++ b/doc/icecast2_faq.html @@ -0,0 +1,85 @@ + +
    + +

    Icecast 2 FAQ

    +
    +
    +
    +
    +

    General Questions

    +

    What is Icecast?

    +
    +

    +Icecast, the project, is a collection of programs and libraries for +streaming audio over the Internet. This includes: +

    +
      +
    • icecast, a program that streams audio data to listeners
    • +
    • libshout, a library for communicating with Icecast servers
    • +
    • IceS, a program that sends audio data to Icecast servers
    • +
    +A source client is an external program which is responsible for sending content data to icecast. Some source clients that support icecast2 are Oddcast, ices2, ices0.3, and DarkIce. +
    + +

    What is icecast, the program?

    +
    +

    +icecast streams audio to listeners, and is compatible with Nullsoft’s Shoutcast. +

    +
    +

    What is libshout ?

    +
    +

    + From the README: +

    +
    +

    + libshout is a library for communicating with and sending data to an icecast server. + It handles the socket connection, the timing of the data, and prevents bad data from getting to the icecast server. +

    +
    +
    + +

    What is IceS?

    +
    +

    + IceS is a program that sends audio data to an icecast server to broadcast to clients. + IceS can either read audio data from disk, + such as from Ogg Vorbis files, or sample live audio from a sound card and encode it on the fly. +

    +
    + +

    How can I view the stream status page?

    +
    +

    + Check your icecast configuration file for an element + called <webroot>. This directory contains web stuff. + In it, place a file called “status.xsl” that + transforms an XML file containing stream + data into a web page + (either XHTML or HTML). +

    +

    + There are sample XSL stylesheets available + in icecast/web/ in the CVS distribution + of icecast. +

    + +

    + In addition, the web directory can + hold multiple status transforms, if you can’t decide which + one you want. +

    +
    + +

    What can I use to listen to an Icecast stream?

    +
    +

    + We maintain a list of Icecast-compatible audio players at + http://www.icecast.org/ +

    +
    +
    + + + diff --git a/doc/icecast2_glossary.html b/doc/icecast2_glossary.html new file mode 100755 index 00000000..946cd480 --- /dev/null +++ b/doc/icecast2_glossary.html @@ -0,0 +1,37 @@ + +
    + +

    Icecast 2 Glossary

    +
    +
    +
    +
    + +

    source client

    +
    +A source client is an external program which is responsible for sending content data to icecast. Some source clients that support icecast2 are Oddcast, ices2, ices0.3, and DarkIce. +
    + +

    slave server (Relay)

    +
    +The slave server in a relay configuration is the server that is pulling the data from the master server. It acts as a listening client to the master server. +
    + +

    master server (Relay)

    +
    +The master server in a relay configuration is the server that has the stream that is being relayed. +
    + +

    mountpoint

    +
    +A mountpoint is a resource on the icecast server that represents a single broadcast stream. Mountpoints are named similar to files (/mystream.ogg, /mymp3stream). When listeners connect to icecast2, they must specify the mountpoint in the request (i.e. http://192.168.1.10:8000/mystream.ogg). Additionally, source clients must specify a mountpoint when they connect as well. Statistics are kept track of by mountpoint. Mountpoints are a fundamental aspect of icecast2 and how it is organized. +
    + +

    fallback mountpoint

    +
    +A fallback mountpoint is configured with a parent mountpoint. In the event of the parent mountpoint losing connection with icecast, Icecast will then move all clients currently connected to the now defunct mountpoint to it's fallback mountpoint. +
    + +
    + + diff --git a/doc/icecast2_introduction.html b/doc/icecast2_introduction.html new file mode 100755 index 00000000..38aca38c --- /dev/null +++ b/doc/icecast2_introduction.html @@ -0,0 +1,43 @@ + +
    + +

    Icecast 2 Introduction

    +
    +
    +
    +
    +

    What is Icecast ?

    +

    Icecast is a streaming media server which currently supports Ogg Vorbis and MP3 audio streams. It can be used to create an Internet radio station or a privately running jukebox and many things in between. It is very versatile in that new formats can be added relatively easily and supports open standards for commuincation and interaction.

    +
    +

    There are two major parts to most streaming media servers: the component providing the content (what we call source clients) and the component which is responsible for serving that content to listeners (this is the function of icecast). +

    +
    +
    +
    +

    What platforms are supported ?

    +

    Currently the following Unix platforms are supported:

    +
  • Linux (Most flavors including Redhat and Debian) +
  • FreeBSD +
  • OpenBSD +
  • Solaris +

    Currently the following Windows platforms are supported:

    +
  • Windows NT +
  • Windows 2000 +
  • Windows XP +
    +
    +
    +

    Where do I go for questions?

    +

    There are many ways to contact the icecast development team

    +

    Best Ways

    +
  • Icecast mailing list http://www.xiph.org/archives +
  • Icecast Developers mailing list http://www.xiph.org/archives +
  • Icecast IRC chat room - irc.freenode.net : #icecast +

    Alternate Ways

    +
  • team@icecast.org +
    +
    +
    +
  • + + diff --git a/doc/icecast2_listenerauth.html b/doc/icecast2_listenerauth.html new file mode 100755 index 00000000..08908a49 --- /dev/null +++ b/doc/icecast2_listenerauth.html @@ -0,0 +1,54 @@ + +
    + +

    Icecast 2 Listener Authentication

    +
    +
    +
    +
    +

    Listener Authentication

    +

    Listener authentication is a feature of icecast which allows you to secure certain mountpoint so that in order to listen, a listener must provide a username and password. With this feature a simple pay-for-play operation can be setup. This section will show you the basics of setting up and maintaining this component.

    + +
    +
    +
    +

    HTPASSWD Listener Authentication

    +

    Config File Entries

    +

    In order to use listener authentication, you MUST configure a mount specific option. This means that you have to provide a <mount> section in the main icecast config file. The following is an example :

    +
    +    <mount>
    +        <mount-name>/example-complex.ogg</mount-name>
    +        <authentication type="htpasswd">
    +                <option name="filename" value="myauth"/>
    +                <option name="allow_duplicate_users" value="0"/>
    +        </authentication>
    +    </mount>
    +
    +

    To support listener authentication you MUST provide at a minimum <mount-name> and <authentication>. The mount-name is the name of the mountpoint that you will use to connect your source client with and authentication configures what type of icecast2 authenticator to use. Currently, only a single type "htpasswd" is implemented. New authenticators will be added later. Each authenticator has a variable number of options that are required and these are specified as shown in the example. The htpasswd authenticator requires a few parameters. The first, filename, specifies the name of the file to use to store users and passwords. Note that this file need not exist (and probably will not exist when you first set it up). Icecast has built-in support for managing users and passwords via the web admin interface. More on this later in this section. The second option, allow_duplicate_users, if set to 0, will prevent multiple connections using the same username. Setting this value to 1 will enable mutltiple connections from the same username on a given mountpoint. Note there is no way to specify a "max connections" for a particular user. + +

    Icecast supports a mixture of streams that require listener authentication and those that do not. Only mounts that are named in the config file can be configured for listener authentication.

    +
    +
    +
    +

    Configuring Users and Passwords

    +

    Once the appropriate entries are made to the config file, connect your source client (using the mountpoint you named in the config file). To configure users and passwords for this stream you must use the web-based admin interface. Navigate to http://server:ip/admin/stats.xsl to begin. If you have configured everything properly, you should see a screen like the following :

    + +

    You will see a red key in front of all mountpoint configured for listener authentication. Also note that this page will only show CONNECTED mountpoints.

    +

    To manage users and passwords for this mountpoint, click on the red key or follow the "Manage Authentication" link. The following screen will be shown :

    + +

    This screen will show all the users configured for this mountpoint. Adding users is as simple as entering a username and password in the fields and clicking "Add New User". Note that usernames MUST be unique and there are NO restrictions on passwords. You can delete users by clicking the appropriate delete link next to each user.

    +
    +
    +
    +

    Finishing it all off

    +

    Ok, so you've created your users, and you have everything setup properly, how do your users login ? Well, we've provided a simple login form that you can use for this purpose. This page (http://server:port/auth.xsl) will bring up a form that users can use to enter their username and password.

    + +

    This page will serve a m3u with the username and password and in most cases should open the correct media player and begin playing your stream

    +
    +
    +
    +

    A note about players and authentication

    +

    We do not have an exaustive list of players that support listener authentication. We use standard HTTP basic authentication, and in general, many media players support this if they support anything at all. Winamp and Foobar2000 support HTTP basic authentication on windows, and XMMS supports it on unix platforms.

    +
    + + diff --git a/doc/icecast2_relay.html b/doc/icecast2_relay.html new file mode 100755 index 00000000..d8cbe1b5 --- /dev/null +++ b/doc/icecast2_relay.html @@ -0,0 +1,47 @@ + +
    + +

    Icecast 2 Relaying

    +
    +
    +
    +
    +

    Overview

    +

    Relaying is the process by which one server mirrors one or more streams from a remote server. The servers need not be of the same type (i.e. icecast can relay from Shoutcast). Relaying is used primarily for large broadcasts that need to distribute listening clients across multiple physical machines.

    +
    +
    +

    Type of Relays

    +

    There are two types of relays that icecast supports. The first type is when both master and slave servers are icecast2 servers. In this case, a "master-slave" relay can be setup such that all that needs to be done is configure the slave server with the connection information (serverip:port) of the master server and the slave will mirror all mountpoints on the master server. The slave will also periodically check the master server to see if any new mountpoints have attached and if so will relay those as well. The second type of relay is a "single-broadcast" relay. In this case, the slave server is configured with a serverip+port+mount and only the mountpoint specified is relayed. In order to relay a broadcast stream on a Shoutcast server, you must use the "single-broadcast" relay and specify a mountpoint of "/".

    +
    +
    +
    +

    Setting Up A Master-Slave Relay

    +

    In order to setup a relay of this type both servers (the one you wish to relay and the one doing the relaying) need to be icecast2 servers. The following configuration snippet is used as an example:

    +
    +    <master-server>192.168.1.11</master-server>
    +    <master-server-port>8001</master-server-port>
    +    <master-update-interval>120</master-update-interval>
    +    <master-password>hackme</master-password>
    +
    +In this example, this configuration is setup in the server which will be doing the relaying (slave server). The master server in this case need not be configured (and actually is unaware of the relaying being performed) as a relay. When the slave server is started, it will connect to the master server located at 192.168.1.11:8001 and will begin to relay all mountpoints connected to the master server. Additionally, every master-update-interval (120 seconds in this case) the slave server will poll the master server to see if any new mountpoints have connected, and if so, the slave server will relay those as well. Note that the names of the mountpoints on the slave server will be identical to those on the master server. +
    +
    +
    +

    Setting Up A Single-Broadcast Relay

    +

    In this case, the master server need not be an icecast2 server. Supported master servers for a single-broadcast relay are Shoutcast, Icecast1.x, and of course Icecast2. The following configuration snippet is used as an example:

    +
       
    +    <relay>
    +        <server>192.168.1.11</server>
    +        <port>8001</port>
    +        <mount>/example.ogg</mount>
    +        <local-mount>/different.ogg</local-mount>
    +        <relay-shoutcast-metadata>0</relay-shoutcast-metadata>
    +    </relay>
    +
    +

    In this example, this configuration is also setup in the server which will be doing the relaying (slave server). The master server in this case need not be configured (and actually is unaware of the relaying being performed) as a relay. When the slave server is started, it will connect to the master server located at 192.168.1.11:8001 and will begin to relay only the mountpoint specified (/example.ogg in this case). Using this type of relay, the user can override the local mountpoint name and make it something entirely different than the one on the master server. Additionally, if the server is a Shoutcast server, then the <mount> must be specified as /. And if you want the Shoutcast relay stream to have metadata contained within it (Shoutcast metadata is embedded in the stream itself) then the <relay-shoutcast-metadata> needs to be set to 1.

    +
    +
    +
    +
    + + diff --git a/doc/icecast2_stats.html b/doc/icecast2_stats.html new file mode 100755 index 00000000..f14e2edf --- /dev/null +++ b/doc/icecast2_stats.html @@ -0,0 +1,83 @@ + +
    + +

    Icecast 2 Server Statistics

    +
    +
    +
    +
    +

    Overview

    +

    This section contains information about the server statistics available from icecast. An example stats XML tree will be shown and each element will be described. The following example stats tree will be used:

    +
    +<?xml version="1.0"?>
    +<icestats>
    +	<client_connections>13</client_connections>
    +	<connections>14</connections>
    +	<source_connections>1</source_connections>
    +	<sources>1</sources>
    +	<source mount="/test.ogg">
    +		<artist></artist>
    +		<audio_info>ice-samplerate=32000;ice-bitrate=Quality -1;ice-channels=1</audio_info>
    +		<ice-bitrate>Quality -1</ice-bitrate>
    +		<ice-channels>1</ice-channels>
    +		<ice-samplerate>32000</ice-samplerate>
    +		<listeners>0</listeners>
    +		<public>0</public>
    +		<title></title>
    +		<type>Ogg Vorbis</type>
    +	</source>
    +</icestats>
    +
    +

    General Statistics

    +

    client-connections

    +
    +Client connections are basically anything that is not a source connection. These include listeners (not concurrent, but cumulative), any admin function accesses, and any static content (file serving) accesses. +
    +

    source-connections

    +
    +Source connections are the number of times (cumulative not currently connected) a source has connected to icecast. +
    +

    connections

    +
    +The total of client + source connections. +
    +

    sources

    +
    +The total of currently connected sources (mountpoints). +
    +

    Source-specific Statistics

    +

    artist

    +
    +Artist of the current song (metadata set by source client). +
    +

    title

    +
    +Title of the current song (metadata set by source client). +
    +

    audio-info

    +
    +Information about the bitrate/samplerate/quality of the stream (set by source client). Also used for YP entries. +
    +

    ice-bitrate

    +

    ice-samplerate

    +

    ice-channels

    +
    +Information about the bitrate/samplerate/quality of the stream (set by source client). +
    +

    listeners

    +
    +The number of currently connected listeners. +
    +

    public

    +
    +Flag that indicates whether this mount is being listed on a YP (sey by source client). +
    +

    type

    +
    +Media type of the stream. +
    +
    +
    +
    + + diff --git a/doc/icecast2_win32.html b/doc/icecast2_win32.html new file mode 100755 index 00000000..0fc37ba9 --- /dev/null +++ b/doc/icecast2_win32.html @@ -0,0 +1,54 @@ + + +
    +

    Icecast 2 - Win32 Specific Documentation

    +
    +

    +The win32 port of icecast2 is simply a UI framework around the core icecast2 server. The win32 version of icecast2 directly uses the main executable of icecast (statically included) and simply provides a GUI interface to icecast2. +

    +

    +Most of the features of icecast2 are available in the win32 port. +

    +

    Server Status Tab

    +

    +The server status tab contains information regarding statistics that are global to the server. There are two types of statistics in icecast2: source level and global statistics. Global statistics are cumulative stats from all sources offered by the server. Source level statistics are stats which apply only to a single source attached to the server. +

    +

    +Examples of global statistics are: +

    +
    +	The number of current sources connected
    +	The number of sources that have attempted connections
    +	Total number of attempted connections to the server
    +
    +

    +

    +The Server Status tab contains at a minimal the global stats for the server. Additionally, you may add source specific stats to this tab. The intent is to provide a single "dashboard view" of what's going on in the server. To add source statistics to the Server Status tab, see the section on the Stats tab. +

    +

    Adding stats to the window title

    +

    +Any stat that is contained on the Server Status tab can be displayed as the icecast2 window title. This provides yet another mechanism by which you can view activities on the server. To enable this feature, right click on any stat in the Server Status tab as seen below : +

    + +
    +
    +
    +

    Removing source level stats from the Server Status Tab

    +

    +To remove a source level stat that you have inserted onto the Server Status Tab, simple right click that statistic and select "Delete from Global Stats". The stat will be deleted from the Server Status tab, but will still remain on the source level Stats tab. +

    + +

    Editing The Icecast Config File

    +

    +Editing the icecast2 configuration file is a very simple process. For a description of what each field means, see the main icecast documenation. Changes to the icecast2 configuration can only be done while the server is stopped. To edit the current server configuration file, select "Configuration/Edit Configuration" from the main menu. +

    + +

    Stats Tab

    +

    +The stats tab contains a view of all the connected mountpoints and the statistics that go along with them. Each connected mountpoint is displayed in the left pane of the window, and all stats for the selected mountpoint are displayed in the right pane of the window. +

    + + +
    + + diff --git a/doc/icecast2_yp.html b/doc/icecast2_yp.html new file mode 100755 index 00000000..248f5868 --- /dev/null +++ b/doc/icecast2_yp.html @@ -0,0 +1,36 @@ + +
    + +

    Icecast 2 YP Directories

    +
    +
    +
    +
    +

    Overview

    +

    A YP (Yellow Pages) directory is a listing of broadcast streams. Icecast2 has it own YP directory located at http://dir.xiph.org. Currently icecast2 can only be listed in an icecast2-supported YP directory. This means that you cannot list your stream in the Shoutcast YP directory.

    +

    In the icecast2 configuration file are all the currently available YP directory servers. Listing your stream in a YP is a combination of settings in the icecast configuration file and also in your source client.

    +
    +
    +
    +

    Configuring icecast2 for YP Support

    +

    First of all, icecast must have been built with YP support. This is automatically done if you have libcurl installed. If libcurl is not detected when icecats is compiled, then YP support is disabled.

    +

    If icecast has been built with YP support, then the following configuration options control the YP directory settings:

    +
    +    <directory>
    +        <yp-url-timeout>15<yp-url-timeout>
    +        <yp-url>http://dir.xiph.org/cgi-bin/yp-cgi<yp-url>
    +    <directory>
    +
    +

    Multiple directory XML chunks can be specified in order to be listed in multiple directories.

    +
    +
    +
    +

    Configuring Your Source Client for YP Support

    +

    This is usually covered in the source client documentation. More specifically, the source client needs to provide the HTTP header ice-public:1 on connect in order to enable YP listing of the stream.

    +

    If a mountpoint is being listed on a YP, then you will see some additional statistics relating to the YP such as last-touch, currently-playing, etc.

    +
    +
    +
    +
    + + diff --git a/doc/index.html b/doc/index.html new file mode 100755 index 00000000..dd8a571d --- /dev/null +++ b/doc/index.html @@ -0,0 +1,76 @@ + +
    + +

    Icecast 2 Documentation Table of Contents

    +
    + +
  • Introduction +
  • Basic Setup +
  • Icecast Config File +
  • Admin Interface +
  • Server Statistics +
  • Relaying +
  • Listing in a YP directory +
  • Listener Authentication +
  • Win32 specific documentation +
  • Glossary +
  • FAQ + + + +
    +icecast 2.x - README
    +---------------------------------------------------------------------
    +
    +Icecast is a streaming media server which currently supports Ogg 
    +Vorbis and MP3 audio streams. It can be used to create an Internet 
    +radio station or a privately running jukebox and many things in 
    +between. It is very versatile in that new formats can be added 
    +relatively easily and supports open standards for commuincation and 
    +interaction.
    +
    +
    +Prerequisites
    +---------------------------------------------------------------------
    +icecast requires the following packages :
    +
    +* libxml2 - http://xmlsoft.org/downloads.html
    +* libxslt - http://xmlsoft.org/XSLT/downloads.html
    +* curl - http://curl.haxx.se/download.html (>= version 7.10 required)
    +  NOTE: icecast may be compiled without curl, however this will
    +        disable all Directory server interaction (YP).
    +* ogg/vorbis - http://www.vorbis.com/files (>= version 1.0 required)
    +
    +A Note About RPMS
    +---------------------------------------------------------------------
    +This section only applies to you if your operating system uses RPMS.
    +
    +In order to build icecast, you will need to install the "devel" RPM
    +packages for each of the prerequisite packages in addition to the
    +normal RPMS for each package.
    +
    +please check the websites for each of the prerequisite packages for
    +appropriate download links for RPMS.
    +
    +
    +Build/Install
    +---------------------------------------------------------------------
    +To build icecast on a Unix platform, perform the following :
    +
    +Run
    +   ./configure
    +   make
    +   make install
    +
    +To build and install this release.
    +
    +A sample config file will be placed in /usr/local/etc (on UNIX) or in 
    +the current working directory (on Win32) and is called icecast.xml
    +
    +Documentation for icecast is available in the doc directory, by 
    +viewing doc/icecast2_TOC.html in a browser.
    +
    +Please email us at icecast@xiph.org or icecast-dev@xiph.org, or come and see
    +us at irc.freenode.net, channel #icecast, if you have any troubles.
    +
    +
    diff --git a/doc/index_win32.html b/doc/index_win32.html new file mode 100644 index 00000000..53f2e3cc --- /dev/null +++ b/doc/index_win32.html @@ -0,0 +1,19 @@ + + + + +Icecast2 Win32 + +
    +

    Icecast 2 - Win32 Specific Documentation

    +
    +

    +The win32 port of icecast2 is simply a UI framework around the core icecast2 server. The win32 version of icecast2 uses directly the main executable of icecast (statically included) and simply provides a nicer, friendlier interface to icecast2. +

    +

    +All of the features of icecast2 are available in the win32 port. +

    +
    + + + diff --git a/doc/listener_auth1.jpg b/doc/listener_auth1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..39e15fe35e1b89b28b1777c4c277f2bf0945bdeb GIT binary patch literal 32628 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 IQ2YNk0gxQ^FaQ7m literal 0 HcmV?d00001 diff --git a/doc/listener_auth3.jpg b/doc/listener_auth3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a2cc1b4f688e4d168eeb1c381b5337574549e95 GIT binary patch literal 23185 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 UD^N1wpID#xOC{sUVb1OO4_@q)NdN!< literal 0 HcmV?d00001 diff --git a/doc/stats1.jpg b/doc/stats1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f4692466950b4e2e8ac88549fd8cdba7934405f8 GIT binary patch literal 69066 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�eZ?tSaI+_CTYpz!o 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 literal 0 HcmV?d00001 diff --git a/doc/style.css b/doc/style.css new file mode 100644 index 00000000..a8fb2ce5 --- /dev/null +++ b/doc/style.css @@ -0,0 +1,79 @@ +.ahem { display: none } +body { + font-family:'Lucida Grande', Verdana, Geneva, Lucida, sans-serif; + background:#000000; + link:#FFFFFF; + text:#323232; + vlink:#FFFFFF; + alink:#FFFFFF; +} +a { + font-weight: bold; + text-decoration: none; +} + +a:link { color: #ff0; } +a:visited { color: #cc3; } +a:hover { color: #f00; } +code,pre { + font-size:90%; + color:#ffffff; + font-family:"Courier New",monospace; + background:#777777; + padding:0 0.5em +} +td { + color:#ffffff; +} +pre { padding:0.5em } +blockquote { margin:0.5em } +blockquote p { margin:0 } + +.width300 { width:300px; background:red } +.width400 { width:400px; background:blue } + +p.ruletest { color:red } + +div.boxtest { + border:2px solid; + padding:30px; + background: #555555; + width:80%;; + color:#ffffff; +} + +div.smallbox{ + border:2px solid; + padding:40px; + background: #ffc; + width:600px; + text:#FFFFFF; +} + +div.indentedbox { + border:0px solid; + padding:10px; + background: #779; +} + + +div.content { + border:20px solid; + padding:30px; + background: #ffc; +} + +div.content { + width:400px; + voice-family: "\"}\""; + voice-family:inherit; + width:300px; +} + /* CSS1 UAs should see and use 2nd width */ + +html>body .content { width:300px } + +p.ruletest { color: blue } + + + diff --git a/doc/win32_section1.html b/doc/win32_section1.html new file mode 100644 index 00000000..a0e45b4a --- /dev/null +++ b/doc/win32_section1.html @@ -0,0 +1,34 @@ + + diff --git a/doc/win32_section2.html b/doc/win32_section2.html new file mode 100644 index 00000000..fd7ebf2c --- /dev/null +++ b/doc/win32_section2.html @@ -0,0 +1,9 @@ + +
    + +

    Editing A Config File

    +
    +

    +Editing the icecast2 configuration file is a very simple process. For a description of what each field means, see the main icecast documenation. Changes to the icecast2 configuration can only be done while the server is stopped. To edit the current server configuration file, select "Configuration/Edit Configuration" from the main menu. +

    +
    diff --git a/doc/win32_section3.html b/doc/win32_section3.html new file mode 100644 index 00000000..220efd3d --- /dev/null +++ b/doc/win32_section3.html @@ -0,0 +1,9 @@ + +
    +

    Stats Tab

    +
    +

    +Explanation of the stats tab here +

    +
    +
    diff --git a/doc/windowtitle.jpg b/doc/windowtitle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b2b4544e4b46445abe61a0f57eff9926380d774f GIT binary patch literal 66677 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

    +Source: http://www.icecast.org/files/%{name}-%{version}.tar.gz +Prefix: %{_prefix} +BuildRoot: %{_tmppath}/%{name}-root + +Requires: libvorbis >= 1.0 +BuildRequires: libvorbis-devel >= 1.0 +Requires: libogg >= 1.0 +BuildRequires: libogg-devel >= 1.0 +Requires: curl >= 7.10.0 +BuildRequires: curl-devel >= 7.10.0 +Requires: libxml2 +BuildRequires: libxml2-devel +Requires: libxslt +BuildRequires: libxslt-devel + +%description +Icecast is a streaming media server which currently supports Ogg Vorbis +and MP3 audio streams. It can be used to create an Internet radio +station or a privately running jukebox and many things in between. +It is very versatile in that new formats can be added relatively +easily and supports open standards for commuincation and interaction. + +%prep +%setup -q -n %{name}-%{version} + +%build +CFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=%{_prefix} --mandir=%{_mandir} --sysconfdir=/etc +make + +%install +[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT + +make DESTDIR=$RPM_BUILD_ROOT install +rm -rf $RPM_BUILD_ROOT%{_datadir}/doc/%{name} + +%clean +[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +%doc README AUTHORS COPYING NEWS TODO +%doc doc/*.html +%doc doc/*.jpg +%doc doc/*.css +%config(noreplace) /etc/%{name}.xml +%{_bindir}/icecast +%{_prefix}/share/icecast/* + +%changelog diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..ae0e3163 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,31 @@ +## Process this with automake to create Makefile.in + +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = avl thread httpp net log timing + +bin_PROGRAMS = icecast + +noinst_HEADERS = admin.h cfgfile.h os.h logging.h sighandler.h connection.h \ + global.h util.h slave.h source.h stats.h refbuf.h client.h format.h \ + format_ogg.h format_vorbis.h compat.h format_mp3.h fserve.h xslt.h yp.h \ + event.h auth.h auth_htpasswd.h auth_cmd.h auth_url.h md5.h +icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c \ + util.c slave.c source.c stats.c refbuf.c client.c format.c format_mp3.c \ + xslt.c fserve.c event.c admin.c auth.c auth_htpasswd.c auth_cmd.c md5.c +EXTRA_icecast_SOURCES = yp.c format_vorbis.c format_ogg.c auth_url.c + +icecast_DEPENDENCIES = @ICECAST_OPTIONAL@ net/libicenet.la thread/libicethread.la \ + httpp/libicehttpp.la log/libicelog.la avl/libiceavl.la timing/libicetiming.la +icecast_LDADD = $(icecast_DEPENDENCIES) @XIPH_LIBS@ + +AM_CFLAGS = @XIPH_CFLAGS@ +AM_CPPFLAGS = @XIPH_CPPFLAGS@ + + +debug: + $(MAKE) all CFLAGS="@DEBUG@" + +profile: + $(MAKE) all CFLAGS="@PROFILE@" + diff --git a/src/TODO b/src/TODO new file mode 100644 index 00000000..bf0c587d --- /dev/null +++ b/src/TODO @@ -0,0 +1 @@ +need a shutdown function in case anything else in the code needs to have icecast gracefully shutdown. \ No newline at end of file diff --git a/src/admin.c b/src/admin.c new file mode 100644 index 00000000..e2d619f8 --- /dev/null +++ b/src/admin.c @@ -0,0 +1,896 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "cfgfile.h" +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "source.h" +#include "global.h" +#include "event.h" +#include "stats.h" +#include "os.h" +#include "xslt.h" + +#include "format.h" + +#include "logging.h" +#include "auth.h" +#ifdef _WIN32 +#define snprintf _snprintf +#endif + +#define CATMODULE "admin" + +#define COMMAND_ERROR (-1) + +/* Mount-specific commands */ +#define COMMAND_RAW_FALLBACK 1 +#define COMMAND_METADATA_UPDATE 2 +#define COMMAND_RAW_SHOW_LISTENERS 3 +#define COMMAND_RAW_MOVE_CLIENTS 4 +#define COMMAND_RAW_MANAGEAUTH 5 + +#define COMMAND_TRANSFORMED_FALLBACK 50 +#define COMMAND_TRANSFORMED_SHOW_LISTENERS 53 +#define COMMAND_TRANSFORMED_MOVE_CLIENTS 54 +#define COMMAND_TRANSFORMED_MANAGEAUTH 55 + +/* Global commands */ +#define COMMAND_RAW_LIST_MOUNTS 101 +#define COMMAND_RAW_STATS 102 +#define COMMAND_RAW_LISTSTREAM 103 +#define COMMAND_PLAINTEXT_LISTSTREAM 104 +#define COMMAND_TRANSFORMED_LIST_MOUNTS 201 +#define COMMAND_TRANSFORMED_STATS 202 +#define COMMAND_TRANSFORMED_LISTSTREAM 203 + +/* Client management commands */ +#define COMMAND_RAW_KILL_CLIENT 301 +#define COMMAND_RAW_KILL_SOURCE 302 +#define COMMAND_TRANSFORMED_KILL_CLIENT 401 +#define COMMAND_TRANSFORMED_KILL_SOURCE 402 + +/* Admin commands requiring no auth */ +#define COMMAND_BUILDM3U 501 + +#define FALLBACK_RAW_REQUEST "fallbacks" +#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl" +#define METADATA_REQUEST "metadata" +#define LISTCLIENTS_RAW_REQUEST "listclients" +#define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl" +#define STATS_RAW_REQUEST "stats" +#define STATS_TRANSFORMED_REQUEST "stats.xsl" +#define LISTMOUNTS_RAW_REQUEST "listmounts" +#define LISTMOUNTS_TRANSFORMED_REQUEST "listmounts.xsl" +#define STREAMLIST_RAW_REQUEST "streamlist" +#define STREAMLIST_TRANSFORMED_REQUEST "streamlist.xsl" +#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt" +#define MOVECLIENTS_RAW_REQUEST "moveclients" +#define MOVECLIENTS_TRANSFORMED_REQUEST "moveclients.xsl" +#define KILLCLIENT_RAW_REQUEST "killclient" +#define KILLCLIENT_TRANSFORMED_REQUEST "killclient.xsl" +#define KILLSOURCE_RAW_REQUEST "killsource" +#define KILLSOURCE_TRANSFORMED_REQUEST "killsource.xsl" +#define ADMIN_XSL_RESPONSE "response.xsl" +#define MANAGEAUTH_RAW_REQUEST "manageauth" +#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl" +#define DEFAULT_RAW_REQUEST "" +#define DEFAULT_TRANSFORMED_REQUEST "" +#define BUILDM3U_RAW_REQUEST "buildm3u" + +#define RAW 1 +#define TRANSFORMED 2 +#define PLAINTEXT 3 + +int admin_get_command(char *command) +{ + if(!strcmp(command, FALLBACK_RAW_REQUEST)) + return COMMAND_RAW_FALLBACK; + else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_FALLBACK; + else if(!strcmp(command, METADATA_REQUEST)) + return COMMAND_METADATA_UPDATE; + else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST)) + return COMMAND_RAW_SHOW_LISTENERS; + else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_SHOW_LISTENERS; + else if(!strcmp(command, STATS_RAW_REQUEST)) + return COMMAND_RAW_STATS; + else if(!strcmp(command, STATS_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_STATS; + else if(!strcmp(command, "stats.xml")) /* The old way */ + return COMMAND_RAW_STATS; + else if(!strcmp(command, LISTMOUNTS_RAW_REQUEST)) + return COMMAND_RAW_LIST_MOUNTS; + else if(!strcmp(command, LISTMOUNTS_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_LIST_MOUNTS; + else if(!strcmp(command, STREAMLIST_RAW_REQUEST)) + return COMMAND_RAW_LISTSTREAM; + else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST)) + return COMMAND_PLAINTEXT_LISTSTREAM; + else if(!strcmp(command, MOVECLIENTS_RAW_REQUEST)) + return COMMAND_RAW_MOVE_CLIENTS; + else if(!strcmp(command, MOVECLIENTS_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_MOVE_CLIENTS; + else if(!strcmp(command, KILLCLIENT_RAW_REQUEST)) + return COMMAND_RAW_KILL_CLIENT; + else if(!strcmp(command, KILLCLIENT_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_KILL_CLIENT; + else if(!strcmp(command, KILLSOURCE_RAW_REQUEST)) + return COMMAND_RAW_KILL_SOURCE; + else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST)) + return COMMAND_RAW_MANAGEAUTH; + else if(!strcmp(command, BUILDM3U_RAW_REQUEST)) + return COMMAND_BUILDM3U; + else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_MANAGEAUTH; + else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_KILL_SOURCE; + else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST)) + return COMMAND_TRANSFORMED_STATS; + else if(!strcmp(command, DEFAULT_RAW_REQUEST)) + return COMMAND_TRANSFORMED_STATS; + else + return COMMAND_ERROR; +} + +static void command_fallback(client_t *client, source_t *source, int response); +static void command_metadata(client_t *client, source_t *source); +static void command_show_listeners(client_t *client, source_t *source, + int response); +static void command_move_clients(client_t *client, source_t *source, + int response); +static void command_stats(client_t *client, int response); +static void command_list_mounts(client_t *client, int response); +static void command_kill_client(client_t *client, source_t *source, + int response); +static void command_manageauth(client_t *client, source_t *source, + int response); +static void command_buildm3u(client_t *client, source_t *source, + int response); +static void command_kill_source(client_t *client, source_t *source, + int response); +static void admin_handle_mount_request(client_t *client, source_t *source, + int command); +static void admin_handle_general_request(client_t *client, int command); +static void admin_send_response(xmlDocPtr doc, client_t *client, + int response, char *xslt_template); +static void html_write(client_t *client, char *fmt, ...); + +xmlDocPtr admin_build_sourcelist(char *current_source) +{ + avl_node *node; + source_t *source; + xmlNodePtr xmlnode, srcnode; + xmlDocPtr doc; + char buf[22]; + time_t now = time(NULL); + + doc = xmlNewDoc("1.0"); + xmlnode = xmlNewDocNode(doc, NULL, "icestats", NULL); + xmlDocSetRootElement(doc, xmlnode); + + if (current_source) { + xmlNewChild(xmlnode, NULL, "current_source", current_source); + } + + node = avl_get_first(global.source_tree); + while(node) { + source = (source_t *)node->key; + thread_mutex_lock (&source->lock); + if (source->running || source->on_demand) + { + srcnode = xmlNewChild (xmlnode, NULL, "source", NULL); + xmlSetProp (srcnode, "mount", source->mount); + + xmlNewChild (srcnode, NULL, "fallback", + (source->fallback_mount != NULL)? + source->fallback_mount:""); + snprintf (buf, sizeof(buf), "%ld", source->listeners); + xmlNewChild (srcnode, NULL, "listeners", buf); + if (source->running) + { + snprintf (buf, sizeof(buf), "%lu", + (unsigned long)(now - source->con->con_time)); + xmlNewChild (srcnode, NULL, "Connected", buf); + xmlNewChild (srcnode, NULL, "Format", + source->format->format_description); + if (source->authenticator) + { + xmlNewChild(srcnode, NULL, "authenticator", + source->authenticator->type); + } + } + } + thread_mutex_unlock (&source->lock); + node = avl_get_next(node); + } + return(doc); +} + +void admin_send_response(xmlDocPtr doc, client_t *client, + int response, char *xslt_template) +{ + xmlChar *buff = NULL; + int len = 0; + ice_config_t *config; + char *fullpath_xslt_template; + int fullpath_xslt_template_len; + char *adminwebroot; + + client->respcode = 200; + if (response == RAW) { + xmlDocDumpMemory(doc, &buff, &len); + html_write(client, "HTTP/1.0 200 OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "\r\n", len); + html_write(client, buff); + } + if (response == TRANSFORMED) { + config = config_get_config(); + adminwebroot = config->adminroot_dir; + config_release_config(); + fullpath_xslt_template_len = strlen(adminwebroot) + + strlen(xslt_template) + 2; + fullpath_xslt_template = malloc(fullpath_xslt_template_len); + memset(fullpath_xslt_template, '\000', fullpath_xslt_template_len); + snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s", + adminwebroot, PATH_SEPARATOR, xslt_template); + html_write(client, "HTTP/1.0 200 OK\r\n" + "Content-Type: text/html\r\n" + "\r\n"); + DEBUG1("Sending XSLT (%s)", fullpath_xslt_template); + xslt_transform(doc, fullpath_xslt_template, client); + free(fullpath_xslt_template); + } + if (buff) { + xmlFree(buff); + } +} +void admin_handle_request(client_t *client, char *uri) +{ + char *mount, *command_string; + int command; + int noauth = 0; + + if(strncmp("/admin/", uri, 7)) { + ERROR0("Internal error: admin request isn't"); + client_send_401(client); + return; + } + + command_string = uri + 7; + + DEBUG1("Got command (%s)", command_string); + command = admin_get_command(command_string); + + if(command < 0) { + ERROR1("Error parsing command string or unrecognised command: %s", + command_string); + client_send_400(client, "Unrecognised command"); + return; + } + + mount = httpp_get_query_param(client->parser, "mount"); + + if(mount != NULL) { + source_t *source; + + if (command == COMMAND_BUILDM3U) { + noauth = 1; + } + /* This is a mount request, handle it as such */ + if (!noauth) { + if(!connection_check_admin_pass(client->parser)) { + if(!connection_check_source_pass(client->parser, mount)) { + INFO1("Bad or missing password on mount modification admin " + "request (command: %s)", command_string); + client_send_401(client); + return; + } + } + } + + avl_tree_rlock(global.source_tree); + source = source_find_mount_raw(mount); + + if (source == NULL) + { + WARN2("Admin command %s on non-existent source %s", + command_string, mount); + avl_tree_unlock(global.source_tree); + client_send_400 (client, "Source does not exist"); + } + else + { + if (source->running == 0) + { + INFO2("Received admin command %s on unavailable mount \"%s\"", + command_string, mount); + avl_tree_unlock (global.source_tree); + client_send_400 (client, "Source is not available"); + return; + } + INFO2("Received admin command %s on mount \"%s\"", + command_string, mount); + admin_handle_mount_request (client, source, command); + avl_tree_unlock(global.source_tree); + } + } + else { + if (command == COMMAND_PLAINTEXT_LISTSTREAM) { + /* this request is used by a slave relay to retrieve + mounts from the master, so handle this request + validating against the relay password */ + if(!connection_check_relay_pass(client->parser)) { + INFO1("Bad or missing password on admin command " + "request (command: %s)", command_string); + client_send_401(client); + return; + } + } + else { + if(!connection_check_admin_pass (client->parser)) { + INFO1("Bad or missing password on admin command " + "request (command: %s)", command_string); + client_send_401(client); + return; + } + } + + admin_handle_general_request(client, command); + } +} + +static void admin_handle_general_request(client_t *client, int command) +{ + switch(command) { + case COMMAND_RAW_STATS: + command_stats(client, RAW); + break; + case COMMAND_RAW_LIST_MOUNTS: + command_list_mounts(client, RAW); + break; + case COMMAND_RAW_LISTSTREAM: + command_list_mounts(client, RAW); + break; + case COMMAND_PLAINTEXT_LISTSTREAM: + command_list_mounts(client, PLAINTEXT); + break; + case COMMAND_TRANSFORMED_STATS: + command_stats(client, TRANSFORMED); + break; + case COMMAND_TRANSFORMED_LIST_MOUNTS: + command_list_mounts(client, TRANSFORMED); + break; + case COMMAND_TRANSFORMED_LISTSTREAM: + command_list_mounts(client, TRANSFORMED); + break; + case COMMAND_TRANSFORMED_MOVE_CLIENTS: + command_list_mounts(client, TRANSFORMED); + break; + default: + WARN0("General admin request not recognised"); + client_send_400(client, "Unknown admin request"); + return; + } +} + +static void admin_handle_mount_request(client_t *client, source_t *source, + int command) +{ + switch(command) { + case COMMAND_RAW_FALLBACK: + command_fallback(client, source, RAW); + break; + case COMMAND_METADATA_UPDATE: + command_metadata(client, source); + break; + case COMMAND_RAW_SHOW_LISTENERS: + command_show_listeners(client, source, RAW); + break; + case COMMAND_RAW_MOVE_CLIENTS: + command_move_clients(client, source, RAW); + break; + case COMMAND_RAW_KILL_CLIENT: + command_kill_client(client, source, RAW); + break; + case COMMAND_RAW_KILL_SOURCE: + command_kill_source(client, source, RAW); + break; + case COMMAND_TRANSFORMED_FALLBACK: + command_fallback(client, source, RAW); + break; + case COMMAND_TRANSFORMED_SHOW_LISTENERS: + command_show_listeners(client, source, TRANSFORMED); + break; + case COMMAND_TRANSFORMED_MOVE_CLIENTS: + command_move_clients(client, source, TRANSFORMED); + break; + case COMMAND_TRANSFORMED_KILL_CLIENT: + command_kill_client(client, source, TRANSFORMED); + break; + case COMMAND_TRANSFORMED_KILL_SOURCE: + command_kill_source(client, source, TRANSFORMED); + break; + case COMMAND_TRANSFORMED_MANAGEAUTH: + command_manageauth(client, source, TRANSFORMED); + break; + case COMMAND_RAW_MANAGEAUTH: + command_manageauth(client, source, RAW); + break; + case COMMAND_BUILDM3U: + command_buildm3u(client, source, RAW); + break; + default: + WARN0("Mount request not recognised"); + client_send_400(client, "Mount request unknown"); + break; + } +} + +#define COMMAND_REQUIRE(client,name,var) \ + do { \ + (var) = httpp_get_query_param((client)->parser, (name)); \ + if((var) == NULL) { \ + client_send_400((client), "Missing parameter"); \ + return; \ + } \ + } while(0); +#define COMMAND_OPTIONAL(client,name,var) \ + (var) = httpp_get_query_param((client)->parser, (name)) + +static void html_success(client_t *client, char *message) +{ + int bytes; + + client->respcode = 200; + bytes = sock_write(client->con->sock, + "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" + "Admin request successful" + "

    %s

    ", message); + if(bytes > 0) client->con->sent_bytes = bytes; + client_destroy(client); +} + +static void html_write(client_t *client, char *fmt, ...) +{ + int bytes; + va_list ap; + + va_start(ap, fmt); + bytes = sock_write_fmt(client->con->sock, fmt, ap); + va_end(ap); + if(bytes > 0) client->con->sent_bytes = bytes; +} + +static void command_move_clients(client_t *client, source_t *source, + int response) +{ + char *dest_source; + source_t *dest; + xmlDocPtr doc; + xmlNodePtr node; + char buf[255]; + int parameters_passed = 0; + + if((COMMAND_OPTIONAL(client, "destination", dest_source))) { + parameters_passed = 1; + } + if (!parameters_passed) { + doc = admin_build_sourcelist(source->mount); + admin_send_response(doc, client, response, + MOVECLIENTS_TRANSFORMED_REQUEST); + xmlFreeDoc(doc); + client_destroy(client); + return; + } + + dest = source_find_mount (dest_source); + + if (dest == NULL) + { + client_send_400 (client, "No such destination"); + return; + } + + if (strcmp (dest->mount, source->mount) == 0) + { + client_send_400 (client, "supplied mountpoints are identical"); + return; + } + + if (dest->running == 0 && dest->on_demand == 0) + { + client_send_400 (client, "Destination not running"); + return; + } + + DEBUG2("source is \"%s\", destination is \"%s\"", source->mount, dest->mount); + + doc = xmlNewDoc("1.0"); + node = xmlNewDocNode(doc, NULL, "iceresponse", NULL); + xmlDocSetRootElement(doc, node); + + source_move_clients (source, dest); + + memset(buf, '\000', sizeof(buf)); + snprintf (buf, sizeof(buf), "Clients moved from %s to %s", + source->mount, dest_source); + xmlNewChild(node, NULL, "message", buf); + xmlNewChild(node, NULL, "return", "1"); + + admin_send_response(doc, client, response, + ADMIN_XSL_RESPONSE); + xmlFreeDoc(doc); + client_destroy(client); +} + +static void command_show_listeners(client_t *client, source_t *source, + int response) +{ + xmlDocPtr doc; + xmlNodePtr node, srcnode; + char *userAgent = NULL; + xmlNodePtr listenernode; + client_t *current; + time_t now = time(NULL); + char buf[22]; + + doc = xmlNewDoc("1.0"); + node = xmlNewDocNode(doc, NULL, "icestats", NULL); + srcnode = xmlNewChild(node, NULL, "source", NULL); + + thread_mutex_lock (&source->lock); + + xmlSetProp(srcnode, "mount", source->mount); + xmlDocSetRootElement(doc, node); + + memset(buf, '\000', sizeof(buf)); + snprintf(buf, sizeof(buf)-1, "%ld", source->listeners); + xmlNewChild(srcnode, NULL, "Listeners", buf); + + current = source->active_clients; + while (current) + { + listenernode = xmlNewChild(srcnode, NULL, "listener", NULL); + xmlNewChild(listenernode, NULL, "IP", current->con->ip); + userAgent = httpp_getvar(current->parser, "user-agent"); + if (userAgent) { + xmlNewChild(listenernode, NULL, "UserAgent", userAgent); + } + else { + xmlNewChild(listenernode, NULL, "UserAgent", "Unknown"); + } + memset(buf, '\000', sizeof(buf)); + snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time); + xmlNewChild(listenernode, NULL, "Connected", buf); + memset(buf, '\000', sizeof(buf)); + snprintf(buf, sizeof(buf)-1, "%lu", current->con->id); + xmlNewChild(listenernode, NULL, "ID", buf); + if (current->username) + xmlNewChild(listenernode, NULL, "username", current->username); + + current = current->next; + } + + thread_mutex_unlock (&source->lock); + + admin_send_response(doc, client, response, + LISTCLIENTS_TRANSFORMED_REQUEST); + xmlFreeDoc(doc); + client_destroy(client); +} + +static void command_buildm3u(client_t *client, source_t *source, + int response) +{ + char *username = NULL; + char *password = NULL; + char *host = NULL; + int port = 0; + ice_config_t *config; + + COMMAND_REQUIRE(client, "username", username); + COMMAND_REQUIRE(client, "password", password); + + config = config_get_config(); + host = strdup(config->hostname); + port = config->port; + config_release_config(); + + client->respcode = 200; + sock_write(client->con->sock, + "HTTP/1.0 200 OK\r\n" + "Content-Type: audio/x-mpegurl\r\n" + "Content-Disposition = attachment; filename=listen.m3u\r\n\r\n" + "http://%s:%s@%s:%d%s\r\n", + username, + password, + host, + port, + source->mount + ); + + free(host); + client_destroy(client); +} + + +static void command_manageauth(client_t *client, source_t *source, + int response) +{ + xmlDocPtr doc; + xmlNodePtr node, srcnode, msgnode; + char *action = NULL; + char *username = NULL; + char *password = NULL; + char *message = NULL; + int ret = AUTH_OK; + + if((COMMAND_OPTIONAL(client, "action", action))) { + if (!strcmp(action, "add")) { + COMMAND_REQUIRE(client, "username", username); + COMMAND_REQUIRE(client, "password", password); + ret = source->authenticator->adduser(source->authenticator, username, password); + if (ret == AUTH_FAILED) { + message = strdup("User add failed - check the icecast error log"); + } + if (ret == AUTH_USERADDED) { + message = strdup("User added"); + } + if (ret == AUTH_USEREXISTS) { + message = strdup("User already exists - not added"); + } + } + if (!strcmp(action, "delete")) { + COMMAND_REQUIRE(client, "username", username); + ret = source->authenticator->deleteuser(source->authenticator, username); + if (ret == AUTH_FAILED) { + message = strdup("User delete failed - check the icecast error log"); + } + if (ret == AUTH_USERDELETED) { + message = strdup("User deleted"); + } + } + } + + doc = xmlNewDoc("1.0"); + node = xmlNewDocNode(doc, NULL, "icestats", NULL); + srcnode = xmlNewChild(node, NULL, "source", NULL); + xmlSetProp(srcnode, "mount", source->mount); + + if (message) { + msgnode = xmlNewChild(node, NULL, "iceresponse", NULL); + xmlNewChild(msgnode, NULL, "message", message); + } + + xmlDocSetRootElement(doc, node); + + source->authenticator->listuser(source->authenticator, srcnode); + + admin_send_response(doc, client, response, + MANAGEAUTH_TRANSFORMED_REQUEST); + if (message) { + free(message); + } + xmlFreeDoc(doc); + client_destroy(client); +} + + +static void command_kill_source(client_t *client, source_t *source, + int response) +{ + xmlDocPtr doc; + xmlNodePtr node; + + doc = xmlNewDoc("1.0"); + node = xmlNewDocNode(doc, NULL, "iceresponse", NULL); + xmlNewChild(node, NULL, "message", "Source Removed"); + xmlNewChild(node, NULL, "return", "1"); + xmlDocSetRootElement(doc, node); + + source->running = 0; + + admin_send_response(doc, client, response, + ADMIN_XSL_RESPONSE); + xmlFreeDoc(doc); + client_destroy(client); +} + +static void command_kill_client(client_t *client, source_t *source, + int response) +{ + char *idtext; + int id; + client_t *listener; + xmlDocPtr doc; + xmlNodePtr node; + char buf[50] = ""; + + COMMAND_REQUIRE(client, "id", idtext); + + id = atoi(idtext); + + thread_mutex_lock (&source->lock); + listener = source_find_client(source, id); + + doc = xmlNewDoc("1.0"); + node = xmlNewDocNode(doc, NULL, "iceresponse", NULL); + xmlDocSetRootElement(doc, node); + DEBUG1("Response is %d", response); + + if(listener != NULL) { + INFO1("Admin request: client %d removed", id); + + /* This tags it for removal on the next iteration of the main source + * loop + */ + listener->con->error = 1; + memset(buf, '\000', sizeof(buf)); + snprintf(buf, sizeof(buf)-1, "Client %d removed", id); + xmlNewChild(node, NULL, "message", buf); + xmlNewChild(node, NULL, "return", "1"); + } + else { + memset(buf, '\000', sizeof(buf)); + snprintf(buf, sizeof(buf)-1, "Client %d not found", id); + xmlNewChild(node, NULL, "message", buf); + xmlNewChild(node, NULL, "return", "0"); + } + thread_mutex_unlock (&source->lock); + admin_send_response(doc, client, response, + ADMIN_XSL_RESPONSE); + xmlFreeDoc(doc); + client_destroy(client); +} + +static void command_fallback(client_t *client, source_t *source, + int response) +{ + char *fallback; + char *old; + + DEBUG0("Got fallback request"); + + COMMAND_REQUIRE(client, "fallback", fallback); + + thread_mutex_lock (&source->lock); + old = source->fallback_mount; + source->fallback_mount = strdup(fallback); + free(old); + thread_mutex_unlock (&source->lock); + + html_success(client, "Fallback configured"); +} + +static void command_metadata(client_t *client, source_t *source) +{ + char *action; + char *song, *title, *artist; + format_plugin_t *plugin; + + DEBUG0("Got metadata update request"); + + COMMAND_REQUIRE(client, "mode", action); + COMMAND_OPTIONAL(client, "song", song); + COMMAND_OPTIONAL(client, "title", title); + COMMAND_OPTIONAL(client, "artist", artist); + + if (strcmp(action, "updinfo") != 0) + { + client_send_400(client, "No such action"); + return; + } + + thread_mutex_lock (&source->lock); + + plugin = source->format; + if (plugin && plugin->set_tag) + { + if (song) + { + plugin->set_tag (plugin, "title", song); + INFO2("Metadata on mountpoint %s changed to \"%s\"", source->mount, song); + } + else + { + if (artist && title) + { + plugin->set_tag (plugin, "artist", artist); + plugin->set_tag (plugin, "title", title); + INFO3("Metadata on mountpoint %s changed to \"%s - %s\"", + source->mount, artist, title); + } + } + + thread_mutex_unlock (&source->lock); + html_success(client, "Metadata update successful"); + } + else + { + thread_mutex_unlock (&source->lock); + client_send_400 (client, "source will not accept URL updates"); + } +} + +static void command_stats(client_t *client, int response) { + xmlDocPtr doc; + + DEBUG0("Stats request, sending xml stats"); + + stats_get_xml(&doc); + admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST); + xmlFreeDoc(doc); + client_destroy(client); + return; +} + +static void command_list_mounts(client_t *client, int response) +{ + DEBUG0("List mounts request"); + + avl_tree_rlock (global.source_tree); + if (response == PLAINTEXT) + { + char buffer [4096], *buf = buffer; + unsigned remaining = sizeof (buffer); + int ret = sprintf (buffer, + "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"); + + avl_node *node = avl_get_first(global.source_tree); + while (node && ret > 0 && (unsigned)ret < remaining) + { + source_t *source = (source_t *)node->key; + remaining -= ret; + buf += ret; + ret = snprintf (buf, remaining, "%s\n", source->mount); + node = avl_get_next(node); + } + avl_tree_unlock (global.source_tree); + /* handle last line */ + if (ret > 0 && (unsigned)ret < remaining) + { + remaining -= ret; + buf += ret; + } + sock_write_bytes (client->con->sock, buffer, sizeof (buffer)-remaining); + } + else + { + xmlDocPtr doc = admin_build_sourcelist(NULL); + avl_tree_unlock (global.source_tree); + + admin_send_response(doc, client, response, + LISTMOUNTS_TRANSFORMED_REQUEST); + xmlFreeDoc(doc); + } + client_destroy(client); + + return; +} + diff --git a/src/admin.h b/src/admin.h new file mode 100644 index 00000000..e54bc722 --- /dev/null +++ b/src/admin.h @@ -0,0 +1,21 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __ADMIN_H__ +#define __ADMIN_H__ + +#include "refbuf.h" +#include "client.h" + +void admin_handle_request(client_t *client, char *uri); + +#endif /* __ADMIN_H__ */ diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 00000000..de7caa73 --- /dev/null +++ b/src/auth.c @@ -0,0 +1,175 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/** + * Client authentication functions + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "auth.h" +#include "auth_htpasswd.h" +#include "auth_cmd.h" +#include "auth_url.h" +#include "source.h" +#include "client.h" +#include "cfgfile.h" +#include "httpp/httpp.h" +#include "md5.h" + +#include "logging.h" +#define CATMODULE "auth" + + + +auth_result auth_check_client(source_t *source, client_t *client) +{ + auth_t *authenticator = source->authenticator; + auth_result result; + + if(authenticator) { + /* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */ + char *header = httpp_getvar(client->parser, "authorization"); + char *userpass, *tmp; + char *username, *password; + + if(header == NULL) + return AUTH_FAILED; + + if(strncmp(header, "Basic ", 6)) { + INFO0("Authorization not using Basic"); + return 0; + } + + userpass = util_base64_decode(header+6); + if(userpass == NULL) { + WARN1("Base64 decode of Authorization header \"%s\" failed", + header+6); + return AUTH_FAILED; + } + + tmp = strchr(userpass, ':'); + if(!tmp) { + free(userpass); + return AUTH_FAILED; + } + + *tmp = 0; + username = userpass; + password = tmp+1; + + client->username = strdup (username); + client->password = strdup (password); + + result = authenticator->authenticate (source, client); + + free(userpass); + + return result; + } + else + { + /* just add the client */ + add_authenticated_client (source, client); + return AUTH_OK; + } +} + + +void auth_clear(auth_t *authenticator) +{ + if (authenticator == NULL) + return; + authenticator->free (authenticator); + free (authenticator->type); + free (authenticator); +} + + +auth_t *auth_get_authenticator(char *type, config_options_t *options) +{ + auth_t *auth = NULL; +#ifdef HAVE_AUTH_URL + if(!strcmp(type, "url")) { + auth = auth_get_url_auth(options); + auth->type = strdup(type); + } + else +#endif + if(!strcmp(type, "command")) { +#ifdef WIN32 + ERROR1("Authenticator type: \"%s\" not supported on win32 platform", type); + return NULL; +#else + auth = auth_get_cmd_auth(options); + auth->type = strdup(type); +#endif + } + else if(!strcmp(type, "htpasswd")) { + auth = auth_get_htpasswd_auth(options); + auth->type = strdup(type); + } + else { + ERROR1("Unrecognised authenticator type: \"%s\"", type); + return NULL; + } + + if(!auth) + ERROR1("Couldn't configure authenticator of type \"%s\"", type); + + return auth; +} + + +/* place authenticated client on named source */ +int auth_postprocess_client (const char *mount, client_t *client) +{ + int ret = -1; + source_t *source; + avl_tree_unlock (global.source_tree); + source = source_find_mount (mount); + if (source) + { + ret = 0; + thread_mutex_lock (&source->lock); + if (source->running) + add_authenticated_client (source, client); + else + ret = -1; + thread_mutex_unlock (&source->lock); + } + avl_tree_unlock (global.source_tree); + if (ret < 0) + source_free_client (NULL, client); + return ret; +} + + +void auth_close_client (client_t *client) +{ + /* failed client, drop global count */ + global_lock(); + global.clients--; + global_unlock(); + if (client->respcode) + client_destroy (client); + else + client_send_401 (client); +} + diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 00000000..dc8e8b96 --- /dev/null +++ b/src/auth.h @@ -0,0 +1,56 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __AUTH_H__ +#define __AUTH_H__ + +#include "source.h" +#include "client.h" +#include "config.h" +#include +#include +#include + +typedef enum +{ + AUTH_OK, + AUTH_FAILED, + AUTH_USERADDED, + AUTH_USEREXISTS, + AUTH_USERDELETED, +} auth_result; + +typedef struct auth_tag +{ + /* Authenticate using the given username and password */ + auth_result (*authenticate)(source_t *source, client_t *client); + void (*free)(struct auth_tag *self); + auth_result (*adduser)(struct auth_tag *auth, const char *username, const char *password); + auth_result (*deleteuser)(struct auth_tag *auth, const char *username); + auth_result (*listuser)(struct auth_tag *auth, xmlNodePtr srcnode); + void (*release_client)(struct source_tag *source, client_t *client); + int (*checkuser)(source_t *source, client_t *client); + + void *state; + void *type; +} auth_t; + +auth_result auth_check_client(source_t *source, client_t *client); + +auth_t *auth_get_authenticator(char *type, config_options_t *options); +void auth_clear(auth_t *authenticator); +int auth_postprocess_client (const char *mount, client_t *client); +void auth_close_client (client_t *client); + +#endif + + diff --git a/src/cfgfile.c b/src/cfgfile.c new file mode 100644 index 00000000..f97a81e0 --- /dev/null +++ b/src/cfgfile.c @@ -0,0 +1,958 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "thread/thread.h" +#include "cfgfile.h" +#include "refbuf.h" +#include "client.h" +#include "logging.h" + +#define CATMODULE "CONFIG" +#define CONFIG_DEFAULT_LOCATION "Earth" +#define CONFIG_DEFAULT_ADMIN "icemaster@localhost" +#define CONFIG_DEFAULT_CLIENT_LIMIT 256 +#define CONFIG_DEFAULT_SOURCE_LIMIT 16 +#define CONFIG_DEFAULT_QUEUE_SIZE_LIMIT (100*1024) +#define CONFIG_DEFAULT_BURST_SIZE (64*1024) +#define CONFIG_DEFAULT_THREADPOOL_SIZE 4 +#define CONFIG_DEFAULT_CLIENT_TIMEOUT 30 +#define CONFIG_DEFAULT_HEADER_TIMEOUT 15 +#define CONFIG_DEFAULT_SOURCE_TIMEOUT 10 +#define CONFIG_DEFAULT_SOURCE_PASSWORD "changeme" +#define CONFIG_DEFAULT_RELAY_PASSWORD "changeme" +#define CONFIG_DEFAULT_ICE_LOGIN 0 +#define CONFIG_DEFAULT_FILESERVE 1 +#define CONFIG_DEFAULT_TOUCH_FREQ 5 +#define CONFIG_DEFAULT_HOSTNAME "localhost" +#define CONFIG_DEFAULT_ACCESS_LOG "access.log" +#define CONFIG_DEFAULT_ERROR_LOG "error.log" +#define CONFIG_DEFAULT_LOG_LEVEL 4 +#define CONFIG_DEFAULT_CHROOT 0 +#define CONFIG_DEFAULT_CHUID 0 +#define CONFIG_DEFAULT_USER NULL +#define CONFIG_DEFAULT_GROUP NULL +#define CONFIG_MASTER_UPDATE_INTERVAL 120 +#define CONFIG_YP_URL_TIMEOUT 10 + +#ifndef _WIN32 +#define CONFIG_DEFAULT_BASE_DIR "/usr/local/icecast" +#define CONFIG_DEFAULT_LOG_DIR "/usr/local/icecast/logs" +#define CONFIG_DEFAULT_WEBROOT_DIR "/usr/local/icecast/webroot" +#define CONFIG_DEFAULT_ADMINROOT_DIR "/usr/local/icecast/admin" +#else +#define CONFIG_DEFAULT_BASE_DIR ".\\" +#define CONFIG_DEFAULT_LOG_DIR ".\\logs" +#define CONFIG_DEFAULT_WEBROOT_DIR ".\\webroot" +#define CONFIG_DEFAULT_ADMINROOT_DIR ".\\admin" +#endif + +static ice_config_t _current_configuration; +static ice_config_locks _locks; + +static void _set_defaults(ice_config_t *c); +static void _parse_root(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_limits(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_directory(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_paths(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_logging(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_security(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_authentication(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *c); +static void _parse_relay(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_mount(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); +static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *c); +static void _add_server(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); + +static void create_locks() { + thread_mutex_create("relay lock", &_locks.relay_lock); + thread_mutex_create("mounts lock", &_locks.mounts_lock); + thread_mutex_create("config lock", &_locks.config_lock); +} + +static void release_locks() { + thread_mutex_destroy(&_locks.relay_lock); + thread_mutex_destroy(&_locks.mounts_lock); + thread_mutex_destroy(&_locks.config_lock); +} + +void config_initialize(void) { + create_locks(); +} + +void config_shutdown(void) { + config_get_config(); + config_clear(&_current_configuration); + config_release_config(); + release_locks(); +} + +void config_init_configuration(ice_config_t *configuration) +{ + memset(configuration, 0, sizeof(ice_config_t)); + _set_defaults(configuration); +} + +void config_clear(ice_config_t *c) +{ + ice_config_dir_t *dirnode, *nextdirnode; + relay_server *relay, *nextrelay; + mount_proxy *mount, *nextmount; + aliases *alias, *nextalias; + int i; + config_options_t *option; + + if (c->config_filename) + free(c->config_filename); + + if (c->location && c->location != CONFIG_DEFAULT_LOCATION) + xmlFree(c->location); + if (c->admin && c->admin != CONFIG_DEFAULT_ADMIN) + xmlFree(c->admin); + if (c->source_password && c->source_password != CONFIG_DEFAULT_SOURCE_PASSWORD) + xmlFree(c->source_password); + if (c->admin_username) + xmlFree(c->admin_username); + if (c->admin_password) + xmlFree(c->admin_password); + if (c->relay_username) + xmlFree(c->relay_username); + if (c->relay_password) + xmlFree(c->relay_password); + if (c->hostname && c->hostname != CONFIG_DEFAULT_HOSTNAME) + xmlFree(c->hostname); + if (c->base_dir && c->base_dir != CONFIG_DEFAULT_BASE_DIR) + xmlFree(c->base_dir); + if (c->log_dir && c->log_dir != CONFIG_DEFAULT_LOG_DIR) + xmlFree(c->log_dir); + if (c->webroot_dir && c->webroot_dir != CONFIG_DEFAULT_WEBROOT_DIR) + xmlFree(c->webroot_dir); + if (c->adminroot_dir && c->adminroot_dir != CONFIG_DEFAULT_ADMINROOT_DIR) + xmlFree(c->adminroot_dir); + if (c->pidfile) + xmlFree(c->pidfile); + if (c->access_log && c->access_log != CONFIG_DEFAULT_ACCESS_LOG) + xmlFree(c->access_log); + if (c->error_log && c->error_log != CONFIG_DEFAULT_ERROR_LOG) + xmlFree(c->error_log); + for(i=0; i < MAX_LISTEN_SOCKETS; i++) { + if (c->listeners[i].bind_address) xmlFree(c->listeners[i].bind_address); + } + if (c->master_server) xmlFree(c->master_server); + if (c->master_username) xmlFree(c->master_username); + if (c->master_password) xmlFree(c->master_password); + if (c->user) xmlFree(c->user); + if (c->group) xmlFree(c->group); + + thread_mutex_lock(&(_locks.relay_lock)); + relay = c->relay; + while(relay) { + nextrelay = relay->next; + xmlFree(relay->server); + xmlFree(relay->mount); + xmlFree(relay->localmount); + free(relay); + relay = nextrelay; + } + thread_mutex_unlock(&(_locks.relay_lock)); + + thread_mutex_lock(&(_locks.mounts_lock)); + mount = c->mounts; + while(mount) { + nextmount = mount->next; + xmlFree(mount->mountname); + xmlFree(mount->username); + xmlFree(mount->password); + xmlFree(mount->dumpfile); + xmlFree(mount->on_connect); + xmlFree(mount->on_disconnect); + xmlFree(mount->fallback_mount); + + xmlFree(mount->auth_type); + option = mount->auth_options; + while(option) { + config_options_t *nextopt = option->next; + xmlFree(option->name); + xmlFree(option->value); + free(option); + option = nextopt; + } + + free(mount); + mount = nextmount; + } + thread_mutex_unlock(&(_locks.mounts_lock)); + + alias = c->aliases; + while(alias) { + nextalias = alias->next; + xmlFree(alias->source); + xmlFree(alias->destination); + xmlFree(alias->bind_address); + free(alias); + alias = nextalias; + } + + dirnode = c->dir_list; + while(dirnode) { + nextdirnode = dirnode->next; + xmlFree(dirnode->host); + free(dirnode); + dirnode = nextdirnode; + } +#ifdef HAVE_YP + i = 0; + while (i < c->num_yp_directories) + { + xmlFree (c->yp_url[i]); + i++; + } +#endif + + memset(c, 0, sizeof(ice_config_t)); +} + +int config_initial_parse_file(const char *filename) +{ + /* Since we're already pointing at it, we don't need to copy it in place */ + return config_parse_file(filename, &_current_configuration); +} + +int config_parse_file(const char *filename, ice_config_t *configuration) +{ + xmlDocPtr doc; + xmlNodePtr node; + + if (filename == NULL || strcmp(filename, "") == 0) return CONFIG_EINSANE; + + xmlInitParser(); + doc = xmlParseFile(filename); + if (doc == NULL) { + return CONFIG_EPARSE; + } + + node = xmlDocGetRootElement(doc); + if (node == NULL) { + xmlFreeDoc(doc); + xmlCleanupParser(); + return CONFIG_ENOROOT; + } + + if (strcmp(node->name, "icecast") != 0) { + xmlFreeDoc(doc); + xmlCleanupParser(); + return CONFIG_EBADROOT; + } + + config_init_configuration(configuration); + + configuration->config_filename = (char *)strdup(filename); + + _parse_root(doc, node->xmlChildrenNode, configuration); + + xmlFreeDoc(doc); + + return 0; +} + +int config_parse_cmdline(int arg, char **argv) +{ + return 0; +} + +ice_config_locks *config_locks(void) +{ + return &_locks; +} + +void config_release_config(void) +{ + thread_mutex_unlock(&(_locks.config_lock)); +} + +ice_config_t *config_get_config(void) +{ + thread_mutex_lock(&(_locks.config_lock)); + return &_current_configuration; +} + +/* MUST be called with the lock held! */ +void config_set_config(ice_config_t *config) { + memcpy(&_current_configuration, config, sizeof(ice_config_t)); +} + +ice_config_t *config_get_config_unlocked(void) +{ + return &_current_configuration; +} + +static void _set_defaults(ice_config_t *configuration) +{ + configuration->location = CONFIG_DEFAULT_LOCATION; + configuration->admin = CONFIG_DEFAULT_ADMIN; + configuration->client_limit = CONFIG_DEFAULT_CLIENT_LIMIT; + configuration->source_limit = CONFIG_DEFAULT_SOURCE_LIMIT; + configuration->queue_size_limit = CONFIG_DEFAULT_QUEUE_SIZE_LIMIT; + configuration->burst_size_limit = CONFIG_DEFAULT_BURST_SIZE; + configuration->threadpool_size = CONFIG_DEFAULT_THREADPOOL_SIZE; + configuration->client_timeout = CONFIG_DEFAULT_CLIENT_TIMEOUT; + configuration->header_timeout = CONFIG_DEFAULT_HEADER_TIMEOUT; + configuration->source_timeout = CONFIG_DEFAULT_SOURCE_TIMEOUT; + configuration->source_password = CONFIG_DEFAULT_SOURCE_PASSWORD; + configuration->ice_login = CONFIG_DEFAULT_ICE_LOGIN; + configuration->fileserve = CONFIG_DEFAULT_FILESERVE; + configuration->touch_interval = CONFIG_DEFAULT_TOUCH_FREQ; + configuration->dir_list = NULL; + configuration->hostname = CONFIG_DEFAULT_HOSTNAME; + configuration->port = 0; + configuration->listeners[0].port = 0; + configuration->listeners[0].bind_address = NULL; + configuration->master_server = NULL; + configuration->master_server_port = 0; + configuration->master_update_interval = CONFIG_MASTER_UPDATE_INTERVAL; + configuration->master_username = NULL; + configuration->master_password = NULL; + configuration->base_dir = CONFIG_DEFAULT_BASE_DIR; + configuration->log_dir = CONFIG_DEFAULT_LOG_DIR; + configuration->webroot_dir = CONFIG_DEFAULT_WEBROOT_DIR; + configuration->adminroot_dir = CONFIG_DEFAULT_ADMINROOT_DIR; + configuration->access_log = CONFIG_DEFAULT_ACCESS_LOG; + configuration->error_log = CONFIG_DEFAULT_ERROR_LOG; + configuration->loglevel = CONFIG_DEFAULT_LOG_LEVEL; + configuration->chroot = CONFIG_DEFAULT_CHROOT; + configuration->chuid = CONFIG_DEFAULT_CHUID; + configuration->user = CONFIG_DEFAULT_USER; + configuration->group = CONFIG_DEFAULT_GROUP; + configuration->num_yp_directories = 0; + configuration->relay_username = NULL; + configuration->relay_password = NULL; +} + +static void _parse_root(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + char *tmp; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "location") == 0) { + if (configuration->location && configuration->location != CONFIG_DEFAULT_LOCATION) xmlFree(configuration->location); + configuration->location = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "admin") == 0) { + if (configuration->admin && configuration->admin != CONFIG_DEFAULT_ADMIN) xmlFree(configuration->admin); + configuration->admin = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if(strcmp(node->name, "authentication") == 0) { + _parse_authentication(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "source-password") == 0) { + /* TODO: This is the backwards-compatibility location */ + char *mount, *pass; + if ((mount = (char *)xmlGetProp(node, "mount")) != NULL) { + pass = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + /* FIXME: This is a placeholder for per-mount passwords */ + } + else { + if (configuration->source_password && configuration->source_password != CONFIG_DEFAULT_SOURCE_PASSWORD) xmlFree(configuration->source_password); + configuration->source_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } + } else if (strcmp(node->name, "icelogin") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->ice_login = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "fileserve") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->fileserve = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "hostname") == 0) { + if (configuration->hostname && configuration->hostname != CONFIG_DEFAULT_HOSTNAME) xmlFree(configuration->hostname); + configuration->hostname = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "listen-socket") == 0) { + _parse_listen_socket(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "port") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->port = atoi(tmp); + configuration->listeners[0].port = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "bind-address") == 0) { + if (configuration->listeners[0].bind_address) + xmlFree(configuration->listeners[0].bind_address); + configuration->listeners[0].bind_address = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "master-server") == 0) { + if (configuration->master_server) xmlFree(configuration->master_server); + configuration->master_server = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "master-username") == 0) { + if (configuration->master_username) xmlFree(configuration->master_username); + configuration->master_username = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "master-password") == 0) { + if (configuration->master_password) xmlFree(configuration->master_password); + configuration->master_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "master-server-port") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->master_server_port = atoi(tmp); + xmlFree (tmp); + } else if (strcmp(node->name, "master-update-interval") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->master_update_interval = atoi(tmp); + xmlFree (tmp); + } else if (strcmp(node->name, "limits") == 0) { + _parse_limits(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "relay") == 0) { + _parse_relay(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "mount") == 0) { + _parse_mount(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "directory") == 0) { + _parse_directory(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "paths") == 0) { + _parse_paths(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "logging") == 0) { + _parse_logging(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "security") == 0) { + _parse_security(doc, node->xmlChildrenNode, configuration); + } + } while ((node = node->next)); +} + +static void _parse_limits(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + char *tmp; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "clients") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->client_limit = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "sources") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->source_limit = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "burst-size") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->burst_size_limit = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "queue-size") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->queue_size_limit = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "threadpool") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->threadpool_size = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "client-timeout") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->client_timeout = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "header-timeout") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->header_timeout = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "source-timeout") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->source_timeout = atoi(tmp); + if (tmp) xmlFree(tmp); + } + } while ((node = node->next)); +} + +static void _parse_mount(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + char *tmp; + mount_proxy *mount = calloc(1, sizeof(mount_proxy)); + mount_proxy *current = configuration->mounts; + mount_proxy *last=NULL; + xmlNodePtr option; + config_options_t *last_option; + + while(current) { + last = current; + current = current->next; + } + + if(last) + last->next = mount; + else + configuration->mounts = mount; + + mount->max_listeners = -1; + mount->next = NULL; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "mount-name") == 0) { + mount->mountname = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "username") == 0) { + mount->username = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "password") == 0) { + mount->password = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "dump-file") == 0) { + mount->dumpfile = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "fallback-mount") == 0) { + mount->fallback_mount = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "fallback-when-full") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + mount->fallback_when_full = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "max-listeners") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + mount->max_listeners = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "fallback-override") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + mount->fallback_override = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "no-mount") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + mount->no_mount = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "no-yp") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + mount->no_yp = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "authentication") == 0) { + mount->auth_type = xmlGetProp(node, "type"); + option = node->xmlChildrenNode; + last_option = NULL; + while(option != NULL) { + if(strcmp(option->name, "option") == 0) { + config_options_t *opt = malloc(sizeof(config_options_t)); + opt->name = xmlGetProp(option, "name"); + if(!opt->name) { + free(opt); + option = option->next; + continue; + } + opt->value = xmlGetProp(option, "value"); + if(!opt->value) { + free(opt->name); + free(opt); + option = option->next; + continue; + } + opt->next = NULL; + + if(last_option) + last_option->next = opt; + else + mount->auth_options = opt; + last_option = opt; + } + option = option->next; + } + } + else if (strcmp(node->name, "on-connect") == 0) { + mount->on_connect = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "on-disconnect") == 0) { + mount->on_disconnect = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "queue-size") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + mount->queue_size_limit = atoi (tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "burst-size") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if (tmp) + { + mount->burst_size = atoi (tmp); + xmlFree(tmp); + } + } + else if (strcmp(node->name, "source-timeout") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if (tmp) + { + mount->source_timeout = atoi (tmp); + xmlFree(tmp); + } + } + } while ((node = node->next)); +} + +static void _parse_relay(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + char *tmp; + relay_server *relay = calloc(1, sizeof(relay_server)); + relay_server *current = configuration->relay; + relay_server *last=NULL; + + while(current) { + last = current; + current = current->next; + } + + if(last) + last->next = relay; + else + configuration->relay = relay; + + relay->next = NULL; + relay->mp3metadata = 1; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "server") == 0) { + relay->server = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "port") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + relay->port = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "mount") == 0) { + relay->mount = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "local-mount") == 0) { + relay->localmount = (char *)xmlNodeListGetString( + doc, node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "relay-shoutcast-metadata") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + relay->mp3metadata = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "username") == 0) { + relay->username = (char *)xmlNodeListGetString(doc, + node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "password") == 0) { + relay->password = (char *)xmlNodeListGetString(doc, + node->xmlChildrenNode, 1); + } + else if (strcmp(node->name, "on-demand") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + relay->on_demand = atoi(tmp); + if (tmp) xmlFree(tmp); + } + } while ((node = node->next)); + if (relay->localmount == NULL) + relay->localmount = xmlStrdup (relay->mount); +} + +static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + listener_t *listener = NULL; + int i; + char *tmp; + + for(i=0; i < MAX_LISTEN_SOCKETS; i++) { + if(configuration->listeners[i].port <= 0) { + listener = &(configuration->listeners[i]); + break; + } + } + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "port") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if(configuration->port == 0) + configuration->port = atoi(tmp); + listener->port = atoi(tmp); + if(tmp) xmlFree(tmp); + } + else if (strcmp(node->name, "bind-address") == 0) { + listener->bind_address = (char *)xmlNodeListGetString(doc, + node->xmlChildrenNode, 1); + } + } while ((node = node->next)); +} + +static void _parse_authentication(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "source-password") == 0) { + char *mount, *pass; + if ((mount = (char *)xmlGetProp(node, "mount")) != NULL) { + pass = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + /* FIXME: This is a placeholder for per-mount passwords */ + } + else { + if (configuration->source_password && + configuration->source_password != + CONFIG_DEFAULT_SOURCE_PASSWORD) + xmlFree(configuration->source_password); + configuration->source_password = + (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } + } else if (strcmp(node->name, "admin-password") == 0) { + if(configuration->admin_password) + xmlFree(configuration->admin_password); + configuration->admin_password = + (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "admin-user") == 0) { + if(configuration->admin_username) + xmlFree(configuration->admin_username); + configuration->admin_username = + (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "relay-password") == 0) { + if(configuration->relay_password) + xmlFree(configuration->relay_password); + configuration->relay_password = + (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "relay-user") == 0) { + if(configuration->relay_username) + xmlFree(configuration->relay_username); + configuration->relay_username = + (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } + } while ((node = node->next)); +} + +static void _parse_directory(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + char *tmp; + + if (configuration->num_yp_directories >= MAX_YP_DIRECTORIES) { + ERROR0("Maximum number of yp directories exceeded!"); + return; + } + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "yp-url") == 0) { + if (configuration->yp_url[configuration->num_yp_directories]) + xmlFree(configuration->yp_url[configuration->num_yp_directories]); + configuration->yp_url[configuration->num_yp_directories] = + (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "yp-url-timeout") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->yp_url_timeout[configuration->num_yp_directories] = + atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "server") == 0) { + _add_server(doc, node->xmlChildrenNode, configuration); + } else if (strcmp(node->name, "touch-interval") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->yp_touch_interval[configuration->num_yp_directories] + = atoi(tmp); + if (tmp) xmlFree(tmp); + } + } while ((node = node->next)); + configuration->num_yp_directories++; +} + +static void _parse_paths(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + char *temp; + aliases *alias, *current, *last; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "basedir") == 0) { + if (configuration->base_dir && configuration->base_dir != CONFIG_DEFAULT_BASE_DIR) xmlFree(configuration->base_dir); + configuration->base_dir = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "logdir") == 0) { + if (configuration->log_dir && configuration->log_dir != CONFIG_DEFAULT_LOG_DIR) xmlFree(configuration->log_dir); + configuration->log_dir = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "pidfile") == 0) { + if (configuration->pidfile) xmlFree(configuration->pidfile); + configuration->pidfile = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "webroot") == 0) { + if (configuration->webroot_dir && configuration->webroot_dir != CONFIG_DEFAULT_WEBROOT_DIR) xmlFree(configuration->webroot_dir); + configuration->webroot_dir = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if(configuration->webroot_dir[strlen(configuration->webroot_dir)-1] == '/') + configuration->webroot_dir[strlen(configuration->webroot_dir)-1] = 0; + } else if (strcmp(node->name, "adminroot") == 0) { + if (configuration->adminroot_dir && configuration->adminroot_dir != CONFIG_DEFAULT_ADMINROOT_DIR) + xmlFree(configuration->adminroot_dir); + configuration->adminroot_dir = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if(configuration->adminroot_dir[strlen(configuration->adminroot_dir)-1] == '/') + configuration->adminroot_dir[strlen(configuration->adminroot_dir)-1] = 0; + } else if (strcmp(node->name, "alias") == 0) { + alias = malloc(sizeof(aliases)); + alias->next = NULL; + alias->source = xmlGetProp(node, "source"); + if(alias->source == NULL) { + free(alias); + continue; + } + alias->destination = xmlGetProp(node, "dest"); + if(alias->destination == NULL) { + xmlFree(alias->source); + free(alias); + continue; + } + temp = NULL; + temp = xmlGetProp(node, "port"); + if(temp != NULL) { + alias->port = atoi(temp); + xmlFree(temp); + } + else + alias->port = -1; + alias->bind_address = xmlGetProp(node, "bind-address"); + current = configuration->aliases; + last = NULL; + while(current) { + last = current; + current = current->next; + } + if(last) + last->next = alias; + else + configuration->aliases = alias; + } + } while ((node = node->next)); +} + +static void _parse_logging(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "accesslog") == 0) { + if (configuration->access_log && configuration->access_log != CONFIG_DEFAULT_ACCESS_LOG) xmlFree(configuration->access_log); + configuration->access_log = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "errorlog") == 0) { + if (configuration->error_log && configuration->error_log != CONFIG_DEFAULT_ERROR_LOG) xmlFree(configuration->error_log); + configuration->error_log = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "loglevel") == 0) { + char *tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->loglevel = atoi(tmp); + if (tmp) xmlFree(tmp); + } + } while ((node = node->next)); +} + +static void _parse_security(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + char *tmp; + xmlNodePtr oldnode; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "chroot") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->chroot = atoi(tmp); + if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "changeowner") == 0) { + configuration->chuid = 1; + oldnode = node; + node = node->xmlChildrenNode; + do { + if(node == NULL) break; + if(xmlIsBlankNode(node)) continue; + if(strcmp(node->name, "user") == 0) { + if(configuration->user) xmlFree(configuration->user); + configuration->user = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if(strcmp(node->name, "group") == 0) { + if(configuration->group) xmlFree(configuration->group); + configuration->group = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } + } while((node = node->next)); + node = oldnode; + } + } while ((node = node->next)); +} + +static void _add_server(xmlDocPtr doc, xmlNodePtr node, + ice_config_t *configuration) +{ + ice_config_dir_t *dirnode, *server; + int addnode; + char *tmp; + + server = (ice_config_dir_t *)malloc(sizeof(ice_config_dir_t)); + server->touch_interval = configuration->touch_interval; + server->host = NULL; + addnode = 0; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "host") == 0) { + server->host = (char *)xmlNodeListGetString(doc, + node->xmlChildrenNode, 1); + addnode = 1; + } else if (strcmp(node->name, "touch-interval") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + server->touch_interval = atoi(tmp); + if (tmp) xmlFree(tmp); + } + server->next = NULL; + } while ((node = node->next)); + + if (addnode) { + dirnode = configuration->dir_list; + if (dirnode == NULL) { + configuration->dir_list = server; + } else { + while (dirnode->next) dirnode = dirnode->next; + + dirnode->next = server; + } + + server = NULL; + addnode = 0; + } + +} + + diff --git a/src/cfgfile.h b/src/cfgfile.h new file mode 100644 index 00000000..ec3931c0 --- /dev/null +++ b/src/cfgfile.h @@ -0,0 +1,176 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __CFGFILE_H__ +#define __CFGFILE_H__ + +#define CONFIG_EINSANE -1 +#define CONFIG_ENOROOT -2 +#define CONFIG_EBADROOT -3 +#define CONFIG_EPARSE -4 + +#define MAX_YP_DIRECTORIES 25 + + +#include "thread/thread.h" +#include "avl/avl.h" +#include "global.h" + +typedef struct ice_config_dir_tag +{ + char *host; + int touch_interval; + struct ice_config_dir_tag *next; +} ice_config_dir_t; + +typedef struct _config_options { + char *name; + char *value; + struct _config_options *next; +} config_options_t; + +typedef struct _mount_proxy { + char *mountname; /* The mountpoint this proxy is used for */ + + char *username; /* Username and password for this mountpoint. If unset, */ + char *password; /* falls back to global source password */ + + char *dumpfile; /* Filename to dump this stream to (will be appended). NULL + to not dump. */ + int fallback_when_full; /* switch new listener to fallback source + when max listeners reached */ + int max_listeners; /* Max listeners for this mountpoint only. -1 to not + limit here (i.e. only use the global limit) */ + char *fallback_mount; /* Fallback mountname */ + + int fallback_override; /* When this source arrives, do we steal back + clients from the fallback? */ + int no_mount; /* Do we permit direct requests of this mountpoint? (or only + indirect, through fallbacks) */ + int no_yp; /* Do we prevent YP on this mount */ + unsigned queue_size_limit; + unsigned source_timeout; /* source timeout in seconds */ + unsigned burst_size; + + char *auth_type; /* Authentication type */ + config_options_t *auth_options; /* Options for this type */ + char *on_connect; + char *on_disconnect; + + struct _mount_proxy *next; +} mount_proxy; + +typedef struct _aliases { + char *source; + char *destination; + int port; + char *bind_address; + struct _aliases *next; +}aliases; + +typedef struct { + int port; + char *bind_address; +} listener_t; + +typedef struct ice_config_tag +{ + char *config_filename; + + char *location; + char *admin; + + int client_limit; + int source_limit; + unsigned queue_size_limit; + unsigned burst_size_limit; + int threadpool_size; + int client_timeout; + int header_timeout; + int source_timeout; + int ice_login; + int fileserve; + + char *source_password; + char *admin_username; + char *admin_password; + char *relay_username; + char *relay_password; + + int touch_interval; + ice_config_dir_t *dir_list; + + char *hostname; + int port; + + listener_t listeners[MAX_LISTEN_SOCKETS]; + + char *master_server; + int master_server_port; + int master_update_interval; + char *master_username; + char *master_password; + + relay_server *relay; + + mount_proxy *mounts; + + char *base_dir; + char *log_dir; + char *pidfile; + char *webroot_dir; + char *adminroot_dir; + aliases *aliases; + + char *access_log; + char *error_log; + int loglevel; + + int chroot; + int chuid; + char *user; + char *group; + char *yp_url[MAX_YP_DIRECTORIES]; + int yp_url_timeout[MAX_YP_DIRECTORIES]; + int yp_touch_interval[MAX_YP_DIRECTORIES]; + int num_yp_directories; +} ice_config_t; + +typedef struct { + mutex_t config_lock; + mutex_t relay_lock; + mutex_t mounts_lock; +} ice_config_locks; + +void config_initialize(void); +void config_shutdown(void); + +int config_parse_file(const char *filename, ice_config_t *configuration); +int config_initial_parse_file(const char *filename); +int config_parse_cmdline(int arg, char **argv); +void config_set_config(ice_config_t *config); +void config_clear(ice_config_t *config); + +int config_rehash(void); + +ice_config_locks *config_locks(void); + +ice_config_t *config_get_config(void); +void config_release_config(void); + +/* To be used ONLY in one-time startup code */ +ice_config_t *config_get_config_unlocked(void); + +#endif /* __CFGFILE_H__ */ + + + diff --git a/src/client.c b/src/client.c new file mode 100644 index 00000000..9bc2c0dc --- /dev/null +++ b/src/client.c @@ -0,0 +1,171 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* client.c +** +** client interface implementation +** +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "thread/thread.h" +#include "avl/avl.h" +#include "httpp/httpp.h" + +#include "connection.h" +#include "refbuf.h" + +#include "client.h" +#include "logging.h" + +#undef CATMODULE +#define CATMODULE "client" + +#ifdef HAVE_AIO +#include +#endif + +client_t *client_create(connection_t *con, http_parser_t *parser) +{ + client_t *client = (client_t *)calloc(1, sizeof(client_t)); + + client->con = con; + client->parser = parser; + client->pos = 0; + + return client; +} + +void client_destroy(client_t *client) +{ + if (client == NULL) + return; + /* write log entry if ip is set (some things don't set it, like outgoing + * slave requests + */ + if(client->con->ip) + logging_access(client); +#ifdef HAVE_AIO + if (aio_cancel (client->con->sock, NULL) == AIO_NOTCANCELED) + { + const struct aiocb *list = &client->aio; + INFO0 ("having to wait for aio cancellation"); + while (aio_suspend (&list, 1, NULL) < 0) + ; + } +#endif + connection_close(client->con); + httpp_destroy(client->parser); + + if (client->free_client_data) + client->free_client_data (client); + + free(client->username); + free(client->password); + + free(client); +} + +void client_send_400(client_t *client, char *message) { + int bytes; + bytes = sock_write(client->con->sock, "HTTP/1.0 400 Bad Request\r\n" + "Content-Type: text/html\r\n\r\n" + "%s\r\n", message); + if(bytes > 0) client->con->sent_bytes = bytes; + client->respcode = 400; + client_destroy(client); +} + +void client_send_404(client_t *client, char *message) { + + int bytes; + bytes = sock_write(client->con->sock, "HTTP/1.0 404 File Not Found\r\n" + "Content-Type: text/html\r\n\r\n" + "%s\r\n", message); + if(bytes > 0) client->con->sent_bytes = bytes; + client->respcode = 404; + client_destroy(client); +} + +void client_send_504(client_t *client, char *message) { + int bytes; + client->respcode = 504; + bytes = sock_write(client->con->sock, + "HTTP/1.0 504 Server Full\r\n" + "Content-Type: text/html\r\n\r\n" + "%s\r\n", message); + if (bytes > 0) client->con->sent_bytes = bytes; + client_destroy(client); +} + +void client_send_401(client_t *client) { + int bytes = sock_write(client->con->sock, + "HTTP/1.0 401 Authentication Required\r\n" + "WWW-Authenticate: Basic realm=\"Icecast2 Server\"\r\n" + "\r\n" + "You need to authenticate\r\n"); + if(bytes > 0) client->con->sent_bytes = bytes; + client->respcode = 401; + client_destroy(client); +} + + +/* helper function for sending the data to a client */ +int client_send_bytes (client_t *client, const void *buf, unsigned len) +{ + int ret; +#ifdef HAVE_AIO + int err; + struct aiocb *aiocbp = &client->aio; + + if (client->pending_io == 0) + { + memset (aiocbp, 0 , sizeof (struct aiocb)); + aiocbp->aio_fildes = client->con->sock; + aiocbp->aio_buf = (void*)buf; /* only read from */ + aiocbp->aio_nbytes = len; + + if (aio_write (aiocbp) < 0) + return -1; + client->pending_io = 1; + } + if ((err = aio_error (aiocbp)) == EINPROGRESS) + return -1; + ret = aio_return (aiocbp); + if (ret < 0) + sock_set_error (err); /* make sure errno gets set */ + + client->pending_io = 0; + +#else + ret = sock_write_bytes (client->con->sock, buf, len); +#endif + + if (ret < 0) + { + if (! sock_recoverable (sock_error())) + { + DEBUG0 ("Client connection died"); + client->con->error = 1; + } + } + if (ret > 0) + client->con->sent_bytes += ret; + return ret; +} + diff --git a/src/client.h b/src/client.h new file mode 100644 index 00000000..ae5451f0 --- /dev/null +++ b/src/client.h @@ -0,0 +1,81 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* client.h +** +** client data structions and function definitions +** +*/ +#ifndef __CLIENT_H__ +#define __CLIENT_H__ + +#ifndef WIN32 +#include +#endif + +#include "connection.h" +#include "refbuf.h" + +typedef struct _client_tag +{ + /* the client's connection */ + connection_t *con; + /* the client's http headers */ + http_parser_t *parser; + + /* http response code for this client */ + int respcode; + + /* auth completed, 0 not yet, 1 passed, 2 failed */ + int authenticated; + + /* where in the queue the client is */ + refbuf_t *refbuf; + + /* position in first buffer */ + unsigned long pos; + + /* Client username, if authenticated */ + char *username; + + /* Client password, if authenticated */ + char *password; + +#ifdef HAVE_AIO + /* for handling async IO */ + struct aiocb aio; + int pending_io; +#endif + + /* Format-handler-specific data for this client */ + void *format_data; + + /* function to call to release format specific resources */ + void (*free_client_data)(struct _client_tag *client); + + char *predata; + unsigned predata_size; + unsigned predata_len; + unsigned predata_offset; + + struct _client_tag *next; +} client_t; + +client_t *client_create(connection_t *con, http_parser_t *parser); +void client_destroy(client_t *client); +void client_send_504(client_t *client, char *message); +void client_send_404(client_t *client, char *message); +void client_send_401(client_t *client); +void client_send_400(client_t *client, char *message); +int client_send_bytes (client_t *client, const void *buf, unsigned len); + +#endif /* __CLIENT_H__ */ diff --git a/src/compat.h b/src/compat.h new file mode 100644 index 00000000..e777db20 --- /dev/null +++ b/src/compat.h @@ -0,0 +1,31 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* compat.h + * + * This file contains most of the ugliness for header portability + * and common types across various systems like Win32, Linux and + * Solaris. + */ + +/* Make sure we define 64 bit types */ +#ifdef _WIN32 +# define int64_t __int64 +# define uint64_t unsigned __int64 +# define uint32_t unsigned int +#else +# if defined(HAVE_STDINT_H) +# include +# elif defined(HAVE_INTTYPES_H) +# include +# endif +#endif diff --git a/src/configtest.c b/src/configtest.c new file mode 100644 index 00000000..1d889277 --- /dev/null +++ b/src/configtest.c @@ -0,0 +1,75 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "cfgfile.h" + +void _dump_config(ice_config_t *config); + +int main(void) +{ + ice_config_t *config; + + config_initialize(); + + config_parse_file("icecast.xml"); + + config = config_get_config_unlocked(); + + _dump_config(config); + + config_shutdown(); + + return 0; +} + +void _dump_config(ice_config_t *config) +{ + ice_config_dir_t *node; + + printf("-----\n"); + printf("location = %s\n", config->location); + printf("admin = %s\n", config->admin); + printf("client_limit = %d\n", config->client_limit); + printf("source_limit = %d\n", config->source_limit); + printf("threadpool_size = %d\n", config->threadpool_size); + printf("client_timeout = %d\n", config->client_timeout); + printf("source_password = %s\n", config->source_password); + printf("touch_interval = %d\n", config->touch_interval); + + node = config->dir_list; + while (node) { + printf("directory.touch_interval = %d\n", node->touch_interval); + printf("directory.host = %s\n", node->host); + + node = node->next; + } + + printf("hostname = %s\n", config->hostname); + printf("port = %d\n", config->port); + printf("bind_address = %s\n", config->bind_address); + printf("base_dir = %s\n", config->base_dir); + printf("log_dir = %s\n", config->log_dir); + printf("access_log = %s\n", config->access_log); + printf("error_log = %s\n", config->error_log); + printf("loglevel = %d\n", config->loglevel); + printf("-----\n"); +} + + + + + diff --git a/src/connection.c b/src/connection.c new file mode 100644 index 00000000..7a8a2531 --- /dev/null +++ b/src/connection.c @@ -0,0 +1,1020 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_POLL +#include +#endif + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#define snprintf _snprintf +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif + +#include "os.h" + +#include "thread/thread.h" +#include "avl/avl.h" +#include "net/sock.h" +#include "httpp/httpp.h" + +#include "cfgfile.h" +#include "global.h" +#include "util.h" +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" +#include "logging.h" +#include "xslt.h" +#include "fserve.h" +#include "sighandler.h" + +#include "yp.h" +#include "source.h" +#include "format.h" +#include "format_mp3.h" +#include "event.h" +#include "admin.h" + +#define CATMODULE "connection" + +typedef struct con_queue_tag { + connection_t *con; + struct con_queue_tag *next; +} con_queue_t; + +typedef struct _thread_queue_tag { + thread_type *thread_id; + struct _thread_queue_tag *next; +} thread_queue_t; + +static mutex_t _connection_mutex; +static unsigned long _current_id = 0; +static int _initialized = 0; + +static con_queue_t *_queue = NULL; +static mutex_t _queue_mutex; + +static thread_queue_t *_conhands = NULL; + +rwlock_t _source_shutdown_rwlock; + +static void *_handle_connection(void *arg); + +void connection_initialize(void) +{ + if (_initialized) return; + + thread_mutex_create("connection", &_connection_mutex); + thread_mutex_create("connection q", &_queue_mutex); + thread_mutex_create("move_clients", &move_clients_mutex); + thread_rwlock_create(&_source_shutdown_rwlock); + thread_cond_create(&global.shutdown_cond); + + _initialized = 1; +} + +void connection_shutdown(void) +{ + if (!_initialized) return; + + thread_cond_destroy(&global.shutdown_cond); + thread_rwlock_destroy(&_source_shutdown_rwlock); + thread_mutex_destroy(&_queue_mutex); + thread_mutex_destroy(&_connection_mutex); + thread_mutex_destroy(&move_clients_mutex); + + _initialized = 0; +} + +static unsigned long _next_connection_id(void) +{ + unsigned long id; + + thread_mutex_lock(&_connection_mutex); + id = _current_id++; + thread_mutex_unlock(&_connection_mutex); + + return id; +} + +connection_t *create_connection(sock_t sock, sock_t serversock, char *ip) { + connection_t *con; + con = (connection_t *)malloc(sizeof(connection_t)); + memset(con, 0, sizeof(connection_t)); + con->sock = sock; + con->serversock = serversock; + con->con_time = time(NULL); + con->id = _next_connection_id(); + con->ip = ip; + + con->event_number = EVENT_NO_EVENT; + con->event = NULL; + + return con; +} + +static int wait_for_serversock(int timeout) +{ +#ifdef HAVE_POLL + struct pollfd ufds[MAX_LISTEN_SOCKETS]; + int i, ret; + + for(i=0; i < global.server_sockets; i++) { + ufds[i].fd = global.serversock[i]; + ufds[i].events = POLLIN; + ufds[i].revents = 0; + } + + ret = poll(ufds, global.server_sockets, timeout); + if(ret < 0) { + return -2; + } + else if(ret == 0) { + return -1; + } + else { + int dst; + for(i=0; i < global.server_sockets; i++) { + if(ufds[i].revents & POLLIN) + return ufds[i].fd; + if(ufds[i].revents & (POLLHUP|POLLERR|POLLNVAL)) + { + if (ufds[i].revents & (POLLHUP|POLLERR)) + { + close (global.serversock[i]); + WARN0("Had to close a listening socket"); + } + global.serversock[i] = -1; + } + } + /* remove any closed sockets */ + for(i=0, dst=0; i < global.server_sockets; i++) + { + if (global.serversock[i] == -1) + continue; + if (i!=dst) + global.serversock[dst] = global.serversock[i]; + dst++; + } + global.server_sockets = dst; + return -1; + } +#else + fd_set rfds; + struct timeval tv, *p=NULL; + int i, ret; + int max = -1; + + FD_ZERO(&rfds); + + for(i=0; i < global.server_sockets; i++) { + FD_SET(global.serversock[i], &rfds); + if(global.serversock[i] > max) + max = global.serversock[i]; + } + + if(timeout >= 0) { + tv.tv_sec = timeout/1000; + tv.tv_usec = (timeout % 1000) * 1000; + p = &tv; + } + + ret = select(max+1, &rfds, NULL, NULL, p); + if(ret < 0) { + return -2; + } + else if(ret == 0) { + return -1; + } + else { + for(i=0; i < global.server_sockets; i++) { + if(FD_ISSET(global.serversock[i], &rfds)) + return global.serversock[i]; + } + return -1; /* Should be impossible, stop compiler warnings */ + } +#endif +} + +static connection_t *_accept_connection(void) +{ + int sock; + connection_t *con; + char *ip; + int serversock; + + serversock = wait_for_serversock(100); + if(serversock < 0) + return NULL; + + /* malloc enough room for a full IP address (including ipv6) */ + ip = (char *)malloc(MAX_ADDR_LEN); + + sock = sock_accept(serversock, ip, MAX_ADDR_LEN); + if (sock >= 0) { + con = create_connection(sock, serversock, ip); + + return con; + } + + if (!sock_recoverable(sock_error())) + { + WARN2("accept() failed with error %d: %s", sock_error(), strerror(sock_error())); + abort(); + /* global.running = ICE_HALTING; */ + } + + free(ip); + + return NULL; +} + +static void _add_connection(connection_t *con) +{ + con_queue_t *node; + + node = (con_queue_t *)malloc(sizeof(con_queue_t)); + + thread_mutex_lock(&_queue_mutex); + node->con = con; + node->next = _queue; + _queue = node; + thread_mutex_unlock(&_queue_mutex); + +} + +static void _push_thread(thread_queue_t **queue, thread_type *thread_id) +{ + /* create item */ + thread_queue_t *item = (thread_queue_t *)malloc(sizeof(thread_queue_t)); + item->thread_id = thread_id; + item->next = NULL; + + + thread_mutex_lock(&_queue_mutex); + if (*queue == NULL) { + *queue = item; + } else { + item->next = *queue; + *queue = item; + } + thread_mutex_unlock(&_queue_mutex); +} + +static thread_type *_pop_thread(thread_queue_t **queue) +{ + thread_type *id; + thread_queue_t *item; + + thread_mutex_lock(&_queue_mutex); + + item = *queue; + if (item == NULL) { + thread_mutex_unlock(&_queue_mutex); + return NULL; + } + + *queue = item->next; + item->next = NULL; + id = item->thread_id; + free(item); + + thread_mutex_unlock(&_queue_mutex); + + return id; +} + +static void _build_pool(void) +{ + ice_config_t *config; + int i; + thread_type *tid; + char buff[64]; + int threadpool_size; + + config = config_get_config(); + threadpool_size = config->threadpool_size; + config_release_config(); + + for (i = 0; i < threadpool_size; i++) { + snprintf(buff, 64, "Connection Thread #%d", i); + tid = thread_create(buff, _handle_connection, NULL, THREAD_ATTACHED); + _push_thread(&_conhands, tid); + } +} + +static void _destroy_pool(void) +{ + thread_type *id; + int i; + + i = 0; + + id = _pop_thread(&_conhands); + while (id != NULL) { + thread_join(id); + id = _pop_thread(&_conhands); + } + INFO0("All connection threads down"); +} + +void connection_accept_loop(void) +{ + connection_t *con; + + _build_pool(); + + while (global.running == ICE_RUNNING) + { + if (global . schedule_config_reread) + { + /* reread config file */ + INFO0("Scheduling config reread ..."); + + connection_inject_event(EVENT_CONFIG_READ, NULL); + global . schedule_config_reread = 0; + } + + con = _accept_connection(); + + if (con) { + _add_connection(con); + } + } + + /* Give all the other threads notification to shut down */ + thread_cond_broadcast(&global.shutdown_cond); + + _destroy_pool(); + + /* wait for all the sources to shutdown */ + thread_rwlock_wlock(&_source_shutdown_rwlock); + thread_rwlock_unlock(&_source_shutdown_rwlock); +} + +static connection_t *_get_connection(void) +{ + con_queue_t *node = NULL; + con_queue_t *oldnode = NULL; + connection_t *con = NULL; + + thread_mutex_lock(&_queue_mutex); + if (_queue) { + node = _queue; + while (node->next) { + oldnode = node; + node = node->next; + } + + /* node is now the last node + ** and oldnode is the previous one, or NULL + */ + if (oldnode) oldnode->next = NULL; + else (_queue) = NULL; + } + thread_mutex_unlock(&_queue_mutex); + + if (node) { + con = node->con; + free(node); + } + + return con; +} + +void connection_inject_event(int eventnum, void *event_data) { + connection_t *con = calloc(1, sizeof(connection_t)); + + con->event_number = eventnum; + con->event = event_data; + + _add_connection(con); +} + + +/* Called when activating a source. Verifies that the source count is not + * exceeded and applies any initial parameters. + */ +int connection_complete_source (source_t *source) +{ + ice_config_t *config = config_get_config(); + + global_lock (); + DEBUG1 ("sources count is %d", global.sources); + + if (global.sources < config->source_limit) + { + char *contenttype; + mount_proxy *mountproxy = config->mounts; + format_type_t format_type; + + /* setup format handler */ + contenttype = httpp_getvar (source->parser, "content-type"); + if (contenttype != NULL) + { + format_type = format_get_type (contenttype); + + if (format_type == FORMAT_ERROR) + { + global_unlock(); + config_release_config(); + if (source->client) + client_send_404 (source->client, "Content-type not supported"); + WARN1("Content-type \"%s\" not supported, dropping source", contenttype); + return -1; + } + } + else + { + WARN0("No content-type header, falling back to backwards compatibility mode " + "for icecast 1.x relays. Assuming content is mp3."); + format_type = FORMAT_TYPE_MP3; + } + + if (format_get_plugin (format_type, source) < 0) + { + global_unlock(); + config_release_config(); + if (source->client) + client_send_404 (source->client, "internal format allocation problem"); + WARN1 ("plugin format failed for \"%s\"", source->mount); + return -1; + } + + global.sources++; + global_unlock(); + + /* set global settings first */ + source->queue_size_limit = config->queue_size_limit; + source->timeout = config->source_timeout; + // source->burst_size = config->burst_size_limit; + source->burst_size_limit = config->burst_size_limit; + + /* for relays, we don't yet have a client, however we do require one + * to retrieve the stream from. This is created here, quite late, + * because we can't use this client to return an error code/message, + * so we only do this once we know we're going to accept the source. + */ + if (source->client == NULL) + source->client = client_create (source->con, source->parser); + + while (mountproxy) + { + if (strcmp (mountproxy->mountname, source->mount) == 0) + { + source_apply_mount (source, mountproxy); + break; + } + mountproxy = mountproxy->next; + } + config_release_config(); + + source->shutdown_rwlock = &_source_shutdown_rwlock; + DEBUG0 ("source is ready to start"); + + return 0; + } + WARN1("Request to add source when maximum source limit" + "reached %d", global.sources); + + global_unlock(); + config_release_config(); + + if (source->client) + client_send_404 (source->client, "too many sources connected"); + + return -1; +} + + +static int _check_pass_http(http_parser_t *parser, + char *correctuser, char *correctpass) +{ + /* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */ + char *header = httpp_getvar(parser, "authorization"); + char *userpass, *tmp; + char *username, *password; + + if(header == NULL) + return 0; + + if(strncmp(header, "Basic ", 6)) + return 0; + + userpass = util_base64_decode(header+6); + if(userpass == NULL) { + WARN1("Base64 decode of Authorization header \"%s\" failed", + header+6); + return 0; + } + + tmp = strchr(userpass, ':'); + if(!tmp) { + free(userpass); + return 0; + } + *tmp = 0; + username = userpass; + password = tmp+1; + + if(strcmp(username, correctuser) || strcmp(password, correctpass)) { + free(userpass); + return 0; + } + free(userpass); + + return 1; +} + +static int _check_pass_icy(http_parser_t *parser, char *correctpass) +{ + char *password; + + password = httpp_getvar(parser, HTTPP_VAR_ICYPASSWORD); + if(!password) + return 0; + + if (strcmp(password, correctpass)) + return 0; + else + return 1; +} + +static int _check_pass_ice(http_parser_t *parser, char *correctpass) +{ + char *password; + + password = httpp_getvar(parser, "ice-password"); + if(!password) + password = ""; + + if (strcmp(password, correctpass)) + return 0; + else + return 1; +} + +int connection_check_admin_pass(http_parser_t *parser) +{ + int ret; + ice_config_t *config = config_get_config(); + char *pass = config->admin_password; + char *user = config->admin_username; + + if(!pass || !user) { + config_release_config(); + return 0; + } + + ret = _check_pass_http(parser, user, pass); + config_release_config(); + return ret; +} + +int connection_check_relay_pass(http_parser_t *parser) +{ + int ret; + ice_config_t *config = config_get_config(); + char *pass = config->relay_password; + char *user = config->relay_username; + + if(!pass || !user) { + config_release_config(); + return 0; + } + + ret = _check_pass_http(parser, user, pass); + config_release_config(); + return ret; +} + + +int connection_check_source_pass(http_parser_t *parser, char *mount) +{ + ice_config_t *config = config_get_config(); + char *pass = config->source_password; + char *user = "source"; + int ret; + int ice_login = config->ice_login; + char *protocol; + + mount_proxy *mountinfo = config->mounts; + thread_mutex_lock(&(config_locks()->mounts_lock)); + + while(mountinfo) { + if(!strcmp(mountinfo->mountname, mount)) { + if(mountinfo->password) + pass = mountinfo->password; + if(mountinfo->username) + user = mountinfo->username; + break; + } + mountinfo = mountinfo->next; + } + + thread_mutex_unlock(&(config_locks()->mounts_lock)); + + if(!pass) { + WARN0("No source password set, rejecting source"); + config_release_config(); + return 0; + } + + protocol = httpp_getvar(parser, HTTPP_VAR_PROTOCOL); + if(protocol != NULL && !strcmp(protocol, "ICY")) { + ret = _check_pass_icy(parser, pass); + } + else { + ret = _check_pass_http(parser, user, pass); + if(!ret && ice_login) + { + ret = _check_pass_ice(parser, pass); + if(ret) + WARN0("Source is using deprecated icecast login"); + } + } + config_release_config(); + return ret; +} + + +static void _handle_source_request(connection_t *con, + http_parser_t *parser, char *uri) +{ + client_t *client; + source_t *source; + + client = client_create(con, parser); + + INFO1("Source logging in at mountpoint \"%s\"", uri); + + if (!connection_check_source_pass(parser, uri)) + { + /* We commonly get this if the source client is using the wrong + * protocol: attempt to diagnose this and return an error + */ + /* TODO: Do what the above comment says */ + INFO1("Source (%s) attempted to login with invalid or missing password", uri); + client_send_401(client); + return; + } + source = source_reserve (uri); + if (source) + { + source->client = client; + source->parser = parser; + source->con = con; + if (connection_complete_source (source) < 0) + { + source->client = NULL; + source_free_source (source); + } + else + thread_create ("Source Thread", source_client_thread, + source, THREAD_DETACHED); + } + else + { + client_send_404 (client, "Mountpoint in use"); + } +} + + +static void _handle_stats_request(connection_t *con, + http_parser_t *parser, char *uri) +{ + stats_connection_t *stats; + + stats_event_inc(NULL, "stats_connections"); + + if (!connection_check_admin_pass(parser)) + { + ERROR0("Bad password for stats connection"); + connection_close(con); + httpp_destroy(parser); + return; + } + + stats_event_inc(NULL, "stats"); + + /* create stats connection and create stats handler thread */ + stats = (stats_connection_t *)malloc(sizeof(stats_connection_t)); + stats->parser = parser; + stats->con = con; + + thread_create("Stats Connection", stats_connection, (void *)stats, THREAD_DETACHED); +} + +static void _handle_get_request(connection_t *con, + http_parser_t *parser, char *passed_uri) +{ + char *fullpath; + client_t *client; + int bytes; + struct stat statbuf; + source_t *source; + int fileserve; + char *host; + int port; + int i; + char *serverhost = NULL; + int serverport = 0; + aliases *alias; + ice_config_t *config; + int client_limit; + char *uri = passed_uri; + + DEBUG1("start with %s", passed_uri); + config = config_get_config(); + fileserve = config->fileserve; + host = config->hostname; + port = config->port; + for(i = 0; i < MAX_LISTEN_SOCKETS; i++) { + if(global.serversock[i] == con->serversock) { + serverhost = config->listeners[i].bind_address; + serverport = config->listeners[i].port; + break; + } + } + alias = config->aliases; + client_limit = config->client_limit; + + + stats_event_inc(NULL, "client_connections"); + + /* there are several types of HTTP GET clients + ** media clients, which are looking for a source (eg, URI = /stream.ogg) + ** stats clients, which are looking for /admin/stats.xml + ** and directory server authorizers, which are looking for /GUID-xxxxxxxx + ** (where xxxxxx is the GUID in question) - this isn't implemented yet. + ** we need to handle the latter two before the former, as the latter two + ** aren't subject to the limits. + */ + /* TODO: add GUID-xxxxxx */ + + /* Handle aliases */ + while(alias) { + if(strcmp(uri, alias->source) == 0 && (alias->port == -1 || alias->port == serverport) && (alias->bind_address == NULL || (serverhost != NULL && strcmp(alias->bind_address, serverhost) == 0))) { + uri = strdup (alias->destination); + DEBUG2 ("alias has made %s into %s", passed_uri, uri); + break; + } + alias = alias->next; + } + config_release_config(); + + /* make a client */ + client = client_create(con, parser); + + /* Dispatch all admin requests */ + if (strncmp(uri, "/admin/", 7) == 0) { + admin_handle_request(client, uri); + if (uri != passed_uri) free (uri); + return; + } + + /* Here we are parsing the URI request to see + ** if the extension is .xsl, if so, then process + ** this request as an XSLT request + */ + fullpath = util_get_path_from_normalised_uri(uri); + if (util_check_valid_extension(fullpath) == XSLT_CONTENT) { + /* If the file exists, then transform it, otherwise, write a 404 */ + if (stat(fullpath, &statbuf) == 0) { + DEBUG0("Stats request, sending XSL transformed stats"); + client->respcode = 200; + bytes = sock_write(client->con->sock, + "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"); + if(bytes > 0) client->con->sent_bytes = bytes; + stats_transform_xslt(client, fullpath); + client_destroy(client); + } + else { + client_send_404(client, "The file you requested could not be found"); + } + free(fullpath); + if (uri != passed_uri) free (uri); + return; + } + else if(fileserve && stat(fullpath, &statbuf) == 0 && +#ifdef _WIN32 + ((statbuf.st_mode) & _S_IFREG)) +#else + S_ISREG(statbuf.st_mode)) +#endif + { + fserve_client_create(client, fullpath); + free(fullpath); + if (uri != passed_uri) free (uri); + return; + } + free(fullpath); + + if(strcmp(util_get_extension(uri), "m3u") == 0) { + char *sourceuri = strdup(uri); + char *dot = strrchr(sourceuri, '.'); + *dot = 0; + avl_tree_rlock(global.source_tree); + source = source_find_mount(sourceuri); + if (source) { + client->respcode = 200; + bytes = sock_write(client->con->sock, + "HTTP/1.0 200 OK\r\n" + "Content-Type: audio/x-mpegurl\r\n\r\n" + "http://%s:%d%s\r\n", + host, + port, + sourceuri + ); + if(bytes > 0) client->con->sent_bytes = bytes; + client_destroy(client); + } + else if(fileserve) { + fullpath = util_get_path_from_normalised_uri(sourceuri); + if(stat(fullpath, &statbuf) == 0) { + fserve_client_create(client, fullpath); + free(fullpath); + } + else { + free(fullpath); + fullpath = util_get_path_from_normalised_uri(uri); + if(stat(fullpath, &statbuf) == 0) { + fserve_client_create(client, fullpath); + free(fullpath); + } + else { + free(fullpath); + client_send_404(client, + "The file you requested could not be found"); + } + } + } + else { + client_send_404(client, "The file you requested could not be found"); + } + avl_tree_unlock(global.source_tree); + free(sourceuri); + if (uri != passed_uri) free (uri); + return; + } + + global_lock(); + if (global.clients >= client_limit) + { + global_unlock(); + client_send_404(client, + "The server is already full. Try again later."); + if (uri != passed_uri) free (uri); + return; + } + global.clients++; + global_unlock(); + + add_client (uri, client); + + if (uri != passed_uri) free (uri); +} + +static void *_handle_connection(void *arg) +{ + char header[4096]; + connection_t *con; + http_parser_t *parser; + char *rawuri, *uri; + client_t *client; + + while (global.running == ICE_RUNNING) { + memset(header, 0, 4096); + + thread_sleep (100000); + if (global.running != ICE_RUNNING) break; + + /* grab a connection and set the socket to blocking */ + while ((con = _get_connection())) { + + /* Handle meta-connections */ + if(con->event_number > 0) { + switch(con->event_number) { + case EVENT_CONFIG_READ: + event_config_read(con->event); + break; + default: + ERROR1("Unknown event number: %d", con->event_number); + break; + } + free(con); + continue; + } + + stats_event_inc(NULL, "connections"); + + sock_set_blocking(con->sock, SOCK_BLOCK); + + /* fill header with the http header */ + if (util_read_header(con->sock, header, 4096) == 0) { + /* either we didn't get a complete header, or we timed out */ + connection_close(con); + continue; + } + + parser = httpp_create_parser(); + httpp_initialize(parser, NULL); + if (httpp_parse(parser, header, strlen(header))) { + /* handle the connection or something */ + + if (strcmp("ICE", httpp_getvar(parser, HTTPP_VAR_PROTOCOL)) && + strcmp("HTTP", httpp_getvar(parser, HTTPP_VAR_PROTOCOL))) { + ERROR0("Bad HTTP protocol detected"); + connection_close(con); + httpp_destroy(parser); + continue; + } + + rawuri = httpp_getvar(parser, HTTPP_VAR_URI); + uri = util_normalise_uri(rawuri); + + if(!uri) { + client = client_create(con, parser); + client_send_404(client, "The path you requested was invalid"); + continue; + } + + if (parser->req_type == httpp_req_source) { + _handle_source_request(con, parser, uri); + } + else if (parser->req_type == httpp_req_stats) { + _handle_stats_request(con, parser, uri); + } + else if (parser->req_type == httpp_req_get) { + _handle_get_request(con, parser, uri); + } + else { + ERROR0("Wrong request type from client"); + connection_close(con); + httpp_destroy(parser); + } + + free(uri); + } + else if(httpp_parse_icy(parser, header, strlen(header))) { + /* TODO: Map incoming icy connections to /icy_0, etc. */ + char mount[20]; + unsigned i = 0; + + strcpy(mount, "/"); + + avl_tree_rlock(global.source_tree); + while (source_find_mount (mount) != NULL) { + snprintf (mount, sizeof (mount), "/icy_%u", i++); + } + avl_tree_unlock(global.source_tree); + + _handle_source_request(con, parser, mount); + } + else { + ERROR0("HTTP request parsing failed"); + connection_close(con); + httpp_destroy(parser); + continue; + } + } + } + DEBUG0 ("Connection thread done"); + + return NULL; +} + +void connection_close(connection_t *con) +{ + sock_close(con->sock); + if (con->ip) free(con->ip); + if (con->host) free(con->host); + free(con); +} diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 00000000..3b9356c1 --- /dev/null +++ b/src/connection.h @@ -0,0 +1,61 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __CONNECTION_H__ +#define __CONNECTION_H__ + +#include +#include +#include "compat.h" +#include "httpp/httpp.h" +#include "thread/thread.h" +#include "net/sock.h" + +struct _client_tag; +struct source_tag; + +typedef struct connection_tag +{ + unsigned long id; + + time_t con_time; + uint64_t sent_bytes; + + + int sock; + int serversock; + int error; + + char *ip; + char *host; + + /* For 'fake' connections */ + int event_number; + void *event; +} connection_t; + +void connection_initialize(void); +void connection_shutdown(void); +void connection_accept_loop(void); +void connection_close(connection_t *con); +connection_t *create_connection(sock_t sock, sock_t serversock, char *ip); +int connection_complete_source (struct source_tag *source); + +void connection_inject_event(int eventnum, void *event_data); + +int connection_check_source_pass(http_parser_t *parser, char *mount); +int connection_check_relay_pass(http_parser_t *parser); +int connection_check_admin_pass(http_parser_t *parser); + +extern rwlock_t _source_shutdown_rwlock; + +#endif /* __CONNECTION_H__ */ diff --git a/src/event.c b/src/event.c new file mode 100644 index 00000000..c676f993 --- /dev/null +++ b/src/event.c @@ -0,0 +1,67 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "event.h" +#include "cfgfile.h" +#include "yp.h" + +#include "refbuf.h" +#include "client.h" +#include "logging.h" +#include "slave.h" + +#define CATMODULE "event" + +void event_config_read(void *arg) +{ + int ret; + ice_config_t *config; + ice_config_t new_config; + /* reread config file */ + + config = config_get_config(); /* Both to get the lock, and to be able + to find out the config filename */ + ret = config_parse_file(config->config_filename, &new_config); + if(ret < 0) { + ERROR0("Error parsing config, not replacing existing config"); + switch(ret) { + case CONFIG_EINSANE: + ERROR0("Config filename null or blank"); + break; + case CONFIG_ENOROOT: + ERROR1("Root element not found in %s", config->config_filename); + break; + case CONFIG_EBADROOT: + ERROR1("Not an icecast2 config file: %s", + config->config_filename); + break; + default: + ERROR1("Parse error in reading %s", config->config_filename); + break; + } + config_release_config(); + } + else { + config_clear(config); + config_set_config(&new_config); + restart_logging (); + slave_recheck(); + yp_recheck_config (config); + + config_release_config(); + } +} + diff --git a/src/event.h b/src/event.h new file mode 100644 index 00000000..13a19d9b --- /dev/null +++ b/src/event.h @@ -0,0 +1,21 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __EVENT_H__ +#define __EVENT_H__ + +#define EVENT_NO_EVENT 0 +#define EVENT_CONFIG_READ 1 + +void event_config_read(void *nothing); + +#endif /* __EVENT_H__ */ diff --git a/src/format.c b/src/format.c new file mode 100644 index 00000000..300412d2 --- /dev/null +++ b/src/format.c @@ -0,0 +1,212 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; -*- */ +/* format.c +** +** format plugin implementation +** +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#ifdef HAVE_STRINGS_H +# include +#endif +#include + +#include "connection.h" +#include "refbuf.h" + +#include "source.h" +#include "format.h" +#include "global.h" +#include "httpp/httpp.h" + +#include "format_ogg.h" +#include "format_mp3.h" + +#include "logging.h" +#define CATMODULE "format" + +#ifdef WIN32 +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define snprintf _snprintf +#endif + +format_type_t format_get_type(char *contenttype) +{ + if(strcmp(contenttype, "application/x-ogg") == 0) + return FORMAT_TYPE_OGG; /* Backwards compatibility */ + else if(strcmp(contenttype, "application/ogg") == 0) + return FORMAT_TYPE_OGG; /* Now blessed by IANA */ + else if(strcmp(contenttype, "audio/mpeg") == 0) + return FORMAT_TYPE_MP3; + else if(strcmp(contenttype, "audio/x-mpeg") == 0) + return FORMAT_TYPE_MP3; + else + return FORMAT_ERROR; +} + +char *format_get_mimetype(format_type_t type) +{ + switch(type) { + case FORMAT_TYPE_OGG: + return "application/ogg"; + break; + case FORMAT_TYPE_MP3: + return "audio/mpeg"; + break; + default: + return NULL; + } +} + +int format_get_plugin(format_type_t type, source_t *source) +{ + int ret; + + switch (type) + { + case FORMAT_TYPE_OGG: + ret = format_ogg_get_plugin (source); + break; + case FORMAT_TYPE_MP3: + ret = format_mp3_get_plugin (source); + break; + default: + ret = -1; + break; + } + + return ret; +} + + +int format_generic_write_buf_to_client(format_plugin_t *format, + client_t *client, unsigned char *buf, int len) +{ + int ret; + + ret = client_send_bytes (client, buf, len); + if (ret < 0 && client->con->error == 0) + ret = 0; + + return ret; +} + + +void format_prepare_headers (source_t *source, client_t *client) +{ + unsigned remaining; + char *ptr; + int bytes; + int bitrate_filtered = 0; + avl_node *node; + char *agent; + + remaining = client->predata_size; + ptr = client->predata; + client->respcode = 200; + + /* ugly hack, but send ICY OK header when client is realplayer */ + agent = httpp_getvar (client->parser, "user-agent"); + if (agent && strstr (agent, "RealMedia") != NULL) + bytes = snprintf (ptr, remaining, "ICY 200 OK\r\nContent-Type: %s\r\n", + format_get_mimetype (source->format->type)); + else + bytes = snprintf (ptr, remaining, "HTTP/1.0 200 OK\r\nContent-Type: %s\r\n", + format_get_mimetype (source->format->type)); + + remaining -= bytes; + ptr += bytes; + + /* iterate through source http headers and send to client */ + avl_tree_rlock (source->parser->vars); + node = avl_get_first (source->parser->vars); + while (node) + { + int next = 1; + http_var_t *var = (http_var_t *)node->key; + bytes = 0; + if (!strcasecmp (var->name, "ice-audio-info")) + { + /* convert ice-audio-info to icy-br */ + char *brfield = NULL; + unsigned int bitrate; + + if (bitrate_filtered == 0) + brfield = strstr (var->value, "bitrate="); + if (brfield && sscanf (brfield, "bitrate=%u", &bitrate)) + { + bytes = snprintf (ptr, remaining, "icy-br:%u\r\n", bitrate); + next = 0; + bitrate_filtered = 1; + } + else + /* show ice-audio_info header as well because of relays */ + bytes = snprintf (ptr, remaining, "%s: %s\r\n", var->name, var->value); + } + else + { + if (strcasecmp (var->name, "ice-password") && + strcasecmp (var->name, "icy-metaint")) + { + if (!strncasecmp ("ice-", var->name, 4)) + { + if (!strcasecmp ("ice-public", var->name)) + bytes = snprintf (ptr, remaining, "icy-pub:%s\r\n", var->value); + else + if (!strcasecmp ("ice-bitrate", var->name)) + bytes = snprintf (ptr, remaining, "icy-br:%s\r\n", var->value); + else + bytes = snprintf (ptr, remaining, "icy%s:%s\r\n", + var->name + 3, var->value); + } + else + if (!strncasecmp ("icy-", var->name, 4)) + { + bytes = snprintf (ptr, remaining, "icy%s:%s\r\n", + var->name + 3, var->value); + } + } + } + + remaining -= bytes; + ptr += bytes; + if (next) + node = avl_get_next (node); + } + avl_tree_unlock (source->parser->vars); + + bytes = snprintf (ptr, remaining, "Server: %s\r\n", ICECAST_VERSION_STRING); + remaining -= bytes; + ptr += bytes; + + bytes = snprintf (ptr, remaining, "\r\n"); + remaining -= bytes; + ptr += bytes; + + client->predata_len = client->predata_size - remaining; +} + + +void format_initialise () +{ + format_ogg_initialise (); +} + diff --git a/src/format.h b/src/format.h new file mode 100644 index 00000000..d0f2c215 --- /dev/null +++ b/src/format.h @@ -0,0 +1,72 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* format.h +** +** format plugin header +** +*/ +#ifndef __FORMAT_H__ +#define __FORMAT_H__ + +#include "client.h" +#include "refbuf.h" +#include "httpp/httpp.h" + +struct source_tag; + +typedef enum _format_type_tag +{ + FORMAT_ERROR, /* No format, source not processable */ + FORMAT_TYPE_OGG, + FORMAT_TYPE_VORBIS, + FORMAT_TYPE_MP3 +} format_type_t; + +typedef struct _format_plugin_tag +{ + format_type_t type; + + /* we need to know the mount to report statistics */ + char *mount; + + char *format_description; + + /* set this is the data format has a header that + ** we must send before regular data + */ + + refbuf_t *(*get_buffer)(struct source_tag *); + int (*write_buf_to_client)(struct _format_plugin_tag *format, client_t *client); + void (*write_buf_to_file)(struct source_tag *source, refbuf_t *refbuf); + int (*create_client_data)(struct source_tag *source, client_t *client); + void (*set_tag)(struct _format_plugin_tag *plugin, char *tag, char *value); + void (*free_plugin)(struct _format_plugin_tag *self); + void (*prerelease)(struct source_tag *source, refbuf_t *refbuf); + + /* for internal state management */ + void *_state; +} format_plugin_t; + +format_type_t format_get_type(char *contenttype); +char *format_get_mimetype(format_type_t type); +int format_get_plugin(format_type_t type, struct source_tag *source); + +int format_generic_write_buf_to_client(format_plugin_t *format, + client_t *client, unsigned char *buf, int len); +void format_send_general_headers(format_plugin_t *format, + struct source_tag *source, client_t *client); +void format_prepare_headers (struct source_tag *source, client_t *client); +void format_initialise(); + +#endif /* __FORMAT_H__ */ + diff --git a/src/format_mp3.c b/src/format_mp3.c new file mode 100644 index 00000000..f72023f1 --- /dev/null +++ b/src/format_mp3.c @@ -0,0 +1,572 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; -*- */ +/* format_mp3.c +** +** format plugin for mp3 +** +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#ifdef HAVE_STRINGS_H +# include +#endif + +#include "refbuf.h" +#include "source.h" +#include "client.h" + +#include "stats.h" +#include "format.h" +#include "httpp/httpp.h" + +#include "logging.h" + +#include "format_mp3.h" + +#ifdef WIN32 +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define snprintf _snprintf +#endif + +#define CATMODULE "format-mp3" + +/* Note that this seems to be 8192 in shoutcast - perhaps we want to be the + * same for compability with crappy clients? + */ +#define ICY_METADATA_INTERVAL 8192 + +static void format_mp3_free_plugin(format_plugin_t *plugin); +static refbuf_t *mp3_get_filter_meta (source_t *source); +static refbuf_t *mp3_get_no_meta (source_t *source); + +static int format_mp3_create_client_data (source_t *source, client_t *client); +static void free_mp3_client_data (client_t *client); +static int format_mp3_write_buf_to_client(format_plugin_t *self, client_t *client); +static void write_mp3_to_file (struct source_tag *source, refbuf_t *refbuf); +static void mp3_set_tag (format_plugin_t *plugin, char *tag, char *value); + + +typedef struct { + int use_metadata; + int metadata_offset; + unsigned since_meta_block; + int in_metadata; + refbuf_t *associated; +} mp3_client_data; + +int format_mp3_get_plugin (source_t *source) +{ + char *metadata; + format_plugin_t *plugin; + mp3_state *state = calloc(1, sizeof(mp3_state)); + refbuf_t *meta; + + plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t)); + + plugin->type = FORMAT_TYPE_MP3; + plugin->get_buffer = mp3_get_no_meta; + plugin->write_buf_to_client = format_mp3_write_buf_to_client; + plugin->write_buf_to_file = write_mp3_to_file; + plugin->create_client_data = format_mp3_create_client_data; + plugin->free_plugin = format_mp3_free_plugin; + plugin->set_tag = mp3_set_tag; + plugin->prerelease = NULL; + plugin->format_description = "MP3 audio"; + + plugin->_state = state; + + meta = refbuf_new (1); + memcpy (meta->data, "", 1); + meta->len = 1; + state->metadata = meta; + state->interval = ICY_METADATA_INTERVAL; + + metadata = httpp_getvar (source->parser, "icy-metaint"); + if (metadata) + { + state->inline_metadata_interval = atoi (metadata); + state->offset = 0; + plugin->get_buffer = mp3_get_filter_meta; + } + source->format = plugin; + + return 0; +} + + +static void mp3_set_tag (format_plugin_t *plugin, char *tag, char *value) +{ + mp3_state *source_mp3 = plugin->_state; + unsigned len; + const char meta[] = "StreamTitle='"; + int size = sizeof (meta) + 1; + + if (tag==NULL || value == NULL) + return; + + len = strlen (value)+1; + size += len; + if (strcmp (tag, "title") == 0 || strcmp (tag, "song") == 0) + { + char *p = strdup (value); + if (p) + { + free (source_mp3->url_title); + free (source_mp3->url_artist); + source_mp3->url_artist = NULL; + source_mp3->url_title = p; + source_mp3->update_metadata = 1; + } + return; + } + if (strcmp (tag, "artist") == 0) + { + char *p = strdup (value); + if (p) + { + free (source_mp3->url_artist); + source_mp3->url_artist = p; + } + } + source_mp3->update_metadata = 1; +} + + +static void filter_shoutcast_metadata (source_t *source, char *metadata, unsigned meta_len) +{ + if (metadata) + { + char *end, *p; + int len; + + do + { + metadata++; + if (strncmp (metadata, "StreamTitle='", 13)) + break; + if ((end = strstr (metadata, "\';")) == NULL) + break; + len = (end - metadata) - 13; + p = calloc (1, len+1); + if (p) + { + memcpy (p, metadata+13, len); + stats_event (source->mount, "title", p); + yp_touch (source->mount); + free (p); + } + } while (0); + } +} + + +void mp3_set_title (source_t *source) +{ + const char meta[] = "StreamTitle='"; + int size; + unsigned char len_byte; + refbuf_t *p; + unsigned len = sizeof(meta) + 6; + mp3_state *source_mp3 = source->format->_state; + + if (source_mp3->url_artist) + len += strlen (source_mp3->url_artist); + if (source_mp3->url_title) + len += strlen (source_mp3->url_title); + if (source_mp3->url_artist && source_mp3->url_title) + len += 3; +#define MAX_META_LEN 255*16 + size = sizeof (meta) + len + 2; + if (len > MAX_META_LEN-(sizeof(meta)+3)) + { + WARN1 ("Metadata too long at %d chars", len); + return; + } + len_byte = size / 16 + 1; + size = len_byte * 16 + 1; + p = refbuf_new (size); + p->len = size; + if (p) + { + mp3_state *source_mp3 = source->format->_state; + + memset (p->data, '\0', size); + if (source_mp3->url_artist && source_mp3->url_title) + snprintf (p->data, size, "%c%s%s - %s';", len_byte, meta, + source_mp3->url_artist, source_mp3->url_title); + else + snprintf (p->data, size, "%c%s%.*s';", len_byte, meta, len, source_mp3->url_title); + filter_shoutcast_metadata (source, p->data, size); + source_mp3->metadata = p; + } +} + + +static int send_mp3_metadata (client_t *client, refbuf_t *associated) +{ + int ret = 0; + unsigned char *metadata; + int meta_len; + mp3_client_data *client_mp3 = client->format_data; + + if (associated == client_mp3->associated) + { + metadata = "\0"; + meta_len = 1; + } + else + { + metadata = associated->data + client_mp3->metadata_offset; + meta_len = associated->len - client_mp3->metadata_offset; + } + ret = client_send_bytes (client, metadata, meta_len); + + if (ret == meta_len) + { + client_mp3->associated = associated; + client_mp3->metadata_offset = 0; + client_mp3->in_metadata = 0; + client_mp3->since_meta_block = 0; + return ret; + } + if (ret > 0) + client_mp3->metadata_offset += ret; + client_mp3->in_metadata = 1; + + return ret; +} + + +/* return bytes actually written, -1 for error or 0 for no more data to write */ + +static int format_mp3_write_buf_to_client (format_plugin_t *self, client_t *client) +{ + int ret, written = 0; + mp3_client_data *client_mp3 = client->format_data; + mp3_state *source_mp3 = self->_state; + refbuf_t *refbuf = client->refbuf; + char *buf; + unsigned len; + + if (refbuf == NULL) + return 0; /* no data yet */ + if (refbuf->next == NULL && client->pos == refbuf->len) + return 0; + buf = refbuf->data + client->pos; + len = refbuf->len - client->pos; + + do + { + /* send any unwritten metadata to the client */ + if (client_mp3->in_metadata) + { + refbuf_t *associated = refbuf->associated; + ret = send_mp3_metadata (client, associated); + + if (ret < (int)associated->len) + break; + written += ret; + } + /* see if we need to send the current metadata to the client */ + if (client_mp3->use_metadata) + { + unsigned remaining = source_mp3->interval - client_mp3->since_meta_block; + + /* sending the metadata block */ + if (remaining <= len) + { + /* send any mp3 before the metadata block */ + if (remaining) + { + ret = client_send_bytes (client, buf, remaining); + + if (ret > 0) + { + client_mp3->since_meta_block += ret; + client->pos += ret; + } + if (ret < (int)remaining) + break; + written += ret; + } + ret = send_mp3_metadata (client, refbuf->associated); + if (client_mp3->in_metadata) + break; + written += ret; + /* change buf and len */ + buf += remaining; + len -= remaining; + } + } + /* write any mp3, maybe after the metadata block */ + if (len) + { + ret = client_send_bytes (client, buf, len); + + if (ret > 0) + { + client_mp3->since_meta_block += ret; + client->pos += ret; + } + if (ret < (int)len) + break; + written += ret; + } + ret = 0; + /* we have now written what we need to in here */ + if (refbuf->next) + { + client->refbuf = refbuf->next; + client->pos = 0; + } + } while (0); + + if (ret > 0) + written += ret; + return written ? written : -1; +} + +static void format_mp3_free_plugin (format_plugin_t *plugin) +{ + /* free the plugin instance */ + mp3_state *state = plugin->_state; + + free(state); + free(plugin); +} + + +static refbuf_t *mp3_get_no_meta (source_t *source) +{ + int bytes; + refbuf_t *refbuf; + mp3_state *source_mp3 = source->format->_state; + + if ((refbuf = refbuf_new (4096)) == NULL) + return NULL; + bytes = sock_read_bytes (source->con->sock, refbuf->data, 4096); + + if (bytes == 0) + { + INFO1 ("End of stream %s", source->mount); + source->running = 0; + refbuf_release (refbuf); + return NULL; + } + if (source_mp3->update_metadata) + { + mp3_set_title (source); + source_mp3->update_metadata = 0; + } + if (bytes > 0) + { + refbuf->len = bytes; + refbuf->associated = source_mp3->metadata; + refbuf->sync_point = 1; + return refbuf; + } + refbuf_release (refbuf); + + if (!sock_recoverable (sock_error())) + source->running = 0; + + return NULL; +} + + +static refbuf_t *mp3_get_filter_meta (source_t *source) +{ + refbuf_t *refbuf; + format_plugin_t *plugin = source->format; + mp3_state *source_mp3 = plugin->_state; + unsigned char *src; + unsigned bytes, mp3_block; + int ret; + + refbuf = refbuf_new (2048); + src = refbuf->data; + + ret = sock_read_bytes (source->con->sock, refbuf->data, 2048); + + if (ret == 0) + { + INFO1 ("End of stream %s", source->mount); + source->running = 0; + refbuf_release (refbuf); + return NULL; + } + if (source_mp3->update_metadata) + { + mp3_set_title (source); + source_mp3->update_metadata = 0; + } + if (ret < 0) + { + refbuf_release (refbuf); + if (sock_recoverable (sock_error())) + return NULL; /* go back to waiting */ + INFO0 ("Error on connection from source"); + source->running = 0; + return NULL; + } + /* fill the buffer with the read data */ + bytes = (unsigned)ret; + while (bytes > 0) + { + unsigned metadata_remaining; + + mp3_block = source_mp3->inline_metadata_interval - source_mp3->offset; + + /* is there only enough to account for mp3 data */ + if (bytes <= mp3_block) + { + refbuf->len += bytes; + source_mp3->offset += bytes; + break; + } + /* we have enough data to get to the metadata block, but only transfer upto it */ + if (mp3_block) + { + src += mp3_block; + bytes -= mp3_block; + refbuf->len += mp3_block; + source_mp3->offset += mp3_block; + continue; + } + + /* are we processing the inline metadata, len == 0 indicates not seen any */ + if (source_mp3->build_metadata_len == 0) + { + memset (source_mp3->build_metadata, 0, sizeof (source_mp3->build_metadata)); + source_mp3->build_metadata_offset = 0; + source_mp3->build_metadata_len = 1 + (*src * 16); + } + + /* do we have all of the metatdata block */ + metadata_remaining = source_mp3->build_metadata_len - source_mp3->build_metadata_offset; + if (bytes < metadata_remaining) + { + memcpy (source_mp3->build_metadata + source_mp3->build_metadata_offset, + src, bytes); + source_mp3->build_metadata_offset += bytes; + break; + } + memcpy (source_mp3->build_metadata + source_mp3->build_metadata_offset, + src, metadata_remaining); + + /* overwrite metadata in the buffer */ + bytes -= metadata_remaining; + memmove (src, src+metadata_remaining, bytes); + + /* assign metadata if it's not 1 byte, as that indicates a change */ + if (source_mp3->build_metadata_len > 1) + { + refbuf_t *meta = refbuf_new (source_mp3->build_metadata_len); + memcpy (meta->data, source_mp3->build_metadata, source_mp3->build_metadata_len); + meta->len = source_mp3->build_metadata_len; + + DEBUG1("shoutcast metadata %.4080s", meta->data+1); + if (strncmp (meta->data+1, "StreamTitle=", 12) == 0) + { + filter_shoutcast_metadata (source, source_mp3->build_metadata, source_mp3->build_metadata_len); + source_mp3->metadata = meta; + } + else + { + ERROR0 ("Incorrect metadata format, ending stream"); + source->running = 0; + refbuf_release (refbuf); + return NULL; + } + } + source_mp3->offset = 0; + source_mp3->build_metadata_len = 0; + } + refbuf->associated = source_mp3->metadata; + refbuf->sync_point = 1; + + return refbuf; +} + + +static void mp3_set_predata (source_t *source, client_t *client) +{ + mp3_client_data *mp3data = client->format_data; + + if (mp3data->use_metadata) + { + unsigned remaining = client->predata_size - client->predata_len + 2; + char *ptr = client->predata + client->predata_len - 2; + int bytes; + + bytes = snprintf (ptr, remaining, "icy-metaint:%u\r\n\r\n", + ICY_METADATA_INTERVAL); + if (bytes > 0) + client->predata_len += bytes - 2; + } +} + + +static int format_mp3_create_client_data(source_t *source, client_t *client) +{ + mp3_client_data *data = calloc(1,sizeof(mp3_client_data)); + char *metadata; + + if (data == NULL) + { + ERROR0 ("malloc failed"); + return -1; + } + + client->format_data = data; + client->free_client_data = free_mp3_client_data; + metadata = httpp_getvar(client->parser, "icy-metadata"); + + if(metadata) + { + data->use_metadata = atoi(metadata)>0?1:0; + + mp3_set_predata (source, client); + } + + return 0; +} + + +static void free_mp3_client_data (client_t *client) +{ + free (client->format_data); + client->format_data = NULL; +} + + +static void write_mp3_to_file (struct source_tag *source, refbuf_t *refbuf) +{ + if (refbuf->len == 0) + return; + if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) < (size_t)refbuf->len) + { + WARN0 ("Write to dump file failed, disabling"); + fclose (source->dumpfile); + source->dumpfile = NULL; + } +} + diff --git a/src/format_mp3.h b/src/format_mp3.h new file mode 100644 index 00000000..7c46b9bd --- /dev/null +++ b/src/format_mp3.h @@ -0,0 +1,41 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* format_mp3.h +** +** mp3 format plugin +** +*/ +#ifndef __FORMAT_MP3_H__ +#define __FORMAT_MP3_H__ + +#include "refbuf.h" + +typedef struct { + /* These are for inline metadata */ + int inline_metadata_interval; + unsigned interval; + int offset; + char *url_artist; + char *url_title; + int update_metadata; + + refbuf_t *metadata; + + unsigned build_metadata_len; + unsigned build_metadata_offset; + char build_metadata[4081]; +} mp3_state; + +int format_mp3_get_plugin(struct source_tag *src); + +#endif /* __FORMAT_MP3_H__ */ diff --git a/src/format_vorbis.c b/src/format_vorbis.c new file mode 100644 index 00000000..06de6952 --- /dev/null +++ b/src/format_vorbis.c @@ -0,0 +1,797 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* format_vorbis.c +** +** format plugin for vorbis +** +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "refbuf.h" +#include "source.h" +#include "client.h" + +#include "stats.h" +#include "format.h" + +#define CATMODULE "format-vorbis" +#include "logging.h" + +static ogg_int64_t next_rebuild_serialno = 0; +static mutex_t serial_lock; + +typedef struct _vstate_tag +{ + ogg_sync_state oy; + ogg_stream_state os, out_os; + vorbis_info vi; + vorbis_comment vc; + + ogg_packet *prev_packet; + refbuf_t *file_headers; + + int initial_audio_packet; + int stream_notify; + int use_url_comment; + int to_terminate; + int more_headers; + int prev_window; + int page_samples_trigger; + ogg_int64_t granulepos; + ogg_int64_t samples_in_page; + ogg_int64_t prev_samples; + ogg_int64_t prev_page_samples; + + refbuf_t *headers_head; + refbuf_t *headers_tail; + ogg_packet *header [3]; + ogg_packet url_comment; + char *url_artist; + char *url_title; + + int (*process_packet)(source_t *); + refbuf_t *(*get_buffer_page)(struct _vstate_tag *source_vorbis); + +} vstate_t; + +struct client_vorbis +{ + refbuf_t *headers; + refbuf_t *header_page; + unsigned pos; + int headers_sent; +}; + + +static ogg_int64_t get_next_serialno () +{ + ogg_int64_t serialno; + thread_mutex_lock (&serial_lock); + serialno = next_rebuild_serialno++; + thread_mutex_unlock (&serial_lock); + return serialno; +} + +static void format_vorbis_free_plugin (format_plugin_t *plugin); +static int create_vorbis_client_data(source_t *source, client_t *client); +static void free_vorbis_client_data (client_t *client); + +static void write_vorbis_to_file (struct source_tag *source, refbuf_t *refbuf); +static refbuf_t *vorbis_get_buffer (source_t *source); +static int vorbis_write_buf_to_client (format_plugin_t *self, client_t *client); +static void vorbis_set_tag (format_plugin_t *plugin, char *tag, char *value); + + +static void free_ogg_packet (ogg_packet *packet) +{ + if (packet) + { + free (packet->packet); + free (packet); + } +} + + +int format_ogg_get_plugin (source_t *source) +{ + format_plugin_t *plugin; + vstate_t *state; + vorbis_comment vc; + + plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t)); + + plugin->type = FORMAT_TYPE_OGG; + plugin->format_description = "Ogg Vorbis"; + plugin->get_buffer = vorbis_get_buffer; + plugin->write_buf_to_client = vorbis_write_buf_to_client; + plugin->write_buf_to_file = write_vorbis_to_file; + plugin->create_client_data = create_vorbis_client_data; + plugin->free_plugin = format_vorbis_free_plugin; + plugin->set_tag = vorbis_set_tag; + + state = (vstate_t *)calloc(1, sizeof(vstate_t)); + ogg_sync_init(&state->oy); + ogg_stream_init (&state->out_os, get_next_serialno()); + + vorbis_comment_init (&vc); + vorbis_commentheader_out (&vc, &state->url_comment); + vorbis_comment_clear (&vc); + + plugin->_state = (void *)state; + source->format = plugin; + + return 0; +} + +void format_vorbis_free_plugin (format_plugin_t *plugin) +{ + vstate_t *state = plugin->_state; + + /* free memory associated with this plugin instance */ + + /* free state memory */ + ogg_sync_clear (&state->oy); + ogg_stream_clear (&state->os); + ogg_stream_clear (&state->out_os); + vorbis_comment_clear (&state->vc); + vorbis_info_clear (&state->vi); + + free_ogg_packet (state->header[0]); + free_ogg_packet (state->header[1]); + free_ogg_packet (state->header[2]); + if (state->prev_packet) + free_ogg_packet (state->prev_packet); + + ogg_packet_clear (&state->url_comment); + + free (state); + + /* free the plugin instance */ + free (plugin); +} + + +static ogg_packet *copy_ogg_packet (ogg_packet *packet) +{ + ogg_packet *next; + do + { + next = malloc (sizeof (ogg_packet)); + if (next == NULL) + break; + memcpy (next, packet, sizeof (ogg_packet)); + next->packet = malloc (next->bytes); + if (next->packet == NULL) + break; + memcpy (next->packet, packet->packet, next->bytes); + return next; + } while (0); + + if (next) + free (next); + return NULL; +} + + +static void add_audio_packet (vstate_t *source_vorbis, ogg_packet *packet) +{ + if (source_vorbis->initial_audio_packet) + { + packet->granulepos = 0; + source_vorbis->initial_audio_packet = 0; + } + else + { + source_vorbis->samples_in_page += (packet->granulepos - source_vorbis->prev_samples); + source_vorbis->prev_samples = packet->granulepos; + source_vorbis->granulepos += source_vorbis->prev_window; + } + /* printf ("Adding packet %lld, granulepos %lld (%ld)\n", packet->packetno, + packet->granulepos, vorbis_packet_blocksize (&source_vorbis->vi, packet)/4); */ + ogg_stream_packetin (&source_vorbis->out_os, packet); +} + + +static refbuf_t *get_buffer_audio (vstate_t *source_vorbis) +{ + refbuf_t *refbuf = NULL; + ogg_page page; + int (*get_ogg_page)(ogg_stream_state*, ogg_page *) = ogg_stream_pageout; + + /* printf ("current sample count is %lld, %ld\n", source_vorbis->samples_in_page, source_vorbis->vi.rate>>1); */ + if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger) + { + get_ogg_page = ogg_stream_flush; + /* printf ("forcing flush with %lld samples\n", source_vorbis->samples_in_page); */ + } + if (get_ogg_page (&source_vorbis->out_os, &page) > 0) + { + /* printf ("got audio page %lld\n", ogg_page_granulepos (&page)); */ + /* squeeze a page copy into a buffer */ + source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples); + source_vorbis->prev_page_samples = ogg_page_granulepos (&page); + + refbuf = refbuf_new (page.header_len + page.body_len); + memcpy (refbuf->data, page.header, page.header_len); + memcpy (refbuf->data+page.header_len, page.body, page.body_len); + refbuf->len = page.header_len + page.body_len; + refbuf->associated = source_vorbis->headers_head; + /* printf ("setting associated to %p\n", refbuf->associated); */ + } + return refbuf; +} + + +static refbuf_t *get_buffer_header (vstate_t *source_vorbis) +{ + int headers_flushed = 0; + ogg_page page; + + /* printf ("in buffer_header\n"); */ + while (ogg_stream_flush (&source_vorbis->out_os, &page) > 0) + { + refbuf_t *refbuf; + /* squeeze a page copy into a buffer */ + /* printf ("Stored vorbis header\n"); */ + refbuf = refbuf_new (page.header_len + page.body_len); + memcpy (refbuf->data, page.header, page.header_len); + memcpy (refbuf->data+page.header_len, page.body, page.body_len); + refbuf->len = page.header_len + page.body_len; + + /* store header page for associated list */ + if (source_vorbis->headers_tail) + source_vorbis->headers_tail->next = refbuf; + if (source_vorbis->headers_head == NULL) + source_vorbis->headers_head = refbuf; + source_vorbis->headers_tail = refbuf; + headers_flushed = 1; + } + if (headers_flushed) + { + /* printf ("headers have now been handled\n"); */ + source_vorbis->get_buffer_page = get_buffer_audio; + } + return NULL; +} + + +static refbuf_t *get_buffer_finished (vstate_t *source_vorbis) +{ + ogg_page page; + + if (ogg_stream_flush (&source_vorbis->out_os, &page) > 0) + { + refbuf_t *refbuf; + /* printf ("EOS stream flush %lld\n", ogg_page_granulepos (&page)); */ + + source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples); + source_vorbis->prev_page_samples = ogg_page_granulepos (&page); + + refbuf = refbuf_new (page.header_len + page.body_len); + memcpy (refbuf->data, page.header, page.header_len); + memcpy (refbuf->data+page.header_len, page.body, page.body_len); + refbuf->len = page.header_len + page.body_len; + refbuf->associated = source_vorbis->headers_head; + return refbuf; + } + ogg_stream_clear (&source_vorbis->out_os); + ogg_stream_init (&source_vorbis->out_os, get_next_serialno()); + source_vorbis->headers_head = NULL; + source_vorbis->headers_tail = NULL; + source_vorbis->get_buffer_page = get_buffer_header; + /* printf ("stream cleared\n"); */ + return NULL; +} + + +/* pushed last packet into stream marked with eos */ +static void initiate_flush (vstate_t *source_vorbis) +{ + if (source_vorbis->prev_packet) + { + /* insert prev_packet with eos */ + source_vorbis->prev_packet->e_o_s = 1; + /* printf ("adding stored packet marked as EOS\n"); */ + add_audio_packet (source_vorbis, source_vorbis->prev_packet); + source_vorbis->prev_packet->e_o_s = 0; + } + source_vorbis->get_buffer_page = get_buffer_finished; + source_vorbis->initial_audio_packet = 1; +} + +/* just deal with ogg vorbis streams at the moment */ + +static int process_vorbis_audio (source_t *source) +{ + vstate_t *source_vorbis = source->format->_state; + int result = 0; + + while (1) + { + int window; + ogg_packet packet; + + /* now, lets extract what packets we can */ + if (ogg_stream_packetout (&source_vorbis->os, &packet) <= 0) + return result; + + result = 1; + + /* calculate granulepos for the packet */ + window = vorbis_packet_blocksize (&source_vorbis->vi, &packet) / 4; + + source_vorbis->granulepos += window; + if (source_vorbis->prev_packet) + { + ogg_packet *prev_packet = source_vorbis->prev_packet; + if (packet.b_o_s) + prev_packet->e_o_s = 1; + add_audio_packet (source_vorbis, prev_packet); + /* printf ("Adding prev packet %lld, granulepos %lld (%d) samples %lld\n", prev_packet->packetno, + prev_packet->granulepos, source_vorbis->prev_window, source_vorbis->samples_in_page); */ + free_ogg_packet (prev_packet); + + packet . granulepos = source_vorbis->granulepos; + } + else + { + packet . granulepos = 0; + } + source_vorbis->prev_window = window; + + /* copy the next packet */ + source_vorbis->prev_packet = copy_ogg_packet (&packet); + + /* allow for pages to be flushed if there's over a certain number of samples */ + if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger) + return 1; + } +} + +/* handle the headers we want going to the clients */ +static int process_vorbis_headers (source_t *source) +{ + vstate_t *source_vorbis = source->format->_state; + + /* trap for missing initial header, this means we're expecting + headers coming in, so jump out and try in a short while */ + if (source_vorbis->header [0] == NULL) + return 0; + /* printf ("Adding the 3 header packets\n"); */ + ogg_stream_packetin (&source_vorbis->out_os, source_vorbis->header [0]); + /* NOTE: we could build a separate comment packet each time */ + if (source_vorbis->use_url_comment) + ogg_stream_packetin (&source_vorbis->out_os, &source_vorbis->url_comment); + else + ogg_stream_packetin (&source_vorbis->out_os, source_vorbis->header [1]); + ogg_stream_packetin (&source_vorbis->out_os, source_vorbis->header [2]); + source_vorbis->use_url_comment = 0; + + source_vorbis->process_packet = process_vorbis_audio; + source_vorbis->granulepos = 0; + source_vorbis->initial_audio_packet = 1; + return 1; +} + + +/* this is called with the first page after the initial header */ +/* it processes any headers that have come in on the stream */ +static int process_vorbis_incoming_hdrs (source_t *source) +{ + char *tag; + ogg_packet header; + vstate_t *source_vorbis = source->format->_state; + + /* printf ("processing incoming header packet\n"); */ + while (source_vorbis->more_headers) + { + /* now, lets extract the packets */ + int result = ogg_stream_packetout (&source_vorbis->os, &header); + + if (result <= 0) + return result; /* need more pages */ + + /* change comments here if need be */ + if (vorbis_synthesis_headerin (&source_vorbis->vi, &source_vorbis->vc, &header) < 0) + { + WARN0 ("Problem parsing ogg vorbis header"); + return -1; + } + header.granulepos = 0; + /* printf ("Parsing [%d] vorbis header %lld, %lld\n", source_vorbis->more_headers, header.packetno, header.granulepos); */ + source_vorbis->header [3-source_vorbis->more_headers] = copy_ogg_packet (&header); + source_vorbis->more_headers--; + } + + /* we have all headers */ + + /* put known comments in the stats, this could be site specific */ + tag = vorbis_comment_query (&source_vorbis->vc, "TITLE", 0); + if (tag == NULL) + tag = "unknown"; + stats_event (source->mount, "title", tag); + + tag = vorbis_comment_query (&source_vorbis->vc, "ARTIST", 0); + if (tag == NULL) + tag = "unknown"; + stats_event (source->mount, "artist", tag); + + stats_event_args (source->mount, "ice-samplerate", "%ld", (long)source_vorbis->vi.rate); + stats_event_args (source->mount, "ice-channels", "%ld", (long)source_vorbis->vi.channels); + stats_event_args (source->mount, "ice-bitrate", "%ld", (long)source_vorbis->vi.bitrate_nominal/1024); + /* set queued pages to contain a 1/4 of a second worth of samples */ + source_vorbis->page_samples_trigger = source_vorbis->vi.rate / 4; + + /* printf ("finished with incoming header packets\n"); */ + source_vorbis->process_packet = process_vorbis_headers; + + return 1; +} + + + +static int initial_vorbis_page (vstate_t *source_vorbis, ogg_packet *packet) +{ + /* init vi and vc */ + vorbis_comment_clear (&source_vorbis->vc); + vorbis_info_clear (&source_vorbis->vi); + + vorbis_info_init (&source_vorbis->vi); + vorbis_comment_init (&source_vorbis->vc); + + /* printf ("processing initial page\n"); */ + if (vorbis_synthesis_headerin (&source_vorbis->vi, &source_vorbis->vc, packet) < 0) + { + /* printf ("not a vorbis packet\n"); */ + return -1; + } + + /* printf ("Handling ogg vorbis header\n"); */ + free_ogg_packet (source_vorbis->header[0]); + free_ogg_packet (source_vorbis->header[1]); + free_ogg_packet (source_vorbis->header[2]); + memset (source_vorbis->header, 0, sizeof (source_vorbis->header)); + source_vorbis->header [0] = copy_ogg_packet (packet); + source_vorbis->more_headers = 2; + + initiate_flush (source_vorbis); + source_vorbis->process_packet = process_vorbis_incoming_hdrs; + /* free previous audio packet, it maybe in a different format */ + free_ogg_packet (source_vorbis->prev_packet); + source_vorbis->prev_packet = NULL; + source_vorbis->prev_window = 0; + + source_vorbis->headers_head = NULL; + source_vorbis->headers_tail = NULL; + source_vorbis->initial_audio_packet = 1; + + return 0; +} + + +static int process_initial_page (source_t *source, ogg_page *page) +{ + vstate_t *source_vorbis = source->format->_state; + int ret = -1; + ogg_packet packet; + + ogg_stream_clear (&source_vorbis->os); + ogg_stream_init (&source_vorbis->os, ogg_page_serialno (page)); + + ogg_stream_pagein (&source_vorbis->os, page); + do + { + if (ogg_stream_packetout (&source_vorbis->os, &packet) <= 0) + break; + ret = 0; + if (initial_vorbis_page (source_vorbis, &packet) == 0) + break; + /* any others */ + ret = -1; + } while (0); + /* printf ("processed initial page\n"); */ + return ret; +} + + +static void vorbis_set_tag (format_plugin_t *plugin, char *tag, char *value) +{ + vstate_t *source_vorbis = plugin->_state; + int change = 0; + if (strcmp (tag, "artist") == 0) + { + char *p = strdup (value); + if (p) + { + free (source_vorbis->url_artist); + source_vorbis->url_artist = p; + change = 1; + } + } + if (strcmp (tag, "title") == 0) + { + char *p = strdup (value); + if (p) + { + free (source_vorbis->url_title); + source_vorbis->url_title = p; + change = 1; + } + } + if (change) + source_vorbis->stream_notify = 1; +} + + +static void update_comments (source_t *source) +{ + vstate_t *source_vorbis = source->format->_state; + vorbis_comment vc; + ogg_packet header; + + initiate_flush (source_vorbis); + + /* printf ("updated comment header\n"); */ + vorbis_comment_init (&vc); + if (source_vorbis->url_artist) + vorbis_comment_add_tag (&vc, "artist", source_vorbis->url_artist); + if (source_vorbis->url_title) + vorbis_comment_add_tag (&vc, "title", source_vorbis->url_title); + vorbis_comment_add (&vc, "server=" ICECAST_VERSION_STRING); + ogg_packet_clear (&source_vorbis->url_comment); + vorbis_commentheader_out (&vc, &source_vorbis->url_comment); + vorbis_comment_clear (&vc); + header.packetno = 1; + source_vorbis->use_url_comment = 1; + source_vorbis->process_packet = process_vorbis_headers; +} + +static refbuf_t *vorbis_get_buffer (source_t *source) +{ + vstate_t *source_vorbis = source->format->_state; + char *data = NULL; + int bytes = 1; + ogg_page page; + refbuf_t *refbuf = NULL; + + while (1) + { + while (1) + { + if (source_vorbis->get_buffer_page) + refbuf = source_vorbis->get_buffer_page (source_vorbis); + if (refbuf) + return refbuf; + + /* printf ("check for processed packets\n"); */ + if (source_vorbis->process_packet && source_vorbis->process_packet (source) > 0) + continue; + /* printf ("Checking for more in-pages\n"); */ + if (ogg_sync_pageout (&source_vorbis->oy, &page) > 0) + { + /* lets see what we do with it */ + if (ogg_page_bos (&page)) + { + process_initial_page (source, &page); + return NULL; + } + /* printf ("Adding in page to out_os\n"); */ + ogg_stream_pagein (&source_vorbis->os, &page); + continue; + } + break; + } + if (source_vorbis->to_terminate) + { + /* normal exit path */ + source->running = 0; + source_vorbis->to_terminate = 0; + return NULL; + } + /* see if any non-stream updates are requested */ + if (source_vorbis->stream_notify) + { + update_comments (source); + source_vorbis->stream_notify = 0; + continue; + } + if (data == NULL) + data = ogg_sync_buffer (&source_vorbis->oy, 4096); + /* printf ("reading data in\n"); */ + bytes = sock_read_bytes (source->con->sock, data, 4096); + if (bytes < 0) + { + if (sock_recoverable (sock_error())) + return NULL; + WARN0 ("source connection has died"); + ogg_sync_wrote (&source_vorbis->oy, 0); + source_vorbis->to_terminate = 1; + initiate_flush (source_vorbis); + return NULL; + } + if (bytes == 0) + { + INFO1 ("End of Stream %s", source->mount); + ogg_sync_wrote (&source_vorbis->oy, 0); + source_vorbis->to_terminate = 1; + initiate_flush (source_vorbis); + return NULL; + } + ogg_sync_wrote (&source_vorbis->oy, bytes); + data = NULL; + } +} + + +static int create_vorbis_client_data (source_t *source, client_t *client) +{ + struct client_vorbis *client_data = calloc (1, sizeof (struct client_vorbis)); + if (client_data == NULL) + { + ERROR0("malloc failed"); + return -1; + } + client_data->headers_sent = 1; + client->format_data = client_data; + client->free_client_data = free_vorbis_client_data; + return 0; +} + +static void free_vorbis_client_data (client_t *client) +{ + free (client->format_data); + client->format_data = NULL; +} + + +static int send_vorbis_headers (client_t *client, refbuf_t *headers) +{ + struct client_vorbis *client_data = client->format_data; + refbuf_t *refbuf; + int written = 0; + + if (client_data->headers_sent) + { + /* printf ("setting client_data header to %p\n", headers); */ + client_data->header_page = headers; + client_data->pos = 0; + client_data->headers_sent = 0; + } + refbuf = client_data->header_page; + while (refbuf) + { + char *data = refbuf->data + client_data->pos; + unsigned len = refbuf->len - client_data->pos; + int ret; + + /* printf ("....sending header at %p\n", refbuf); */ + ret = client_send_bytes (client, data, len); + if (ret > 0) + written += ret; + if (ret < (int)len) + return written ? written : -1; + client_data->pos += ret; + if (client_data->pos == refbuf->len) + { + refbuf = refbuf->next; + client_data->header_page = refbuf; + client_data->pos = 0; + } + } + client_data->headers_sent = 1; + client_data->headers = headers; + return written; +} + + +static int vorbis_write_buf_to_client (format_plugin_t *self, client_t *client) +{ + refbuf_t *refbuf = client->refbuf; + char *buf; + unsigned len; + struct client_vorbis *client_data = client->format_data; + int ret, written = 0; + + /* rare but the listener could connect before audio is ready */ + if (refbuf == NULL) + return 0; + /* printf ("client %p (%p) @ %lu\n", refbuf, refbuf->next, client->pos); */ + if (refbuf->next == NULL && client->pos == refbuf->len) + return 0; + + if (refbuf->next && client->pos == refbuf->len) + { + client->refbuf = refbuf->next; + client->pos = 0; + } + refbuf = client->refbuf; + buf = refbuf->data + client->pos; + len = refbuf->len - client->pos; + do + { + if (client_data->headers != refbuf->associated) + { + /* printf ("sending header data %p\n", refbuf->associated); */ + ret = send_vorbis_headers (client, refbuf->associated); + if (client_data->headers_sent == 0) + break; + written += ret; + } + /* printf ("sending audio data\n"); */ + ret = client_send_bytes (client, buf, len); + + if (ret > 0) + client->pos += ret; + + if (ret < (int)len) + break; + written += ret; + /* we have now written the header page(s) */ + ret = 0; + } while (0); + + if (ret > 0) + written += ret; + return written ? written : -1; +} + + +static int write_vorbis_data (struct source_tag *source, refbuf_t *refbuf) +{ + int ret = 1; + if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len) + { + WARN0 ("Write to dump file failed, disabling"); + fclose (source->dumpfile); + source->dumpfile = NULL; + ret = 0; + } + return ret; +} + + +static void write_vorbis_to_file (struct source_tag *source, refbuf_t *refbuf) +{ + vstate_t *source_vorbis = source->format->_state; + + if (source_vorbis->file_headers != refbuf->associated) + { + refbuf_t *header = refbuf->associated; + while (header) + { + if (write_vorbis_data (source, header) == 0) + return; + header = header->next; + } + source_vorbis->file_headers = refbuf->associated; + } + write_vorbis_data (source, refbuf); +} + + +void format_ogg_initialise (void) +{ + next_rebuild_serialno = 1; + thread_mutex_create ("serial", &serial_lock); +} + diff --git a/src/format_vorbis.h b/src/format_vorbis.h new file mode 100644 index 00000000..ddf77ad2 --- /dev/null +++ b/src/format_vorbis.h @@ -0,0 +1,24 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* format_vorbis.h +** +** vorbis format plugin header +** +*/ +#ifndef __FORMAT_VORBIS_H__ +#define __FORMAT_VORBIS_H__ + +int format_ogg_get_plugin (source_t *source); +void format_ogg_initialise (void); + +#endif /* __FORMAT_VORBIS_H__ */ diff --git a/src/fserve.c b/src/fserve.c new file mode 100644 index 00000000..ec34f338 --- /dev/null +++ b/src/fserve.c @@ -0,0 +1,522 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#ifdef HAVE_POLL +#include +#endif + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#endif + +#include "thread/thread.h" +#include "avl/avl.h" +#include "httpp/httpp.h" +#include "net/sock.h" + +#include "connection.h" +#include "global.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" +#include "format.h" +#include "logging.h" +#include "cfgfile.h" +#include "util.h" + +#include "fserve.h" + +#undef CATMODULE +#define CATMODULE "fserve" + +#define BUFSIZE 4096 + +#ifdef _WIN32 +#define MIMETYPESFILE ".\\mime.types" +#else +#define MIMETYPESFILE "/etc/mime.types" +#endif + +static avl_tree *client_tree; +static avl_tree *pending_tree; +static avl_tree *mimetypes = NULL; + +static cond_t fserv_cond; +static thread_type *fserv_thread; +static int run_fserv; +static int fserve_clients; +static int client_tree_changed=0; + +#ifdef HAVE_POLL +static struct pollfd *ufds = NULL; +static int ufdssize = 0; +#else +static fd_set fds; +static int fd_max = 0; +#endif + +typedef struct { + char *ext; + char *type; +} mime_type; + +/* avl tree helper */ +static int _compare_clients(void *compare_arg, void *a, void *b); +static int _remove_client(void *key); +static int _free_client(void *key); +static int _delete_mapping(void *mapping); +static void *fserv_thread_function(void *arg); +static void create_mime_mappings(char *fn); + +void fserve_initialize(void) +{ + ice_config_t *config = config_get_config(); + int serve = config->fileserve; + + config_release_config(); + + if(!serve) + return; + + create_mime_mappings(MIMETYPESFILE); + + client_tree = avl_tree_new(_compare_clients, NULL); + pending_tree = avl_tree_new(_compare_clients, NULL); + thread_cond_create(&fserv_cond); + + run_fserv = 1; + + fserv_thread = thread_create("File Serving Thread", + fserv_thread_function, NULL, THREAD_ATTACHED); +} + +void fserve_shutdown(void) +{ + ice_config_t *config = config_get_config(); + int serve = config->fileserve; + + config_release_config(); + + if(!serve) + return; + + if(!run_fserv) + return; + + avl_tree_free(mimetypes, _delete_mapping); + + run_fserv = 0; + thread_cond_signal(&fserv_cond); + thread_join(fserv_thread); + + thread_cond_destroy(&fserv_cond); + avl_tree_free(client_tree, _free_client); + avl_tree_free(pending_tree, _free_client); +} + +static void wait_for_fds() { + avl_node *client_node; + fserve_t *client; + int i; + + while(run_fserv) { +#ifdef HAVE_POLL + if(client_tree_changed) { + client_tree_changed = 0; + i = 0; + ufdssize = fserve_clients; + ufds = realloc(ufds, ufdssize * sizeof(struct pollfd)); + avl_tree_rlock(client_tree); + client_node = avl_get_first(client_tree); + while(client_node) { + client = client_node->key; + ufds[i].fd = client->client->con->sock; + ufds[i].events = POLLOUT; + client_node = avl_get_next(client_node); + } + avl_tree_unlock(client_tree); + } + + if(poll(ufds, ufdssize, 200) > 0) + return; +#else + struct timeval tv; + fd_set realfds; + tv.tv_sec = 0; + tv.tv_usec = 200000; + if(client_tree_changed) { + client_tree_changed = 0; + i=0; + FD_ZERO(&fds); + fd_max = 0; + avl_tree_rlock(client_tree); + client_node = avl_get_first(client_tree); + while(client_node) { + client = client_node->key; + FD_SET(client->client->con->sock, &fds); + if(client->client->con->sock > fd_max) + fd_max = client->client->con->sock; + client_node = avl_get_next(client_node); + } + avl_tree_unlock(client_tree); + } + + memcpy(&realfds, &fds, sizeof(fd_set)); + if(select(fd_max+1, NULL, &realfds, NULL, &tv) > 0) + return; +#endif + else { + avl_tree_rlock(pending_tree); + client_node = avl_get_first(pending_tree); + avl_tree_unlock(pending_tree); + if(client_node) + return; + } + } +} + +static void *fserv_thread_function(void *arg) +{ + avl_node *client_node, *pending_node; + fserve_t *client; + int sbytes, bytes; + + while (run_fserv) { + avl_tree_rlock(client_tree); + + client_node = avl_get_first(client_tree); + if(!client_node) { + avl_tree_rlock(pending_tree); + pending_node = avl_get_first(pending_tree); + if(!pending_node) { + /* There are no current clients. Wait until there are... */ + avl_tree_unlock(pending_tree); + avl_tree_unlock(client_tree); + thread_cond_wait(&fserv_cond); + continue; + } + avl_tree_unlock(pending_tree); + } + + /* This isn't hugely efficient, but it'll do for now */ + avl_tree_unlock(client_tree); + wait_for_fds(); + + avl_tree_rlock(client_tree); + client_node = avl_get_first(client_tree); + + while(client_node) { + avl_node_wlock(client_node); + + client = (fserve_t *)client_node->key; + + if(client->offset >= client->datasize) { + /* Grab a new chunk */ + bytes = fread(client->buf, 1, BUFSIZE, client->file); + if(bytes <= 0) { + client->client->con->error = 1; + avl_node_unlock(client_node); + client_node = avl_get_next(client_node); + continue; + } + client->offset = 0; + client->datasize = bytes; + } + + /* Now try and send current chunk. */ + sbytes = client_send_bytes (client->client, + &client->buf[client->offset], + client->datasize - client->offset); + + /* TODO: remove clients if they take too long. */ + if(sbytes > 0) { + client->offset += sbytes; + } + + avl_node_unlock(client_node); + client_node = avl_get_next(client_node); + } + + avl_tree_unlock(client_tree); + + /* Now we need a write lock instead, to delete done clients. */ + avl_tree_wlock(client_tree); + + client_node = avl_get_first(client_tree); + while(client_node) { + client = (fserve_t *)client_node->key; + if(client->client->con->error) { + fserve_clients--; + client_node = avl_get_next(client_node); + avl_delete(client_tree, (void *)client, _free_client); + client_tree_changed = 1; + continue; + } + client_node = avl_get_next(client_node); + } + + avl_tree_wlock(pending_tree); + + /* And now insert new clients. */ + client_node = avl_get_first(pending_tree); + while(client_node) { + client = (fserve_t *)client_node->key; + avl_insert(client_tree, client); + client_tree_changed = 1; + fserve_clients++; + stats_event_inc(NULL, "clients"); + client_node = avl_get_next(client_node); + + } + + /* clear pending */ + while(avl_get_first(pending_tree)) { + avl_delete(pending_tree, avl_get_first(pending_tree)->key, + _remove_client); + } + + avl_tree_unlock(pending_tree); + avl_tree_unlock(client_tree); + } + + /* Shutdown path */ + + avl_tree_wlock(pending_tree); + while(avl_get_first(pending_tree)) + avl_delete(pending_tree, avl_get_first(pending_tree)->key, + _free_client); + avl_tree_unlock(pending_tree); + + avl_tree_wlock(client_tree); + while(avl_get_first(client_tree)) + avl_delete(client_tree, avl_get_first(client_tree)->key, + _free_client); + avl_tree_unlock(client_tree); + + thread_exit(0); + return NULL; +} + +static char *fserve_content_type(char *path) +{ + char *ext = util_get_extension(path); + mime_type exttype = {ext, NULL}; + void *result; + + if (!avl_get_by_key (mimetypes, &exttype, &result)) + { + mime_type *mime = result; + return mime->type; + } + else { + /* Fallbacks for a few basic ones */ + if(!strcmp(ext, "ogg")) + return "application/ogg"; + else if(!strcmp(ext, "mp3")) + return "audio/mpeg"; + else if(!strcmp(ext, "html")) + return "text/html"; + else if(!strcmp(ext, "txt")) + return "text/plain"; + else + return "application/octet-stream"; + } +} + +static void fserve_client_destroy(fserve_t *client) +{ + if(client) { + if(client->buf) + free(client->buf); + if(client->file) + fclose(client->file); + + if(client->client) + client_destroy(client->client); + free(client); + } +} + +int fserve_client_create(client_t *httpclient, char *path) +{ + fserve_t *client = calloc(1, sizeof(fserve_t)); + int bytes; + int client_limit; + ice_config_t *config = config_get_config(); + + client_limit = config->client_limit; + config_release_config(); + + client->file = fopen(path, "rb"); + if(!client->file) { + client_send_404(httpclient, "File not readable"); + return -1; + } + + client->client = httpclient; + client->offset = 0; + client->datasize = 0; + client->buf = malloc(BUFSIZE); + + global_lock(); + if(global.clients >= client_limit) { + httpclient->respcode = 504; + bytes = sock_write(httpclient->con->sock, + "HTTP/1.0 504 Server Full\r\n" + "Content-Type: text/html\r\n\r\n" + "Server is full, try again later.\r\n"); + if(bytes > 0) httpclient->con->sent_bytes = bytes; + fserve_client_destroy(client); + global_unlock(); + return -1; + } + global.clients++; + global_unlock(); + + httpclient->respcode = 200; + bytes = sock_write(httpclient->con->sock, + "HTTP/1.0 200 OK\r\n" + "Content-Type: %s\r\n\r\n", + fserve_content_type(path)); + if(bytes > 0) httpclient->con->sent_bytes = bytes; + + sock_set_blocking(client->client->con->sock, SOCK_NONBLOCK); + sock_set_nodelay(client->client->con->sock); + + avl_tree_wlock(pending_tree); + avl_insert(pending_tree, client); + avl_tree_unlock(pending_tree); + + thread_cond_signal(&fserv_cond); + + return 0; +} + +static int _compare_clients(void *compare_arg, void *a, void *b) +{ + fserve_t *clienta = (fserve_t *)a; + fserve_t *clientb = (fserve_t *)b; + + connection_t *cona = clienta->client->con; + connection_t *conb = clientb->client->con; + + if (cona->id < conb->id) return -1; + if (cona->id > conb->id) return 1; + + return 0; +} + +static int _remove_client(void *key) +{ + return 1; +} + +static int _free_client(void *key) +{ + fserve_t *client = (fserve_t *)key; + + fserve_client_destroy(client); + global_lock(); + global.clients--; + global_unlock(); + stats_event_dec(NULL, "clients"); + + + return 1; +} + +static int _delete_mapping(void *mapping) { + mime_type *map = mapping; + free(map->ext); + free(map->type); + free(map); + + return 1; +} + +static int _compare_mappings(void *arg, void *a, void *b) +{ + return strcmp( + ((mime_type *)a)->ext, + ((mime_type *)b)->ext); +} + +static void create_mime_mappings(char *fn) { + FILE *mimefile = fopen(fn, "r"); + char line[4096]; + char *type, *ext, *cur; + mime_type *mapping; + + mimetypes = avl_tree_new(_compare_mappings, NULL); + + if(!mimefile) + return; + + while(fgets(line, 4096, mimefile)) + { + line[4095] = 0; + + if(*line == 0 || *line == '#') + continue; + + type = line; + + cur = line; + + while(*cur != ' ' && *cur != '\t' && *cur) + cur++; + if(*cur == 0) + continue; + + *cur++ = 0; + + while(1) { + while(*cur == ' ' || *cur == '\t') + cur++; + if(*cur == 0) + break; + + ext = cur; + while(*cur != ' ' && *cur != '\t' && *cur != '\n' && *cur) + cur++; + *cur++ = 0; + if(*ext) + { + void *tmp; + /* Add a new extension->type mapping */ + mapping = malloc(sizeof(mime_type)); + mapping->ext = strdup(ext); + mapping->type = strdup(type); + if(!avl_get_by_key(mimetypes, mapping, &tmp)) + avl_delete(mimetypes, mapping, _delete_mapping); + avl_insert(mimetypes, mapping); + } + } + } + + fclose(mimefile); +} + diff --git a/src/fserve.h b/src/fserve.h new file mode 100644 index 00000000..5f076260 --- /dev/null +++ b/src/fserve.h @@ -0,0 +1,35 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __FSERVE_H__ +#define __FSERVE_H__ + +#include + +typedef struct +{ + client_t *client; + + FILE *file; + int offset; + int datasize; + unsigned char *buf; +} fserve_t; + +void fserve_initialize(void); +void fserve_shutdown(void); +int fserve_client_create(client_t *httpclient, char *path); + + +#endif + + diff --git a/src/global.c b/src/global.c new file mode 100644 index 00000000..cc7a3075 --- /dev/null +++ b/src/global.c @@ -0,0 +1,63 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "thread/thread.h" +#include "avl/avl.h" +#include "httpp/httpp.h" + +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "source.h" +#include "format.h" + +#include "global.h" + +ice_global_t global; + +static mutex_t _global_mutex; + +void global_initialize(void) +{ + memset(global.serversock, 0, sizeof(int)*MAX_LISTEN_SOCKETS); + global.server_sockets = 0; + global.relays = NULL; + global.master_relays = NULL; + global.running = 0; + global.clients = 0; + global.sources = 0; + global.source_tree = avl_tree_new(source_compare_sources, NULL); + thread_mutex_create("global", &_global_mutex); +} + +void global_shutdown(void) +{ + thread_mutex_destroy(&_global_mutex); + avl_tree_free(global.source_tree, NULL); +} + +void global_lock(void) +{ + thread_mutex_lock(&_global_mutex); +} + +void global_unlock(void) +{ + thread_mutex_unlock(&_global_mutex); +} diff --git a/src/global.h b/src/global.h new file mode 100644 index 00000000..e646b0f7 --- /dev/null +++ b/src/global.h @@ -0,0 +1,55 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __GLOBAL_H__ +#define __GLOBAL_H__ + +#define ICE_LISTEN_QUEUE 5 + +#define ICE_RUNNING 1 +#define ICE_HALTING 2 + +#define ICECAST_VERSION_STRING "Icecast " PACKAGE_VERSION + +#define MAX_LISTEN_SOCKETS 10 + +#include "thread/thread.h" +#include "slave.h" + +typedef struct ice_global_tag +{ + int serversock[MAX_LISTEN_SOCKETS]; + int server_sockets; + + int running; + + int sources; + int clients; + int schedule_config_reread; + + avl_tree *source_tree; + /* for locally defined relays */ + struct _relay_server *relays; + /* relays retrieved from master */ + struct _relay_server *master_relays; + + cond_t shutdown_cond; +} ice_global_t; + +extern ice_global_t global; + +void global_initialize(void); +void global_shutdown(void); +void global_lock(void); +void global_unlock(void); + +#endif /* __GLOBAL_H__ */ diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 00000000..5b066aa6 --- /dev/null +++ b/src/logging.c @@ -0,0 +1,122 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "thread/thread.h" +#include "httpp/httpp.h" + +#include "connection.h" +#include "refbuf.h" +#include "client.h" + +#include "os.h" +#include "cfgfile.h" +#include "logging.h" +#include "util.h" + +#ifdef _WIN32 +#define snprintf _snprintf +#endif + +/* the global log descriptors */ +int errorlog = 0; +int accesslog = 0; + +/* +** ADDR USER AUTH DATE REQUEST CODE BYTES REFERER AGENT [TIME] +** +** ADDR = client->con->ip +** USER = - +** we should do this for real once we support authentication +** AUTH = - +** DATE = _make_date(client->con->con_time) +** REQUEST = build from client->parser +** CODE = client->respcode +** BYTES = client->con->sent_bytes +** REFERER = get from client->parser +** AGENT = get from client->parser +** TIME = timing_get_time() - client->con->con_time +*/ +void logging_access(client_t *client) +{ + char datebuf[128]; + char reqbuf[1024]; + struct tm thetime; + time_t now; + time_t stayed; + char *referrer, *user_agent; + + now = time(NULL); + + /* build the data */ + localtime_r (&now, &thetime); + strftime (datebuf, sizeof(datebuf), LOGGING_FORMAT_CLF, &thetime); + + /* build the request */ + snprintf (reqbuf, sizeof(reqbuf), "%s %s %s/%s", + httpp_getvar (client->parser, HTTPP_VAR_REQ_TYPE), + httpp_getvar (client->parser, HTTPP_VAR_URI), + httpp_getvar (client->parser, HTTPP_VAR_PROTOCOL), + httpp_getvar (client->parser, HTTPP_VAR_VERSION)); + + stayed = now - client->con->con_time; + + referrer = httpp_getvar (client->parser, "referer"); + if (referrer == NULL) + referrer = "-"; + + user_agent = httpp_getvar (client->parser, "user-agent"); + if (user_agent == NULL) + user_agent = "-"; + +#ifdef HAVE_LOGGING_IP + log_write_direct (accesslog, "%s - - [%s] \"%s\" %d %lld \"%s\" \"%s\" %d", + client->con->ip, + datebuf, reqbuf, client->respcode, client->con->sent_bytes, + referrer, user_agent, (int)stayed); +#else + log_write_direct (accesslog, "- - - [%s] \"%s\" %d %lld \"%s\" \"%s\" %d", + datebuf, reqbuf, client->respcode, client->con->sent_bytes, + referrer, user_agent, (int)stayed); +#endif +} + + + +void restart_logging () +{ + ice_config_t *config = config_get_config_unlocked(); + + if (strcmp (config->error_log, "-")) + { + char fn_error[FILENAME_MAX]; + snprintf (fn_error, FILENAME_MAX, "%s%s%s", config->log_dir, PATH_SEPARATOR, config->error_log); + log_set_filename (errorlog, fn_error); + log_set_level (errorlog, config->loglevel); + log_reopen (errorlog); + } + + if (strcmp (config->access_log, "-")) + { + char fn_error[FILENAME_MAX]; + snprintf (fn_error, FILENAME_MAX, "%s%s%s", config->log_dir, PATH_SEPARATOR, config->access_log); + log_set_filename (accesslog, fn_error); + log_reopen (accesslog); + } +} diff --git a/src/logging.h b/src/logging.h new file mode 100644 index 00000000..e817e215 --- /dev/null +++ b/src/logging.h @@ -0,0 +1,100 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __LOGGING_H__ +#define __LOGGING_H__ + +#include "log/log.h" + +/* declare the global log descriptors */ + +extern int errorlog; +extern int accesslog; + +/* these are all ERRORx and WARNx where _x_ is the number of parameters +** it takes. it turns out most other copmilers don't have support for +** varargs macros. that totally sucks, but this is still pretty easy. +** +** feel free to add more here if needed. +*/ + +#ifdef _WIN32 +#define __FUNCTION__ strrchr (__FILE__, '\\') ? strrchr (__FILE__, '\\') + 1 : __FILE__ +#endif + +#define ERROR0(y) log_write(errorlog, 1, CATMODULE "/", __FUNCTION__, y) +#define ERROR1(y, a) log_write(errorlog, 1, CATMODULE "/", __FUNCTION__, y, a) +#define ERROR2(y, a, b) log_write(errorlog, 1, CATMODULE "/", __FUNCTION__, y, a, b) +#define ERROR3(y, a, b, c) log_write(errorlog, 1, CATMODULE "/", __FUNCTION__, y, a, b, c) + +#define WARN0(y) log_write(errorlog, 2, CATMODULE "/", __FUNCTION__, y) +#define WARN1(y, a) log_write(errorlog, 2, CATMODULE "/", __FUNCTION__, y, a) +#define WARN2(y, a, b) log_write(errorlog, 2, CATMODULE "/", __FUNCTION__, y, a, b) +#define WARN3(y, a, b, c) log_write(errorlog, 2, CATMODULE "/", __FUNCTION__, y, a, b, c) + +#define INFO0(y) log_write(errorlog, 3, CATMODULE "/", __FUNCTION__, y) +#define INFO1(y, a) log_write(errorlog, 3, CATMODULE "/", __FUNCTION__, y, a) +#define INFO2(y, a, b) log_write(errorlog, 3, CATMODULE "/", __FUNCTION__, y, a, b) +#define INFO3(y, a, b, c) log_write(errorlog, 3, CATMODULE "/", __FUNCTION__, y, a, b, c) + +#define DEBUG0(y) log_write(errorlog, 4, CATMODULE "/", __FUNCTION__, y) +#define DEBUG1(y, a) log_write(errorlog, 4, CATMODULE "/", __FUNCTION__, y, a) +#define DEBUG2(y, a, b) log_write(errorlog, 4, CATMODULE "/", __FUNCTION__, y, a, b) +#define DEBUG3(y, a, b, c) log_write(errorlog, 4, CATMODULE "/", __FUNCTION__, y, a, b, c) +#define DEBUG4(y, a, b, c, d) log_write(errorlog, 4, CATMODULE "/", __FUNCTION__, y, a, b, c, d) + +/* CATMODULE is the category or module that logging messages come from. +** we set one here in cause someone forgets in the .c file. +*/ +/*#define CATMODULE "unknown" + */ + +/* this is the logging call to write entries to the access_log +** the combined log format is: +** ADDR USER AUTH DATE REQUEST CODE BYTES REFERER AGENT [TIME] +** ADDR = ip address of client +** USER = username if authenticated +** AUTH = auth type, not used, and set to "-" +** DATE = date in "[30/Apr/2001:01:25:34 -0700]" format +** REQUEST = request, ie "GET /live.ogg HTTP/1.0" +** CODE = response code, ie, 200 or 404 +** BYTES = total bytes of data sent (other than headers) +** REFERER = the refering URL +** AGENT = the user agent +** +** for icecast, we add on extra field at the end, which will be +** ignored by normal log parsers +** +** TIME = seconds that the connection lasted +** +** this allows you to get bitrates (BYTES / TIME) +** and figure out exact times of connections +** +** it should be noted also that events are sent on client disconnect, +** so the DATE is the timestamp of disconnection. DATE - TIME is the +** time of connection. +*/ + +#define LOGGING_FORMAT_CLF "%d/%b/%Y:%H:%M:%S %z" + +void logging_access(client_t *client); +void restart_logging (void); + +#endif /* __LOGGING_H__ */ + + + + + + + + diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..b77c936d --- /dev/null +++ b/src/main.c @@ -0,0 +1,494 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "thread/thread.h" +#include "avl/avl.h" +#include "net/sock.h" +#include "net/resolver.h" +#include "httpp/httpp.h" + +#ifdef CHUID +#include +#include +#include +#include +#endif + +#include "cfgfile.h" +#include "sighandler.h" + +#include "global.h" +#include "os.h" +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "slave.h" +#include "stats.h" +#include "logging.h" +#include "xslt.h" +#include "fserve.h" +#include "yp.h" +#include "format.h" + +#include + +#ifdef _WIN32 +/* For getpid() */ +#include +#define snprintf _snprintf +#define getpid _getpid +#endif + +#undef CATMODULE +#define CATMODULE "main" + +static void _fatal_error(char *perr) +{ +#ifdef WIN32 + MessageBox(NULL, perr, "Error", MB_OK); +#else + fprintf(stdout, "%s\n", perr); +#endif +} + +static void _print_usage() +{ + printf(ICECAST_VERSION_STRING "\n\n"); + printf("usage: icecast [-h -b -v] -c \n"); + printf("options:\n"); + printf("\t-c \tSpecify configuration file\n"); + printf("\t-h\t\tDisplay usage\n"); + printf("\t-v\t\tDisplay version info\n"); + printf("\t-b\t\tRun icecast in the background\n"); + printf("\n"); +} + +static void _stop_logging(void) +{ + log_close(errorlog); + log_close(accesslog); +} + +static void _initialize_subsystems(void) +{ + log_initialize(); + thread_initialize(); + sock_initialize(); + resolver_initialize(); + config_initialize(); + connection_initialize(); + global_initialize(); + refbuf_initialize(); + xslt_initialize(); + format_initialise(); +} + +static void _shutdown_subsystems(void) +{ + fserve_shutdown(); + xslt_shutdown(); + refbuf_shutdown(); + slave_shutdown(); + yp_shutdown(); + stats_shutdown(); + + /* Now that these are done, we can stop the loggers. */ + _stop_logging(); + + global_shutdown(); + connection_shutdown(); + config_shutdown(); + resolver_shutdown(); + sock_shutdown(); + thread_shutdown(); + log_shutdown(); + + xmlCleanupParser(); +} + +static int _parse_config_opts(int argc, char **argv, char *filename, int size) +{ + int i = 1; + int processID = 0; + int config_ok = 0; + + + if (argc < 2) return -1; + + while (i < argc) { + if (strcmp(argv[i], "-b") == 0) { +#ifndef WIN32 + fprintf(stdout, "Starting icecast2\nDetaching from the console\n"); + if ((processID = (int)fork()) > 0) { + /* exit the parent */ + exit(0); + } + else if (processID < 0) { + fprintf(stderr, "FATAL: Unable to fork child!"); + exit(1); + } +#endif + } + if (strcmp(argv[i], "-v") == 0) { + fprintf(stdout, "%s\n", ICECAST_VERSION_STRING); + exit(0); + } + + if (strcmp(argv[i], "-c") == 0) { + if (i + 1 < argc) { + strncpy(filename, argv[i + 1], size-1); + filename[size-1] = 0; + config_ok = 1; + } else { + return -1; + } + } + i++; + } + + if(config_ok) + return 1; + else + return -1; +} + +static int _start_logging(void) +{ + char fn_error[FILENAME_MAX]; + char fn_access[FILENAME_MAX]; + char buf[1024]; + + ice_config_t *config = config_get_config_unlocked(); + + if(strcmp(config->error_log, "-")) { + snprintf(fn_error, FILENAME_MAX, "%s%s%s", config->log_dir, PATH_SEPARATOR, config->error_log); + errorlog = log_open(fn_error); + } else { + errorlog = log_open_file(stderr); + } + + if (errorlog < 0) + { + snprintf (buf, sizeof(buf), "FATAL: could not open error logging: %s", strerror(errno)); + _fatal_error (buf); + } + + if(strcmp(config->access_log, "-")) { + snprintf(fn_access, FILENAME_MAX, "%s%s%s", config->log_dir, PATH_SEPARATOR, config->access_log); + accesslog = log_open(fn_access); + } else { + accesslog = log_open_file(stderr); + } + + if (accesslog < 0) + { + snprintf (buf, sizeof(buf), "FATAL: could not open access logging: %s", + strerror(errno)); + _fatal_error(buf); + } + + log_set_level(errorlog, config->loglevel); + log_set_level(accesslog, 4); + + if (errorlog >= 0 && accesslog >= 0) return 1; + + return 0; +} + +static int _setup_sockets(void) +{ + ice_config_t *config; + int i = 0; + int ret = 0; + int successful = 0; + char pbuf[1024]; + + config = config_get_config_unlocked(); + + for(i = 0; i < MAX_LISTEN_SOCKETS; i++) { + if(config->listeners[i].port <= 0) + break; + + global.serversock[i] = sock_get_server_socket( + config->listeners[i].port, config->listeners[i].bind_address); + + if (global.serversock[i] == SOCK_ERROR) { + memset(pbuf, '\000', sizeof(pbuf)); + snprintf(pbuf, sizeof(pbuf)-1, + "Could not create listener socket on port %d", + config->listeners[i].port); + _fatal_error(pbuf); + return 0; + } + else { + ret = 1; + successful++; + } + } + + global.server_sockets = successful; + + return ret; +} + +static int _start_listening(void) +{ + int i; + for(i=0; i < global.server_sockets; i++) { + if (sock_listen(global.serversock[i], ICE_LISTEN_QUEUE) == SOCK_ERROR) + return 0; + + sock_set_blocking(global.serversock[i], SOCK_NONBLOCK); + } + + return 1; +} + +/* bind the socket and start listening */ +static int _server_proc_init(void) +{ + if (!_setup_sockets()) + return 0; + + if (!_start_listening()) { + _fatal_error("Failed trying to listen on server socket"); + return 0; + } + + return 1; +} + +/* this is the heart of the beast */ +static void _server_proc(void) +{ + int i; + + connection_accept_loop(); + + for(i=0; i < MAX_LISTEN_SOCKETS; i++) + sock_close(global.serversock[i]); +} + +/* chroot the process. Watch out - we need to do this before starting other + * threads. Change uid as well, after figuring out uid _first_ */ + +static void _ch_root_uid_setup(void) +{ + ice_config_t *conf = config_get_config_unlocked(); +#ifdef CHUID + struct passwd *user; + struct group *group; + uid_t uid=-1; + gid_t gid=-1; + + if(conf->chuid) + { + if(conf->user) { + user = getpwnam(conf->user); + if(user) + uid = user->pw_uid; + else + fprintf(stderr, "Couldn't find user \"%s\" in password file\n", conf->user); + } + if(conf->group) { + group = getgrnam(conf->group); + + if(group) + gid = group->gr_gid; + else + fprintf(stderr, "Couldn't find group \"%s\" in groups file\n", conf->group); + } + } +#endif + +#ifdef CHROOT + if (conf->chroot) + { + if(getuid()) /* root check */ + { + fprintf(stderr, "WARNING: Cannot change server root unless running as root.\n"); + return; + } + if(chroot(conf->base_dir)) + { + fprintf(stderr,"WARNING: Couldn't change server root: %s\n", strerror(errno)); + return; + } + else + fprintf(stdout, "Changed root successfully to \"%s\".\n", conf->base_dir); + + } +#endif +#ifdef CHUID + + if(conf->chuid) + { + if(getuid()) /* root check */ + { + fprintf(stderr, "WARNING: Can't change user id unless you are root.\n"); + return; + } + + if(gid != -1) { + if(!setgid(gid)) + fprintf(stdout, "Changed groupid to %i.\n", (int)gid); + else + fprintf(stdout, "Error changing groupid: %s.\n", strerror(errno)); + } + + if(uid != -1) { + if(!setuid(uid)) + fprintf(stdout, "Changed userid to %i.\n", (int)uid); + else + fprintf(stdout, "Error changing userid: %s.\n", strerror(errno)); + } + } +#endif +} + +int main(int argc, char **argv) +{ + int res, ret; + ice_config_t *config; + char *pidfile = NULL; + char filename[512]; + char pbuf[1024]; + + /* parse the '-c icecast.xml' option + ** only, so that we can read a configfile + */ + res = _parse_config_opts(argc, argv, filename, 512); + if (res == 1) { + /* startup all the modules */ + _initialize_subsystems(); + + /* parse the config file */ + config_get_config(); + ret = config_initial_parse_file(filename); + config_release_config(); + if (ret < 0) { + memset(pbuf, '\000', sizeof(pbuf)); + snprintf(pbuf, sizeof(pbuf)-1, + "FATAL: error parsing config file (%s)", filename); + _fatal_error(pbuf); + switch (ret) { + case CONFIG_EINSANE: + _fatal_error("filename was null of blank"); + break; + case CONFIG_ENOROOT: + _fatal_error("no root element found"); + break; + case CONFIG_EBADROOT: + _fatal_error("root element is not "); + break; + default: + _fatal_error("XML config parsing error"); + break; + } + _shutdown_subsystems(); + return 1; + } + } else if (res == -1) { + _print_usage(); + return 1; + } + + /* override config file options with commandline options */ + config_parse_cmdline(argc, argv); + + /* Bind socket, before we change userid */ + if(!_server_proc_init()) { + _fatal_error("Server startup failed. Exiting"); + _shutdown_subsystems(); + return 1; + } + + _ch_root_uid_setup(); /* Change user id and root if requested/possible */ + + stats_initialize(); /* We have to do this later on because of threading */ + fserve_initialize(); /* This too */ + +#ifdef CHUID + /* We'll only have getuid() if we also have setuid(), it's reasonable to + * assume */ + if(!getuid()) /* Running as root! Don't allow this */ + { + fprintf(stderr, "WARNING: You should not run icecast2 as root\n"); + fprintf(stderr, "Use the changeowner directive in the config file\n"); + _shutdown_subsystems(); + return 1; + } +#endif + + /* setup default signal handlers */ + sighandler_initialize(); + + if (!_start_logging()) { + _fatal_error("FATAL: Could not start logging"); + _shutdown_subsystems(); + return 1; + } + + config = config_get_config_unlocked(); + /* recreate the pid file */ + if (config->pidfile) + { + FILE *f; + pidfile = strdup (config->pidfile); + if (pidfile && (f = fopen (config->pidfile, "w")) != NULL) + { + fprintf (f, "%d\n", (int)getpid()); + fclose (f); + } + } + + INFO0 (ICECAST_VERSION_STRING " server started"); + + /* REM 3D Graphics */ + + /* let her rip */ + global.running = ICE_RUNNING; + + /* Startup yp thread */ + yp_initialize(); + + /* Do this after logging init */ + slave_initialize(); + + _server_proc(); + + INFO0("Shutting down"); + + _shutdown_subsystems(); + + if (pidfile) + { + remove (pidfile); + free (pidfile); + } + + return 0; +} + + diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 00000000..cf21fb56 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,277 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* + * md5.c + * + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* Modified for icecast by Mike Smith , mostly changing header + * and type definitions + */ + +#include "config.h" +#include "compat.h" +#include "md5.h" + +#include +#include +#include + +static void MD5Transform(uint32_t buf[4], uint32_t const in[HASH_LEN]); + +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do + { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } + while (--longs); +} + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned char const *buf, + unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; + + /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; + /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + if (t) + { + unsigned char *p = (unsigned char *) ctx->in + t; + t = 64 - t; + if (len < t) + { + memcpy(p, buf, len); + return; + } + + memcpy(p, buf, t); + byteReverse(ctx->in, HASH_LEN); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) + { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, HASH_LEN); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[HASH_LEN], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) + { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, HASH_LEN); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } + else + { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, HASH_LEN); + memset(ctx, 0, sizeof(ctx)); + /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +# define F1(x, y, z) (z ^ (x & (y ^ z))) +# define F2(x, y, z) F1(z, x, y) +# define F3(x, y, z) (x ^ y ^ z) +# define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +# define MD5STEP(f, w, x, y, z, data, s) do { w += f(x, y, z) + data; w = (w<>(32-s)); w += x; }while (0) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32_t buf[4], uint32_t const in[HASH_LEN]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 00000000..2da4a78f --- /dev/null +++ b/src/md5.h @@ -0,0 +1,36 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __MD5_H__ +#define __MD5_H__ + +#include "config.h" +#include "compat.h" + +#define HASH_LEN 16 + +struct MD5Context +{ + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, + unsigned len); +void MD5Final(unsigned char digest[HASH_LEN], struct MD5Context *context); + + +#endif + + diff --git a/src/net/.cvsignore b/src/net/.cvsignore deleted file mode 100644 index 8550dbfc..00000000 --- a/src/net/.cvsignore +++ /dev/null @@ -1,6 +0,0 @@ -Makefile -Makefile.in -.deps -.libs -*.la -*.lo diff --git a/src/net/BUILDING b/src/net/BUILDING deleted file mode 100644 index b63563a4..00000000 --- a/src/net/BUILDING +++ /dev/null @@ -1,3 +0,0 @@ -defines that affect compilation - -none currently diff --git a/src/net/COPYING b/src/net/COPYING deleted file mode 100644 index 92b8903f..00000000 --- a/src/net/COPYING +++ /dev/null @@ -1,481 +0,0 @@ - GNU LIBRARY GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 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. - -[This is the first released version of the library GPL. It is - numbered 2 because it goes with version 2 of the ordinary GPL.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Library General Public License, applies to some -specially designated Free Software Foundation software, and to any -other libraries whose authors decide to use it. You can use it for -your libraries, 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 library, or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link a program with the library, you must provide -complete object files to the recipients so that they can relink them -with the library, after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - Our method of protecting your rights has two steps: (1) copyright -the library, and (2) offer you this license which gives you legal -permission to copy, distribute and/or modify the library. - - Also, for each distributor's protection, we want to make certain -that everyone understands that there is no warranty for this free -library. If the library is modified by someone else and passed on, we -want its recipients to know that what they have is not the original -version, 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 companies distributing free -software will individually obtain patent licenses, thus in effect -transforming the program into proprietary software. To prevent this, -we have made it clear that any patent must be licensed for everyone's -free use or not licensed at all. - - Most GNU software, including some libraries, is covered by the ordinary -GNU General Public License, which was designed for utility programs. This -license, the GNU Library General Public License, applies to certain -designated libraries. This license is quite different from the ordinary -one; be sure to read it in full, and don't assume that anything in it is -the same as in the ordinary license. - - The reason we have a separate public license for some libraries is that -they blur the distinction we usually make between modifying or adding to a -program and simply using it. Linking a program with a library, without -changing the library, is in some sense simply using the library, and is -analogous to running a utility program or application program. However, in -a textual and legal sense, the linked executable is a combined work, a -derivative of the original library, and the ordinary General Public License -treats it as such. - - Because of this blurred distinction, using the ordinary General -Public License for libraries did not effectively promote software -sharing, because most developers did not use the libraries. We -concluded that weaker conditions might promote sharing better. - - However, unrestricted linking of non-free programs would deprive the -users of those programs of all benefit from the free status of the -libraries themselves. This Library General Public License is intended to -permit developers of non-free programs to use free libraries, while -preserving your freedom as a user of such programs to change the free -libraries that are incorporated in them. (We have not seen how to achieve -this as regards changes in header files, but we have achieved it as regards -changes in the actual functions of the Library.) The hope is that this -will lead to faster development of free libraries. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, while the latter only -works together with the library. - - Note that it is possible for a library to be covered by the ordinary -General Public License rather than by this special one. - - GNU LIBRARY GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library which -contains a notice placed by the copyright holder or other authorized -party saying it may be distributed under the terms of this Library -General Public License (also called "this License"). Each licensee is -addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also compile or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - c) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - d) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the 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. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Library General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - 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. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/src/net/Makefile.am b/src/net/Makefile.am deleted file mode 100644 index d2658f5e..00000000 --- a/src/net/Makefile.am +++ /dev/null @@ -1,21 +0,0 @@ -## Process this with automake to create Makefile.in - -AUTOMAKE_OPTIONS = foreign - -EXTRA_DIST = BUILDING COPYING README TODO test_resolver.c - -noinst_LTLIBRARIES = libicenet.la -noinst_HEADERS = resolver.h sock.h - -libicenet_la_SOURCES = sock.c resolver.c -libicenet_la_CFLAGS = @XIPH_CFLAGS@ -AM_CPPFLAGS = @XIPH_CPPFLAGS@ - -INCLUDES = -I$(srcdir)/.. - -debug: - $(MAKE) all CFLAGS="@DEBUG@" - -profile: - $(MAKE) all CFLAGS="@PROFILE@ - diff --git a/src/net/README b/src/net/README deleted file mode 100644 index d0d2d00a..00000000 --- a/src/net/README +++ /dev/null @@ -1,10 +0,0 @@ -This is a name resolving library that's threadsafe. - -Right now it only implements this with mutexes, but we should extend it to use gethostbyXXXX_r() -if it's available. - -It shoudl work on win32, but i'm probably forgetting a headerfile. - -API is basically not going to change. Please consult with the rest of the team before changing the interface. - -jack. \ No newline at end of file diff --git a/src/net/TODO b/src/net/TODO deleted file mode 100644 index 3357459c..00000000 --- a/src/net/TODO +++ /dev/null @@ -1 +0,0 @@ -- add getXbyY_r function support diff --git a/src/net/resolver.c b/src/net/resolver.c deleted file mode 100644 index 68ce5cad..00000000 --- a/src/net/resolver.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - * resolver.c - name resolver library - * - * Copyright (C) 1999 the icecast team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifdef HAVE_CONFIG_H - #include -#endif - -#include -#include -#include -#include - -#ifndef _WIN32 -#include -#include -#include -#include -#else -#include -#endif - -#ifndef NO_THREAD -#include -#else -#define thread_mutex_create(x) do{}while(0) -#define thread_mutex_destroy(x) do{}while(0) -#define thread_mutex_lock(x) do{}while(0) -#define thread_mutex_unlock(x) do{}while(0) -#endif - -#include "resolver.h" -#include "sock.h" - -/* internal function */ - -static int _isip(const char *what); - -/* internal data */ - -#ifndef NO_THREAD -static mutex_t _resolver_mutex; -#endif -static int _initialized = 0; - -#ifdef HAVE_INET_PTON -static int _isip(const char *what) -{ - union { - struct in_addr v4addr; - struct in6_addr v6addr; - } addr_u; - - if (inet_pton(AF_INET, what, &addr_u.v4addr) <= 0) - return inet_pton(AF_INET6, what, &addr_u.v6addr) > 0 ? 1 : 0; - - return 1; -} - -#else -static int _isip(const char *what) -{ - struct in_addr inp; - - return inet_aton(what, &inp); -} -#endif - - -#if defined (HAVE_GETNAMEINFO) && defined (HAVE_GETADDRINFO) -char *resolver_getname(const char *ip, char *buff, int len) -{ - struct addrinfo *head = NULL, hints; - char *ret = NULL; - - if (!_isip(ip)) { - strncpy(buff, ip, len); - buff [len-1] = '\0'; - return buff; - } - - memset (&hints, 0, sizeof (hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_CANONNAME; - if (getaddrinfo (ip, NULL, &hints, &head)) - return NULL; - - if (head) - { - if (getnameinfo(head->ai_addr, head->ai_addrlen, buff, len, NULL, - 0, NI_NAMEREQD) == 0) - ret = buff; - - freeaddrinfo (head); - } - - return ret; -} - - -char *resolver_getip(const char *name, char *buff, int len) -{ - struct addrinfo *head, hints; - char *ret = NULL; - - if (_isip(name)) { - strncpy(buff, name, len); - buff [len-1] = '\0'; - return buff; - } - - memset (&hints, 0, sizeof (hints)); - hints . ai_family = AF_UNSPEC; - hints . ai_socktype = SOCK_STREAM; - if (getaddrinfo (name, NULL, &hints, &head)) - return NULL; - - if (head) - { - if (getnameinfo(head->ai_addr, head->ai_addrlen, buff, len, NULL, - 0, NI_NUMERICHOST) == 0) - ret = buff; - freeaddrinfo (head); - } - - return ret; -} - -#else - -char *resolver_getname(const char *ip, char *buff, int len) -{ - struct hostent *host; - char *ret = NULL; - struct in_addr addr; - - if (! _isip(ip)) - { - strncpy(buff, ip, len); - buff [len-1] = '\0'; - return buff; - } - - thread_mutex_lock(&_resolver_mutex); - if (inet_aton (ip, &addr)) { - if ((host=gethostbyaddr (&addr, sizeof (struct in_addr), AF_INET))) - { - ret = strncpy (buff, host->h_name, len); - buff [len-1] = '\0'; - } - } - - thread_mutex_unlock(&_resolver_mutex); - return ret; -} - -char *resolver_getip(const char *name, char *buff, int len) -{ - struct hostent *host; - char *ret = NULL; - - if (_isip(name)) - { - strncpy(buff, name, len); - buff [len-1] = '\0'; - return buff; - } - thread_mutex_lock(&_resolver_mutex); - host = gethostbyname(name); - if (host) - { - char * temp = inet_ntoa(*(struct in_addr *)host->h_addr); - ret = strncpy(buff, temp, len); - buff [len-1] = '\0'; - } - thread_mutex_unlock(&_resolver_mutex); - - return ret; -} -#endif - - -void resolver_initialize() -{ - /* initialize the lib if we havne't done so already */ - - if (!_initialized) - { - _initialized = 1; - thread_mutex_create ("resolver", &_resolver_mutex); - - /* keep dns connects (TCP) open */ -#ifdef HAVE_SETHOSTENT - sethostent(1); -#endif - } -} - -void resolver_shutdown(void) -{ - if (_initialized) - { - thread_mutex_destroy(&_resolver_mutex); - _initialized = 0; -#ifdef HAVE_ENDHOSTENT - endhostent(); -#endif - } -} - diff --git a/src/net/resolver.h b/src/net/resolver.h deleted file mode 100644 index 6ed1a219..00000000 --- a/src/net/resolver.h +++ /dev/null @@ -1,41 +0,0 @@ -/* -** resolver.h -** -** name resolver library header -** -*/ - -#ifndef __RESOLVER_H -#define __RESOLVER_H - - -/* -** resolver_lookup -** -** resolves a hosts name from it's ip address -** or -** resolves an ip address from it's host name -** -** returns a pointer to buff, or NULL if an error occured -** -*/ - -#ifdef _mangle -# define resolver_initialize _mangle(resolver_initialize) -# define resolver_shutdown _mangle(resolver_shutdown) -# define resolver_getname _mangle(resolver_getname) -# define resolver_getip _mangle(resolver_getip) -#endif - -void resolver_initialize(void); -void resolver_shutdown(void); - -char *resolver_getname(const char *ip, char *buff, int len); -char *resolver_getip(const char *name, char *buff, int len); - -#endif - - - - - diff --git a/src/net/sock.c b/src/net/sock.c deleted file mode 100644 index bba95649..00000000 --- a/src/net/sock.c +++ /dev/null @@ -1,816 +0,0 @@ -/* -*- c-basic-offset: 4; -*- */ -/* sock.c: General Socket Functions - * - * Copyright (c) 1999 the icecast team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#ifndef _WIN32 -#include -#include -#include -#include -#include -#include -#include -#else -#include -#define vsnprintf _vsnprintf -#define EINPROGRESS WSAEINPROGRESS -#define ENOTSOCK WSAENOTSOCK -#define EWOULDBLOCK WSAEWOULDBLOCK -#define EALREADY WSAEALREADY -#define socklen_t int -#ifndef __MINGW32__ -#define va_copy(ap1, ap2) memcpy(&ap1, &ap2, sizeof(va_list)) -#endif -#endif - -#include "sock.h" -#include "resolver.h" - -/* sock_initialize -** -** initializes the socket library. you must call this -** before using the library! -*/ -void sock_initialize(void) -{ -#ifdef _WIN32 - WSADATA wsad; - WSAStartup(0x0101, &wsad); -#endif - - resolver_initialize(); -} - -/* sock_shutdown -** -** shutdown the socket library. remember to call this when you're -** through using the lib -*/ -void sock_shutdown(void) -{ -#ifdef _WIN32 - WSACleanup(); -#endif - - resolver_shutdown(); -} - -/* sock_get_localip -** -** gets the local ip address for the machine -** the ip it returns *should* be on the internet. -** in any case, it's as close as we can hope to get -** unless someone has better ideas on how to do this -*/ -char *sock_get_localip(char *buff, int len) -{ - char temp[1024]; - - if (gethostname(temp, sizeof(temp)) != 0) - return NULL; - - if (resolver_getip(temp, buff, len)) - return buff; - - return NULL; -} - -/* sock_error -** -** returns the last socket error -*/ -int sock_error(void) -{ -#ifdef _WIN32 - return WSAGetLastError(); -#else - return errno; -#endif -} - -void sock_set_error(int val) -{ -#ifdef _WIN32 - WSASetLastError (val); -#else - errno = val; -#endif -} - -/* sock_recoverable -** -** determines if the socket error is recoverable -** in terms of non blocking sockets -*/ -int sock_recoverable(int error) -{ - switch (error) - { - case 0: - case EAGAIN: - case EINTR: - case EINPROGRESS: -#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN - case EWOULDBLOCK: -#endif -#ifdef ERESTART - case ERESTART: -#endif - return 1; - default: - return 0; - } -} - -int sock_stalled (int error) -{ - switch (error) - { - case EAGAIN: - case EINPROGRESS: - case EALREADY: -#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN - case EWOULDBLOCK: -#endif -#ifdef ERESTART - case ERESTART: -#endif - return 1; - default: - return 0; - } -} - - -static int sock_connect_pending (int error) -{ - return error == EINPROGRESS || error == EALREADY; -} - -/* sock_valid_socket -** -** determines if a sock_t represents a valid socket -*/ -int sock_valid_socket(sock_t sock) -{ - int ret; - int optval; - socklen_t optlen; - - optlen = sizeof(int); - /* apparently on windows getsockopt.optval is a char * */ - ret = getsockopt(sock, SOL_SOCKET, SO_TYPE, (void*) &optval, &optlen); - - return (ret == 0); -} - -/* inet_aton -** -** turns an ascii ip address into a binary representation -*/ -#ifdef _WIN32 -int inet_aton(const char *s, struct in_addr *a) -{ - int lsb, b2, b3, msb; - - if (sscanf(s, "%d.%d.%d.%d", &lsb, &b2, &b3, &msb) < 4) { - return 0; - } - - a->s_addr = inet_addr(s); - - return (a->s_addr != INADDR_NONE); -} -#endif /* _WIN32 */ - -/* sock_set_blocking -** -** set the sock blocking or nonblocking -** SOCK_BLOCK for blocking -** SOCK_NONBLOCK for nonblocking -*/ -int sock_set_blocking(sock_t sock, const int block) -{ -#ifdef _WIN32 -#ifdef __MINGW32__ - u_long varblock = block; -#else - int varblock = block; -#endif -#endif - - if ((!sock_valid_socket(sock)) || (block < 0) || (block > 1)) - return SOCK_ERROR; - -#ifdef _WIN32 - return ioctlsocket(sock, FIONBIO, &varblock); -#else - return fcntl(sock, F_SETFL, (block == SOCK_BLOCK) ? 0 : O_NONBLOCK); -#endif -} - -int sock_set_nolinger(sock_t sock) -{ - struct linger lin = { 0, 0 }; - return setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&lin, - sizeof(struct linger)); -} - -int sock_set_nodelay(sock_t sock) -{ - int nodelay = 1; - - return setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&nodelay, - sizeof(int)); -} - -int sock_set_keepalive(sock_t sock) -{ - int keepalive = 1; - return setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, - sizeof(int)); -} - -/* sock_close -** -** close the socket -*/ -int sock_close(sock_t sock) -{ -#ifdef _WIN32 - return closesocket(sock); -#else - return close(sock); -#endif -} - -/* sock_writev - * - * write multiple buffers at once, return bytes actually written - */ -#ifdef HAVE_WRITEV - -ssize_t sock_writev (int sock, const struct iovec *iov, const size_t count) -{ - return writev (sock, iov, count); -} - -#else - -ssize_t sock_writev (int sock, const struct iovec *iov, const size_t count) -{ - int i = count, accum = 0, ret; - const struct iovec *v = iov; - - while (i) - { - if (v->iov_base && v->iov_len) - { - ret = sock_write_bytes (sock, v->iov_base, v->iov_len); - if (ret == -1 && accum==0) - return -1; - if (ret == -1) - ret = 0; - accum += ret; - if (ret < (int)v->iov_len) - break; - } - v++; - i--; - } - return accum; -} - -#endif - -/* sock_write_bytes -** -** write bytes to the socket -** this function will _NOT_ block -*/ -int sock_write_bytes(sock_t sock, const void *buff, const size_t len) -{ - /* sanity check */ - if (!buff) { - return SOCK_ERROR; - } else if (len <= 0) { - return SOCK_ERROR; - } /*else if (!sock_valid_socket(sock)) { - return SOCK_ERROR; - } */ - - return send(sock, buff, len, 0); -} - -/* sock_write_string -** -** writes a string to a socket -** This function must only be called with a blocking socket. -*/ -int sock_write_string(sock_t sock, const char *buff) -{ - return (sock_write_bytes(sock, buff, strlen(buff)) > 0); -} - -/* sock_write -** -** write a formatted string to the socket -** this function must only be called with a blocking socket. -** will truncate the string if it's greater than 1024 chars. -*/ -int sock_write(sock_t sock, const char *fmt, ...) -{ - int rc; - va_list ap; - - va_start (ap, fmt); - rc = sock_write_fmt (sock, fmt, ap); - va_end (ap); - - return rc; -} - -int sock_write_fmt(sock_t sock, const char *fmt, va_list ap) -{ - char buffer [1024], *buff = buffer; - int len; - int rc = SOCK_ERROR; - va_list ap_retry; - - va_copy (ap_retry, ap); - - len = vsnprintf (buff, sizeof (buffer), fmt, ap); - - if (len > 0) - { - if ((size_t)len < sizeof (buffer)) /* common case */ - rc = sock_write_bytes(sock, buff, (size_t)len); - else - { - /* truncated */ - buff = malloc (++len); - if (buff) - { - len = vsnprintf (buff, len, fmt, ap_retry); - if (len > 0) - rc = sock_write_bytes (sock, buff, len); - free (buff); - } - } - } - va_end (ap_retry); - - return rc; -} - - -/* function to check if there are any pending reads on - * the stated socket. timeout in ms, 0 for immediate - * return - */ -int sock_read_pending(sock_t sock, unsigned timeout) -{ - struct timeval tv; - fd_set rfds; - - tv.tv_sec = timeout/1000; - tv.tv_usec = (timeout%1000)*1000; - - FD_ZERO(&rfds); - FD_SET(sock, &rfds); - - switch (select(sock + 1, &rfds, NULL, NULL, &tv)) - { - case 0: return SOCK_TIMEOUT; - default: return 1; - case -1: return SOCK_ERROR; - } -} - - -int sock_read_bytes(sock_t sock, char *buff, size_t len) -{ - - /*if (!sock_valid_socket(sock)) return 0; */ - if (!buff) return 0; - if (len <= 0) return 0; - - return recv(sock, buff, len, 0); -} - -/* sock_read_line -** -** Read one line of at max len bytes from sock into buff. -** If ok, return 1 and nullterminate buff. Otherwize return 0. -** Terminating \n is not put into the buffer. -** -** this function will probably not work on sockets in nonblocking mode -*/ -int sock_read_line(sock_t sock, char *buff, const int len) -{ - char c = '\0'; - int read_bytes, pos; - - /*if (!sock_valid_socket(sock)) { - return 0; - } else*/ if (!buff) { - return 0; - } else if (len <= 0) { - return 0; - } - - pos = 0; - read_bytes = recv(sock, &c, 1, 0); - - if (read_bytes < 0) { - return 0; - } - - while ((c != '\n') && (pos < len) && (read_bytes == 1)) { - if (c != '\r') - buff[pos++] = c; - read_bytes = recv(sock, &c, 1, 0); - } - - if (read_bytes == 1) { - buff[pos] = '\0'; - return 1; - } else { - return 0; - } -} - -/* see if a connection has been established - * return SOCK_TIMEOUT for timeout - * return SOCK_ERROR for failure - * return 0 for try again, interrupted - * return 1 for ok - */ -int sock_connected (int sock, unsigned timeout) -{ - fd_set wfds; - int val = SOCK_ERROR; - socklen_t size = sizeof val; - struct timeval tv, *timeval = NULL; - - if (timeout) - { - tv.tv_sec = timeout; - tv.tv_usec = 0; - timeval = &tv; - } - - FD_ZERO(&wfds); - FD_SET(sock, &wfds); - - switch (select(sock + 1, NULL, &wfds, NULL, timeval)) - { - case 0: - return SOCK_TIMEOUT; - default: - /* on windows getsockopt.val is defined as char* */ - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*) &val, &size) == 0) - { - if (val == 0) - return 1; - sock_set_error (val); - } - /* fall through */ - case -1: - if (sock_recoverable (sock_error())) - return 0; - return SOCK_ERROR; - } -} - -#ifdef HAVE_GETADDRINFO - -int sock_connect_non_blocking (const char *hostname, const unsigned port) -{ - int sock = SOCK_ERROR; - struct addrinfo *ai, *head, hints; - char service[8]; - - memset (&hints, 0, sizeof (hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - snprintf (service, sizeof (service), "%u", port); - - if (getaddrinfo (hostname, service, &hints, &head)) - return SOCK_ERROR; - - ai = head; - while (ai) - { - if ((sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol)) - > -1) - { - sock_set_blocking (sock, SOCK_NONBLOCK); - if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 && - !sock_connect_pending(sock_error())) - { - sock_close (sock); - sock = SOCK_ERROR; - } - else - break; - } - ai = ai->ai_next; - } - if (head) freeaddrinfo (head); - - return sock; -} - - -sock_t sock_connect_wto(const char *hostname, const int port, const int timeout) -{ - int sock = SOCK_ERROR; - struct addrinfo *ai, *head, hints; - char service[8]; - - memset (&hints, 0, sizeof (hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - snprintf (service, sizeof (service), "%u", port); - - if (getaddrinfo (hostname, service, &hints, &head)) - return SOCK_ERROR; - - ai = head; - while (ai) - { - if ((sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol)) >= 0) - { - if (timeout) - sock_set_blocking (sock, SOCK_NONBLOCK); - - if (connect (sock, ai->ai_addr, ai->ai_addrlen) == 0) - break; - - /* loop as the connect maybe async */ - while (sock != SOCK_ERROR) - { - if (sock_recoverable (sock_error())) - { - if (sock_connected (sock, timeout) > 0) - { - if (timeout) /* really wants to reset to what it was before */ - sock_set_blocking(sock, SOCK_BLOCK); - break; - } - continue; - } - sock_close (sock); - sock = SOCK_ERROR; - } - if (sock != SOCK_ERROR) - break; - } - ai = ai->ai_next; - } - if (head) - freeaddrinfo (head); - - return sock; -} - -#else - -int sock_try_connection (int sock, const char *hostname, const unsigned port) -{ - struct sockaddr_in sin, server; - char ip[MAX_ADDR_LEN]; - - if (!hostname || !hostname[0] || port == 0) - return -1; - - memset(&sin, 0, sizeof(struct sockaddr_in)); - memset(&server, 0, sizeof(struct sockaddr_in)); - - if (!resolver_getip(hostname, ip, MAX_ADDR_LEN)) - { - sock_close (sock); - return -1; - } - - if (inet_aton(ip, (struct in_addr *)&sin.sin_addr) == 0) - { - sock_close(sock); - return -1; - } - - memcpy(&server.sin_addr, &sin.sin_addr, sizeof(struct sockaddr_in)); - - server.sin_family = AF_INET; - server.sin_port = htons(port); - - return connect(sock, (struct sockaddr *)&server, sizeof(server)); -} - -int sock_connect_non_blocking (const char *hostname, const unsigned port) -{ - int sock; - - sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == -1) - return -1; - - sock_set_blocking (sock, SOCK_NONBLOCK); - sock_try_connection (sock, hostname, port); - - return sock; -} - -sock_t sock_connect_wto(const char *hostname, const int port, const int timeout) -{ - int sock; - - sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == -1) - return -1; - - if (timeout) - { - sock_set_blocking (sock, SOCK_NONBLOCK); - if (sock_try_connection (sock, hostname, port) < 0) - { - int ret = sock_connected (sock, timeout); - if (ret <= 0) - { - sock_close (sock); - return SOCK_ERROR; - } - } - sock_set_blocking(sock, SOCK_BLOCK); - } - else - { - if (sock_try_connection (sock, hostname, port) < 0) - { - sock_close (sock); - sock = SOCK_ERROR; - } - } - return sock; -} -#endif - - -/* sock_get_server_socket -** -** create a socket for incoming requests on a specified port and -** interface. if interface is null, listen on all interfaces. -** returns the socket, or SOCK_ERROR on failure -*/ -sock_t sock_get_server_socket(const int port, char *sinterface) -{ -#ifdef HAVE_INET_PTON - struct sockaddr_storage sa; -#else - struct sockaddr_in sa; -#endif - int family, len, error, opt; - sock_t sock; - char ip[MAX_ADDR_LEN]; - - if (port < 0) - return SOCK_ERROR; - - /* defaults */ - memset(&sa, 0, sizeof(sa)); - family = AF_INET; - len = sizeof(struct sockaddr_in); - - /* set the interface to bind to if specified */ - if (sinterface != NULL) { - if (!resolver_getip(sinterface, ip, sizeof (ip))) - return SOCK_ERROR; - -#ifdef HAVE_INET_PTON - if (inet_pton(AF_INET, ip, &((struct sockaddr_in*)&sa)->sin_addr) > 0) { - ((struct sockaddr_in*)&sa)->sin_family = AF_INET; - ((struct sockaddr_in*)&sa)->sin_port = htons(port); - } else if (inet_pton(AF_INET6, ip, - &((struct sockaddr_in6*)&sa)->sin6_addr) > 0) { - family = AF_INET6; - len = sizeof (struct sockaddr_in6); - ((struct sockaddr_in6*)&sa)->sin6_family = AF_INET6; - ((struct sockaddr_in6*)&sa)->sin6_port = htons(port); - } else { - return SOCK_ERROR; - } -#else - if (!inet_aton(ip, &sa.sin_addr)) { - return SOCK_ERROR; - } else { - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - } -#endif - } else { - ((struct sockaddr_in*)&sa)->sin_addr.s_addr = INADDR_ANY; - ((struct sockaddr_in*)&sa)->sin_family = AF_INET; - ((struct sockaddr_in*)&sa)->sin_port = htons(port); - } - - /* get a socket */ - sock = socket(family, SOCK_STREAM, 0); - if (sock == -1) - return SOCK_ERROR; - - /* reuse it if we can */ - opt = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(int)); - - /* bind socket to port */ - error = bind(sock, (struct sockaddr *)&sa, len); - if (error == -1) - return SOCK_ERROR; - - return sock; -} - -int sock_listen(sock_t serversock, int backlog) -{ - if (!sock_valid_socket(serversock)) - return 0; - - if (backlog <= 0) - backlog = 10; - - return (listen(serversock, backlog) == 0); -} - -int sock_accept(sock_t serversock, char *ip, int len) -{ -#ifdef HAVE_INET_PTON - struct sockaddr_storage sa; -#else - struct sockaddr_in sa; -#endif - int ret; - socklen_t slen; - - if (!sock_valid_socket(serversock)) - return SOCK_ERROR; - - slen = sizeof(sa); - ret = accept(serversock, (struct sockaddr *)&sa, &slen); - - if (ret >= 0 && ip != NULL) { -#ifdef HAVE_INET_PTON - if(((struct sockaddr_in *)&sa)->sin_family == AF_INET) - inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr, - ip, len); - else if(((struct sockaddr_in6 *)&sa)->sin6_family == AF_INET6) - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&sa)->sin6_addr, - ip, len); - else { - strncpy(ip, "ERROR", len-1); - ip[len-1] = 0; - } -#else - /* inet_ntoa is not reentrant, we should protect this */ - strncpy(ip, inet_ntoa(sa.sin_addr), len); -#endif - sock_set_nolinger(ret); - sock_set_keepalive(ret); - } - - return ret; -} - diff --git a/src/net/sock.h b/src/net/sock.h deleted file mode 100644 index 53b5eb60..00000000 --- a/src/net/sock.h +++ /dev/null @@ -1,142 +0,0 @@ -/* sock.h - * - General Socket Function Headers - * - * Copyright (c) 1999 the icecast team - * - * 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. - * - */ - -#ifndef __SOCK_H -#define __SOCK_H - -#include - -#ifdef HAVE_WINSOCK2_H -#include -#endif - -#ifdef HAVE_UNISTD_H -#include -#elif _WIN32 -#include -#endif - -#ifdef HAVE_SYS_UIO_H -#include -#else -#ifndef _SYS_UIO_H -struct iovec -{ - void *iov_base; - size_t iov_len; -}; -#endif -#endif - -#if !defined(HAVE_INET_ATON) && defined(HAVE_INET_PTON) -#define inet_aton(a,b) inet_pton(AF_INET, (a), (b)) -#endif - -#ifdef INET6_ADDRSTRLEN -#define MAX_ADDR_LEN INET6_ADDRSTRLEN -#else -#define MAX_ADDR_LEN 46 -#endif - -typedef int sock_t; - -/* The following values are based on unix avoiding errno value clashes */ -#define SOCK_SUCCESS 0 -#define SOCK_ERROR -1 -#define SOCK_TIMEOUT -2 - -#define SOCK_BLOCK 0 -#define SOCK_NONBLOCK 1 - -/* sock connect macro */ -#define sock_connect(h, p) sock_connect_wto(h, p, 0) - -#ifdef _mangle -# define sock_initialize _mangle(sock_initialize) -# define sock_shutdown _mangle(sock_shutdown) -# define sock_get_localip _mangle(sock_get_localip) -# define sock_error _mangle(sock_error) -# define sock_set_error _mangle(sock_set_error) -# define sock_recoverable _mangle(sock_recoverable) -# define sock_stalled _mangle(sock_stalled) -# define sock_valid_socket _mangle(sock_valid_socket) -# define sock_set_blocking _mangle(sock_set_blocking) -# define sock_set_nolinger _mangle(sock_set_nolinger) -# define sock_set_nodelay _mangle(sock_set_nodelay) -# define sock_set_keepalive _mangle(sock_set_keepalive) -# define sock_close _mangle(sock_close) -# define sock_connect_wto _mangle(sock_connect_wto) -# define sock_connect_non_blocking _mangle(sock_connect_non_blocking) -# define sock_connected _mangle(sock_connected) -# define sock_write_bytes _mangle(sock_write_bytes) -# define sock_write _mangle(sock_write) -# define sock_write_fmt _mangle(sock_write_fmt) -# define sock_write_string _mangle(sock_write_string) -# define sock_writev _mangle(sock_writev) -# define sock_read_bytes _mangle(sock_read_bytes) -# define sock_read_line _mangle(sock_read_line) -# define sock_get_server_socket _mangle(sock_get_server_socket) -# define sock_listen _mangle(sock_listen) -# define sock_accept _mangle(sock_accept) -#endif - -/* Misc socket functions */ -void sock_initialize(void); -void sock_shutdown(void); -char *sock_get_localip(char *buff, int len); -int sock_error(void); -int sock_recoverable(int error); -int sock_stalled(int error); -int sock_valid_socket(sock_t sock); -int sock_set_blocking(sock_t sock, const int block); -int sock_set_nolinger(sock_t sock); -int sock_set_keepalive(sock_t sock); -int sock_set_nodelay(sock_t sock); -void sock_set_error(int val); -int sock_close(sock_t sock); - -/* Connection related socket functions */ -sock_t sock_connect_wto(const char *hostname, const int port, const int timeout); -int sock_connect_non_blocking(const char *host, const unsigned port); -int sock_connected(int sock, unsigned timeout); - -/* Socket write functions */ -int sock_write_bytes(sock_t sock, const void *buff, const size_t len); -int sock_write(sock_t sock, const char *fmt, ...); -int sock_write_fmt(sock_t sock, const char *fmt, va_list ap); -int sock_write_string(sock_t sock, const char *buff); -ssize_t sock_writev (int sock, const struct iovec *iov, const size_t count); - - -/* Socket read functions */ -int sock_read_bytes(sock_t sock, char *buff, size_t len); -int sock_read_line(sock_t sock, char *string, const int len); - -/* server socket functions */ -sock_t sock_get_server_socket(const int port, char *sinterface); -int sock_listen(sock_t serversock, int backlog); -int sock_accept(sock_t serversock, char *ip, int len); - -#ifdef _WIN32 -int inet_aton(const char *s, struct in_addr *a); -#endif - -#endif /* __SOCK_H */ diff --git a/src/net/test_resolver.c b/src/net/test_resolver.c deleted file mode 100644 index cac1c8e2..00000000 --- a/src/net/test_resolver.c +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include - -#include "resolver.h" - -int main() -{ - char buff[1024]; - - resolver_initialize(); - - printf("I got %s, when looking up %s.\n", resolver_getip("bach.greenwitch.com", buff, 1024), "bach.greenwitch.com"); - printf("I got %s, when looking up %s.\n", resolver_getname("207.181.249.14", buff, 1024), "207.181.249.14"); - - return 0; -} - diff --git a/src/os.h b/src/os.h new file mode 100644 index 00000000..95f0bdad --- /dev/null +++ b/src/os.h @@ -0,0 +1,30 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __OS_H__ +#define __OS_H__ + +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifdef _WIN32 +#define PATH_SEPARATOR "\\" +#define size_t int +#define ssize_t int +#else +#define PATH_SEPARATOR "/" +#endif + +#endif /* __OS_H__ */ diff --git a/src/refbuf.c b/src/refbuf.c new file mode 100644 index 00000000..3abce6e7 --- /dev/null +++ b/src/refbuf.c @@ -0,0 +1,80 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* refbuf.c +** +** reference counting buffer implementation +** +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "refbuf.h" + +void refbuf_initialize(void) +{ +} + +void refbuf_shutdown(void) +{ +} + + +void refbuf_free (refbuf_t *refbuf) +{ + free (refbuf->data); + free (refbuf); +} + + +refbuf_t *refbuf_new(unsigned long size) +{ + refbuf_t *refbuf; + + refbuf = malloc (sizeof(refbuf_t)); + if (refbuf) + { + refbuf->data = NULL; + if (size && (refbuf->data = malloc (size)) == NULL) + { + free (refbuf); + return NULL; + } + refbuf->len = 0; + refbuf->sync_point = 0; + refbuf->allocated = size; + refbuf->next = NULL; + refbuf->associated = NULL; + refbuf->refbuf_associated_release = refbuf_free; + refbuf->refbuf_release = refbuf_free; + } + + return refbuf; +} + + +void refbuf_release(refbuf_t *refbuf) +{ + while (refbuf->associated) + { + refbuf_t *ref = refbuf->associated; + refbuf->associated = ref->next; + refbuf->refbuf_associated_release (ref); + } + refbuf->refbuf_release (refbuf); +} + diff --git a/src/refbuf.h b/src/refbuf.h new file mode 100644 index 00000000..db0dbcd3 --- /dev/null +++ b/src/refbuf.h @@ -0,0 +1,51 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* refbuf.h +** +** reference counting data buffer +** +*/ +#ifndef __REFBUF_H__ +#define __REFBUF_H__ + +typedef struct _refbuf_tag +{ + char *data; + unsigned len; + unsigned allocated; + int idx; + int sync_point; + struct _refbuf_tag *associated; + void (*refbuf_associated_release)(struct _refbuf_tag *); + void (*refbuf_release)(struct _refbuf_tag *); + + struct _refbuf_tag *next; +} refbuf_t; + +void refbuf_initialize(void); +void refbuf_shutdown(void); + +void refbuf_free (refbuf_t *refbuf); +refbuf_t *refbuf_new(unsigned long size); +void refbuf_release(refbuf_t *self); + + +#endif /* __REFBUF_H__ */ + + + + + + + + diff --git a/src/sighandler.c b/src/sighandler.c new file mode 100644 index 00000000..59349373 --- /dev/null +++ b/src/sighandler.c @@ -0,0 +1,70 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "thread/thread.h" +#include "avl/avl.h" +#include "httpp/httpp.h" + +#include "global.h" +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "logging.h" +#include "event.h" + +#define CATMODULE "sighandler" + +#ifndef _WIN32 +void _sig_hup(int signo); +void _sig_die(int signo); +void _sig_ignore(int signo); +#endif + +void sighandler_initialize(void) +{ +#ifndef _WIN32 + signal(SIGHUP, _sig_hup); + signal(SIGINT, _sig_die); + signal(SIGTERM, _sig_die); + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, _sig_ignore); +#endif +} + +#ifndef _WIN32 +void _sig_ignore(int signo) +{ + signal(signo, _sig_ignore); +} + +void _sig_hup(int signo) +{ + global . schedule_config_reread = 1; + /* some OSes require us to reattach the signal handler */ + signal(SIGHUP, _sig_hup); +} + +void _sig_die(int signo) +{ + INFO1("Caught signal %d, shutting down...", signo); + + /* inform the server to start shutting down */ + global.running = ICE_HALTING; +} + +#endif diff --git a/src/sighandler.h b/src/sighandler.h new file mode 100644 index 00000000..553a9350 --- /dev/null +++ b/src/sighandler.h @@ -0,0 +1,21 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __SIGHANDLER_H__ +#define __SIGHANDLER_H__ + +extern int schedule_config_reread; + +void sighandler_initialize(void); + + +#endif /* __SIGHANDLER_H__ */ diff --git a/src/slave.c b/src/slave.c new file mode 100644 index 00000000..bde51a15 --- /dev/null +++ b/src/slave.c @@ -0,0 +1,488 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* slave.c + * by Ciaran Anscomb + * + * Periodically requests a list of streams from a master server + * and creates source threads for any it doesn't already have. + * */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#define snprintf _snprintf +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif + +#include "os.h" + +#include "thread/thread.h" +#include "avl/avl.h" +#include "net/sock.h" +#include "httpp/httpp.h" + +#include "cfgfile.h" +#include "global.h" +#include "util.h" +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" +#include "logging.h" +#include "source.h" +#include "format.h" + +#define CATMODULE "slave" + +static void *_slave_thread(void *arg); +thread_type *_slave_thread_id; +static int slave_running = 0; +static unsigned max_interval = 0; + +relay_server *relay_free (relay_server *relay) +{ + relay_server *next = relay->next; + DEBUG1("freeing relay %s", relay->localmount); + if (relay->source) + source_free_source (relay->source); + xmlFree (relay->server); + xmlFree (relay->mount); + xmlFree (relay->localmount); + xmlFree (relay->username); + xmlFree (relay->password); + xmlFree (relay); + return next; +} + + +relay_server *relay_copy (relay_server *r) +{ + relay_server *copy = calloc (1, sizeof (relay_server)); + + if (copy) + { + copy->server = xmlStrdup (r->server); + copy->mount = xmlStrdup (r->mount); + copy->localmount = xmlStrdup (r->localmount); + copy->username = xmlStrdup (r->username); + copy->password = xmlStrdup (r->password); + copy->port = r->port; + copy->mp3metadata = r->mp3metadata; + copy->on_demand = r->on_demand; + } + return copy; +} + + +static void *_relay_thread (void *arg) +{ + relay_server *relay = arg; + + relay->running = 1; + stats_event_inc(NULL, "source_relay_connections"); + + source_main (relay->source); + + relay->running = 0; + if (relay->cleanup) + relay_free (relay); + + return NULL; +} + + +void slave_recheck (void) +{ + max_interval = 0; +} + + +void slave_initialize(void) +{ + if (slave_running) + return; + + slave_running = 1; + _slave_thread_id = thread_create("Slave Thread", _slave_thread, NULL, THREAD_ATTACHED); +} + + +void slave_shutdown(void) +{ + relay_server *relay; + + if (!slave_running) + return; + slave_running = 0; + thread_join (_slave_thread_id); + + relay = global.relays; + while (relay) + relay = relay_free (relay); + global.relays = NULL; + + relay = global.master_relays; + while (relay) + relay = relay_free (relay); + global.master_relays = NULL; +} + + +/* This does the actual connection for a relay. A thread is + * started off if a connection can be acquired + */ +static void start_relay_stream (relay_server *relay) +{ + sock_t streamsock = SOCK_ERROR; + source_t *src = relay->source; + http_parser_t *parser = NULL; + connection_t *con=NULL; + char header[4096]; + + if (relay->on_demand && src->on_demand_req == 0) + return; + + INFO1("Starting relayed source at mountpoint \"%s\"", relay->localmount); + do + { + char *auth_header; + + streamsock = sock_connect_wto (relay->server, relay->port, 30); + if (streamsock == SOCK_ERROR) + { + WARN3("Failed to relay stream from master server, couldn't connect to http://%s:%d%s", + relay->server, relay->port, relay->mount); + break; + } + con = create_connection (streamsock, -1, NULL); + + if (relay->username && relay->password) + { + char *esc_authorisation; + unsigned len = strlen(relay->username) + strlen(relay->password) + 2; + + auth_header = malloc (len); + snprintf (auth_header, len, "%s:%s", relay->username, relay->password); + esc_authorisation = util_base64_encode(auth_header); + auth_header = malloc (len + 24); + snprintf (auth_header, len+24, + "Authorization: Basic %s\r\n", esc_authorisation); + free(esc_authorisation); + } + else + auth_header = strdup (""); + + /* At this point we may not know if we are relaying an mp3 or vorbis + * stream, but only send the icy-metadata header if the relay details + * state so (the typical case). It's harmless in the vorbis case. If + * we don't send in this header then relay will not have mp3 metadata. + */ + sock_write(streamsock, "GET %s HTTP/1.0\r\n" + "User-Agent: " ICECAST_VERSION_STRING "\r\n" + "%s" + "%s" + "\r\n", + relay->mount, relay->mp3metadata?"Icy-MetaData: 1\r\n":"", + auth_header); + free (auth_header); + memset (header, 0, sizeof(header)); + if (util_read_header (con->sock, header, 4096) == 0) + { + WARN0("Header read failed"); + break; + } + parser = httpp_create_parser(); + httpp_initialize (parser, NULL); + if (! httpp_parse_response (parser, header, strlen(header), relay->localmount)) + { + ERROR0("Error parsing relay request"); + break; + } + if (httpp_getvar (parser, HTTPP_VAR_ERROR_MESSAGE)) + { + ERROR1("Error from relay request: %s", httpp_getvar(parser, HTTPP_VAR_ERROR_MESSAGE)); + break; + } + src->parser = parser; + src->con = con; + if (connection_complete_source (src) < 0) + { + DEBUG0("Failed to complete source initialisation"); + break; + } + thread_create ("Relay Thread", _relay_thread, relay, THREAD_DETACHED); + + return; + } while (0); + + if (con == NULL && streamsock != SOCK_ERROR) + sock_close (streamsock); + if (con) + connection_close (con); + src->con = NULL; + if (parser) + httpp_destroy (parser); + src->parser = NULL; + source_clear_source (relay->source); +} + + +/* wrapper for starting the provided relay stream */ +static void check_relay_stream (relay_server *relay) +{ + if (relay->source == NULL) + { + /* new relay, reserve the name */ + DEBUG1("Adding relay source at mountpoint \"%s\"", relay->localmount); + relay->source = source_reserve (relay->localmount); + if (relay->source) + { + if (relay->on_demand) + DEBUG0 ("setting on_demand"); + relay->source->on_demand = relay->on_demand; + } + } + if (relay->source && !relay->running) + { + start_relay_stream (relay); + } +} + + +/* go through updated looking for relays that are different configured. The + * returned list contains relays that should be kept running, current contains + * the list of relays to shutdown + */ +static relay_server * +update_relay_set (relay_server **current, relay_server *updated) +{ + relay_server *relay = updated; + relay_server *existing_relay, **existing_p; + relay_server *new_list = NULL; + + while (relay) + { + existing_relay = *current; + existing_p = current; + + while (existing_relay) + { + if (strcmp (relay->localmount, existing_relay->localmount) == 0) + break; + existing_p = &existing_relay->next; + existing_relay = existing_relay->next; + } + if (existing_relay == NULL) + { + /* new one, copy and insert */ + existing_relay = relay_copy (relay); + } + else + { + *existing_p = existing_relay->next; + } + existing_relay->next = new_list; + new_list = existing_relay; + relay = relay->next; + } + return new_list; +} + + +/* update the relay_list with entries from new_relay_list. Any new relays + * are added to the list, and any not listed in the provided new_relay_list + * get marked for shutting down, just in case they are not shutting down by + * themselves + */ +static void +update_relays (relay_server **relay_list, relay_server *new_relay_list) +{ + relay_server *relay, *current; + + current = update_relay_set (relay_list, new_relay_list); + + /* ok whats left, lets make sure they shut down */ + relay = *relay_list; + while (relay) + { + relay->cleanup = 1; + if (relay->source) + { + if (relay->source->running) + DEBUG1 ("requested %s to shut down", relay->source->mount); + relay->source->running = 0; + relay = relay->next; + } + else + relay = relay_free (relay); + } + /* re-assign new set */ + *relay_list = current; +} + + +static int update_from_master(ice_config_t *config) +{ + char *master = NULL, *password = NULL, *username= NULL; + int port; + sock_t mastersock; + int ret = 0; + char buf[256]; + do + { + char *authheader, *data; + relay_server *relays = NULL, *relay; + int len, count = 1; + + if (config->master_username) + username = strdup (config->master_password); + else + username = strdup ("relay"); + if (config->master_password) + password = strdup (config->master_password); + + if (config->master_server) + master = strdup (config->master_server); + + port = config->master_server_port; + + if (password == NULL || master == NULL || port == 0) + break; + ret = 1; + config_release_config(); + mastersock = sock_connect_wto (master, port, 0); + + if (mastersock == SOCK_ERROR) + { + WARN0("Relay slave failed to contact master server to fetch stream list"); + break; + } + + len = strlen(username) + strlen(password) + 1; + authheader = malloc(len+1); + strcpy(authheader, username); + strcat(authheader, ":"); + strcat(authheader, password); + data = util_base64_encode(authheader); + sock_write (mastersock, + "GET /admin/streamlist.txt HTTP/1.0\r\n" + "Authorization: Basic %s\r\n" + "\r\n", data); + free(authheader); + free(data); + + while (sock_read_line(mastersock, buf, sizeof(buf))) + { + if (!strlen(buf)) + break; + } + while (sock_read_line(mastersock, buf, sizeof(buf))) + { + relay_server *r; + if (!strlen(buf)) + continue; + DEBUG2 ("read %d from master \"%s\"", count++, buf); + r = calloc (1, sizeof (relay_server)); + if (r) + { + r->server = xmlStrdup (master); + r->port = port; + r->mount = xmlStrdup (buf); + r->localmount = xmlStrdup (buf); + r->mp3metadata = 1; + r->next = relays; + relays = r; + } + } + sock_close (mastersock); + + update_relays (&global.master_relays, relays); + /* start any inactive relays */ + relay = global.master_relays; + while (relay) + { + check_relay_stream (relay); + relay = relay->next; + } + relay = relays; + while (relay) + relay = relay_free (relay); + } while(0); + + if (master) + free (master); + if (username) + free (username); + if (password) + free (password); + + return ret; +} + + +static void *_slave_thread(void *arg) +{ + ice_config_t *config; + relay_server *relay; + unsigned interval = 0; + + while (slave_running) + { + thread_sleep (1000000); + if (max_interval > ++interval) + continue; + + interval = 0; + config = config_get_config(); + + max_interval = config->master_update_interval; + + /* the connection could time some time, so the lock can drop */ + if (update_from_master (config)) + config = config_get_config(); + + thread_mutex_lock (&(config_locks()->relay_lock)); + + update_relays (&global.relays, config->relay); + + config_release_config(); + + /* start any inactive relays */ + relay = global.relays; + while (relay) + { + check_relay_stream (relay); + relay = relay->next; + } + thread_mutex_unlock (&(config_locks()->relay_lock)); + } + INFO0 ("Slave thread shutdown complete"); + + return NULL; +} + diff --git a/src/slave.h b/src/slave.h new file mode 100644 index 00000000..c0aba81e --- /dev/null +++ b/src/slave.h @@ -0,0 +1,37 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __SLAVE_H__ +#define __SLAVE_H__ + +typedef struct _relay_server { + char *server; + int port; + char *mount; + char *username; + char *password; + char *localmount; + struct source_tag *source; + int mp3metadata; + int on_demand; + int running; + int cleanup; + struct _relay_server *next; +} relay_server; + + +void slave_initialize(void); +void slave_shutdown(void); +void slave_recheck (void); +relay_server *relay_free (relay_server *relay); + +#endif /* __SLAVE_H__ */ diff --git a/src/source.c b/src/source.c new file mode 100644 index 00000000..6bfbd0be --- /dev/null +++ b/src/source.c @@ -0,0 +1,1238 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#include +#else +#include +#include +#define snprintf _snprintf +#endif + +#include "thread/thread.h" +#include "avl/avl.h" +#include "httpp/httpp.h" +#include "net/sock.h" + +#include "connection.h" +#include "global.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" +#include "logging.h" +#include "cfgfile.h" +#include "util.h" +#include "source.h" +#include "format.h" +#include "auth.h" + +#undef CATMODULE +#define CATMODULE "source" + +#define MAX_FALLBACK_DEPTH 10 + +mutex_t move_clients_mutex; + +/* avl tree helper */ +static int _compare_clients(void *compare_arg, void *a, void *b); +static void parse_audio_info (source_t *source, const char *str); +#ifdef _WIN32 +#define source_run_script(x,y) WARN0("on [dis]connect scripts disabled"); +#else +static void source_run_script (char *command, char *mountpoint); +#endif + +/* Allocate a new source with the stated mountpoint, if one already + * exists with that mountpoint in the global source tree then return + * NULL. + */ +source_t *source_reserve (const char *mount) +{ + source_t *src = NULL; + + do + { + avl_tree_wlock (global.source_tree); + src = source_find_mount_raw (mount); + if (src) + { + src = NULL; + break; + } + + src = calloc (1, sizeof(source_t)); + if (src == NULL) + break; + + /* make duplicates for strings or similar */ + src->mount = strdup (mount); + src->max_listeners = -1; + + src->active_clients_tail = &src->active_clients; + src->pending_clients_tail = &src->pending_clients; + + thread_mutex_create (src->mount, &src->lock); + + avl_insert (global.source_tree, src); + + } while (0); + + avl_tree_unlock (global.source_tree); + return src; +} + + +/* Find a mount with this raw name - ignoring fallbacks. You should have the + * global source tree locked to call this. + */ +source_t *source_find_mount_raw(const char *mount) +{ + source_t *source; + avl_node *node; + int cmp; + + if (!mount) { + return NULL; + } + /* get the root node */ + node = global.source_tree->root->right; + + while (node) { + source = (source_t *)node->key; + cmp = strcmp(mount, source->mount); + if (cmp < 0) + node = node->left; + else if (cmp > 0) + node = node->right; + else + return source; + } + + /* didn't find it */ + return NULL; +} + + +/* Search for mount, if the mount is there but not currently running then + * check the fallback, and so on. Must have a global source lock to call + * this function. + */ +source_t *source_find_mount (const char *mount) +{ + source_t *source = NULL; + ice_config_t *config; + mount_proxy *mountinfo; + int depth = 0; + + config = config_get_config(); + while (mount != NULL) + { + /* limit the number of times through, maybe infinite */ + if (depth > MAX_FALLBACK_DEPTH) + { + source = NULL; + break; + } + + source = source_find_mount_raw(mount); + if (source) + { + if (source->running) + break; + if (source->on_demand) + break; + } + + /* source is not running, meaning that the fallback is not configured + within the source, we need to check the mount list */ + mountinfo = config->mounts; + source = NULL; + while (mountinfo) + { + if (strcmp (mountinfo->mountname, mount) == 0) + break; + mountinfo = mountinfo->next; + } + if (mountinfo) + mount = mountinfo->fallback_mount; + else + mount = NULL; + depth++; + } + + config_release_config(); + return source; +} + + +int source_compare_sources(void *arg, void *a, void *b) +{ + source_t *srca = (source_t *)a; + source_t *srcb = (source_t *)b; + + return strcmp(srca->mount, srcb->mount); +} + + +void source_clear_source (source_t *source) +{ + DEBUG1 ("clearing source \"%s\"", source->mount); + client_destroy(source->client); + source->client = NULL; + source->parser = NULL; + source->con = NULL; + + if (source->dumpfile) + { + INFO1 ("Closing dumpfile for %s", source->mount); + fclose (source->dumpfile); + source->dumpfile = NULL; + } + + /* lets drop any clients still connected */ + while (source->active_clients) + { + client_t *client = source->active_clients; + source->active_clients = client->next; + source_free_client (source, client); + } + source->active_clients_tail = &source->active_clients; + while (source->pending_clients) + { + client_t *client = source->pending_clients; + source->pending_clients = client->next; + source_free_client (source, client); + } + source->pending_clients_tail = &source->pending_clients; + + /* flush out the stream data, we don't want any left over */ + while (source->stream_data) + { + refbuf_t *p = source->stream_data; + source->stream_data = p->next; + if (source->stream_data && p->associated == source->stream_data->associated) + p->associated = NULL; + refbuf_release (p); + } + source->stream_data_tail = NULL; + + if (source->format && source->format->free_plugin) + { + source->format->free_plugin (source->format); + } + source->format = NULL; + + auth_clear (source->authenticator); + + source->burst_point = NULL; + source->first_normal_client = NULL; + source->queue_size = 0; + source->queue_size_limit = 0; + source->listeners = 0; + source->no_mount = 0; + source->max_listeners = -1; + source->yp_public = 0; + free (source->audio_info); + source->audio_info = NULL; + + free(source->fallback_mount); + source->fallback_mount = NULL; + + free(source->dumpfilename); + source->dumpfilename = NULL; + + free (source->on_connect); + source->on_connect = NULL; + + free (source->on_disconnect); + source->on_disconnect = NULL; + + source->on_demand_req = 0; +} + + +/* Remove the provided source from the global tree and free it */ +void source_free_source (source_t *source) +{ + DEBUG1 ("freeing source \"%s\"", source->mount); + avl_tree_wlock (global.source_tree); + avl_delete (global.source_tree, source, NULL); + avl_tree_unlock (global.source_tree); + + /* There should be no listeners on this mount */ + if (source->active_clients) + WARN1("active listeners on mountpoint %s", source->mount); + + if (source->pending_clients) + WARN1("pending listeners on mountpoint %s", source->mount); + + thread_mutex_destroy (&source->lock); + + free (source->mount); + free (source); + + return; +} + + +client_t *source_find_client(source_t *source, int id) +{ + client_t fakeclient, *client = NULL; + connection_t fakecon; + + fakeclient.con = &fakecon; + fakeclient.con->id = id; + + client = source->active_clients; + while (client) + { + if (_compare_clients (NULL, client, &fakeclient) == 0) + break; + client = client->next; + } + + return client; +} + + +/* Move clients from source to dest provided dest is running + * and that the stream format is the same. + * The only lock that should be held when this is called is the + * source tree lock + */ +void source_move_clients (source_t *source, source_t *dest) +{ + /* we don't want the two write locks to deadlock in here */ + thread_mutex_lock (&move_clients_mutex); + thread_mutex_lock (&dest->lock); + + /* if the destination is not running then we can't move clients */ + if (dest->running == 0 && dest->on_demand == 0) + { + WARN1 ("destination mount %s not running, unable to move clients ", dest->mount); + thread_mutex_unlock (&dest->lock); + thread_mutex_unlock (&move_clients_mutex); + return; + } + + do + { + long count = 0; + thread_mutex_lock (&source->lock); + + if (source->format == NULL) + { + INFO1 ("source mount %s is not available", source->mount); + break; + } + if (dest->format) + { + if (source->format->type != dest->format->type) + { + WARN2 ("stream %s and %s are of different types, ignored", source->mount, dest->mount); + break; + } + } + + /* we need to move the client and pending trees */ + while (source->active_clients) + { + client_t *client = source->active_clients; + source->active_clients = client->next; + client->refbuf = dest->stream_data_tail; + client->pos = 0; + *dest->pending_clients_tail = client; + dest->pending_clients_tail = &client->next; + count++; + } + source->active_clients_tail = &source->active_clients; + if (count != source->listeners) + WARN2 ("count %u, listeners %u", count, source->listeners); + count = 0; + while (source->pending_clients) + { + client_t *client = source->pending_clients; + source->pending_clients = client->next; + *dest->pending_clients_tail = client; + dest->pending_clients_tail = &client->next; + count++; + } + source->pending_clients_tail = &source->pending_clients; + if (count != source->new_listeners) + WARN2 ("count %u, new listeners %u", count, source->new_listeners); + + INFO2 ("passing %d listeners to \"%s\"", + source->listeners + source->new_listeners, dest->mount); + + dest->new_listeners += source->listeners + source->new_listeners; + dest->check_pending = 1; + source->listeners = 0; + source->new_listeners = 0; + stats_event (source->mount, "listeners", "0"); + + } while (0); + + thread_mutex_unlock (&source->lock); + /* see if we need to wake up an on-demand relay */ + if (dest->running == 0 && dest->on_demand) + { + dest->on_demand_req = 1; + slave_recheck(); + } + thread_mutex_unlock (&dest->lock); + thread_mutex_unlock (&move_clients_mutex); +} + + +/* clients need to be start from somewhere in the queue + * so we will look for a refbuf which has been previous + * marked as a sync point */ +static void find_client_start (source_t *source, client_t *client) +{ + refbuf_t *refbuf = source->burst_point; + + while (refbuf) + { + if (refbuf->sync_point) + { + break; + } + refbuf = refbuf->next; + } +#if 0 + if (refbuf == NULL) + DEBUG1 ("no start point for client %u", client->con->id); +#endif + client->refbuf = refbuf; +} + + +/* general send routine per listener. The deletion_expected tells us whether + * the last in the queue is about to disappear, so if this client is still + * referring to it after writing then drop the client as it's fallen too far + * behind. + * + * return 1 for client should be specially handled, either removed or placed + * elsewhere + * 0 for normal case. + */ +static int send_to_listener (source_t *source, client_t *client, int deletion_expected) +{ + int bytes; + int loop = 10; /* max number of iterations in one go */ + int total_written = 0; + int ret = 1; + + if (client->predata) + { + char *ptr = client->predata + client->predata_offset; + unsigned len = client->predata_len - client->predata_offset; + bytes = client_send_bytes (client, ptr, len); + if (bytes > 0 && (unsigned)bytes < len) + { + client->predata_offset += bytes; + return 0; + } + free (client->predata); + client->predata_size = client->predata_len = client->predata_offset = 0; + client->predata = NULL; + } + + /* new users need somewhere to start from */ + if (client->refbuf == NULL) + find_client_start (source, client); + + while (1) + { + /* jump out if client has error'd */ + if (client->con->error) + break; + + /* lets not send too much to one client in one go, but don't + sleep for too long if more data can be sent */ + if (total_written > 15000 || loop == 0) + break; + + loop--; + + bytes = source->format->write_buf_to_client (source->format, client); + if (bytes <= 0) + { + ret = 0; + break; /* can't write any more */ + } + + total_written += bytes; + } + + /* the refbuf referenced at head (last in queue) may be marked for deletion + if so, check to see if this client is still referring to it */ + if (deletion_expected && client->refbuf == source->stream_data) + { + DEBUG0("Client has fallen too far behind, removing"); + client->con->error = 1; + ret = 1; + } + return ret; +} + + +static void process_listeners (source_t *source, int fast_clients_only, int deletion_expected) +{ + client_t *sentinel = NULL, *client, **client_p; + + if (fast_clients_only) + { + sentinel = source->first_normal_client; + } + + source->first_normal_client = source->active_clients; + + client = source->active_clients; + client_p = &source->active_clients; + while (client && client != sentinel) + { + int move_it = send_to_listener (source, client, deletion_expected); + + if (move_it) + { + client_t *to_go = client; + + *client_p = client->next; + if (client->next == NULL) + source->active_clients_tail = client_p; + client = client->next; + + if (source->first_normal_client == to_go) + { + source->first_normal_client = to_go->next; + } + + if (to_go->con->error) + { + source_free_client (source, to_go); + source->listeners--; + DEBUG0("Client removed"); + } + else + { + /* move fast clients to beginning of list */ + if (client_p == &source->active_clients) + { + source->active_clients_tail = &to_go->next; + client_p = &to_go->next; + } + to_go->next = source->active_clients; + source->active_clients = to_go; + } + } + else + { + client_p = &client->next; + client = client->next; + } + } +} + + +/* get some data from the source. The stream data is placed in a refbuf + * and sent back, however NULL is also valid as in the case of a short + * timeout and there's no data pending. + */ +static void get_next_buffer (source_t *source) +{ + refbuf_t *refbuf = NULL; + + while (global.running == ICE_RUNNING && source->running) + { + int fds; + time_t current = time(NULL); + int delay = 200; + + if (source->active_clients != source->first_normal_client) + delay = 0; + + thread_mutex_unlock (&source->lock); + + fds = util_timed_wait_for_fd (source->con->sock, delay); + + /* take the lock */ + thread_mutex_lock (&source->lock); + + if (fds < 0) + { + if (! sock_recoverable (sock_error())) + { + WARN0 ("Problem while waiting on socket, Disconnecting source"); + source->running = 0; + } + continue; + } + + if (fds == 0) + { + if (source->last_read + (time_t)source->timeout < current) + { + WARN0 ("Disconnecting source due to socket timeout"); + source->running = 0; + break; + } + if (delay == 0) + { + process_listeners (source, 1, 0); + continue; + } + break; + } + source->last_read = current; + refbuf = source->format->get_buffer (source); + if (refbuf) + { + /* append buffer to the in-flight data queue, */ + if (source->stream_data == NULL) + { + source->stream_data = refbuf; + source->burst_point = refbuf; + source->burst_size = 0; + } + if (source->stream_data_tail) + source->stream_data_tail->next = refbuf; + source->stream_data_tail = refbuf; + source->queue_size += refbuf->len; + + /* move the starting point for new listeners */ + source->burst_size += refbuf->len; + if (source->burst_size > source->burst_size_limit) + { + // DEBUG2 ("starting counts are %d, %d", source->burst_size, source->burst_size_limit); + source->burst_size -= source->burst_point->len; + source->burst_point = source->burst_point->next; + } + + /* save stream to file */ + if (source->dumpfile && source->format->write_buf_to_file) + source->format->write_buf_to_file (source, refbuf); + } + break; + } +} + + +static void source_init (source_t *source) +{ + ice_config_t *config = config_get_config(); + char *listenurl, *str = NULL; + int listen_url_size; + + /* 6 for max size of port */ + listen_url_size = strlen("http://") + strlen(config->hostname) + + strlen(":") + 6 + strlen(source->mount) + 1; + + listenurl = malloc (listen_url_size); + if (listenurl) + { + snprintf (listenurl, listen_url_size, "http://%s:%d%s", + config->hostname, config->port, source->mount); + config_release_config(); + + stats_event (source->mount, "listenurl", listenurl); + + free(listenurl); + } + else + config_release_config(); + + thread_mutex_lock (&source->lock); + if (source->yp_prevent == 0) + { + if ((str = httpp_getvar (source->parser, "ice-public"))) + source->yp_public = atoi (str); + if ((str = httpp_getvar (source->parser, "icy-pub"))) + source->yp_public = atoi (str); + /* handle header from icecast v2 release */ + if ((str = httpp_getvar (source->parser, "icy-public"))) + source->yp_public = atoi (str); + if (str == NULL) + str = "0"; + stats_event (source->mount, "public", str); + } + + str = httpp_getvar(source->parser, "ice-genre"); + if (str == NULL) + str = httpp_getvar(source->parser, "icy-genre"); + stats_event (source->mount, "genre", str); + + str = httpp_getvar(source->parser, "ice-description"); + if (str == NULL) + str = httpp_getvar(source->parser, "icy-description"); + stats_event (source->mount, "server_description", str); + + str = httpp_getvar(source->parser, "ice-name"); + if (str == NULL) + str = httpp_getvar(source->parser, "icy-name"); + stats_event (source->mount, "server_name", str); + + source->audio_info = util_dict_new(); + str = httpp_getvar(source->parser, "ice-audio-info"); + if (str) + { + parse_audio_info(source, str); + stats_event (source->mount, "audio_info", str); + } + + if (source->dumpfilename != NULL) + { + source->dumpfile = fopen (source->dumpfilename, "ab"); + if (source->dumpfile == NULL) + { + WARN2("Cannot open dump file \"%s\" for appending: %s, disabling.", + source->dumpfilename, strerror(errno)); + } + } + + /* grab a read lock, to make sure we get a chance to cleanup */ + thread_rwlock_rlock (source->shutdown_rwlock); + + /* start off the statistics */ + source->listeners = 0; + stats_event_inc (NULL, "sources"); + stats_event_inc (NULL, "source_total_connections"); + stats_event (source->mount, "listeners", "0"); + stats_event (source->mount, "type", source->format->format_description); + + sock_set_blocking (source->con->sock, SOCK_NONBLOCK); + + DEBUG0("Source creation complete"); + source->running = 1; + source->last_read = time (NULL); + thread_mutex_unlock (&source->lock); + + if (source->on_connect) + source_run_script (source->on_connect, source->mount); + + /* + ** Now, if we have a fallback source and override is on, we want + ** to steal it's clients, because it means we've come back online + ** after a failure and they should be gotten back from the waiting + ** loop or jingle track or whatever the fallback is used for + */ + + if (source->fallback_override && source->fallback_mount) + { + source_t *fallback_source; + + avl_tree_rlock(global.source_tree); + fallback_source = source_find_mount(source->fallback_mount); + + if (fallback_source) + source_move_clients (fallback_source, source); + + avl_tree_unlock(global.source_tree); + } + thread_mutex_lock (&source->lock); + if (source->yp_public) + yp_add (source); +} + + +static void source_shutdown (source_t *source) +{ + INFO1("Source \"%s\" exiting", source->mount); + source->running = 0; + + yp_remove (source->mount); + + if (source->on_disconnect) + source_run_script (source->on_disconnect, source->mount); + + if (source->fallback_mount) + { + source_t *fallback_source; + + avl_tree_rlock(global.source_tree); + fallback_source = source_find_mount (source->fallback_mount); + + if (fallback_source != NULL) + { + /* be careful wrt to deadlocking */ + thread_mutex_unlock (&source->lock); + source_move_clients (source, fallback_source); + thread_mutex_lock (&source->lock); + } + + avl_tree_unlock (global.source_tree); + } + + /* delete this sources stats */ + stats_event_dec (NULL, "sources"); + stats_event (source->mount, "listeners", NULL); + + /* we don't remove the source from the tree here, it may be a relay and + therefore reserved */ + source_clear_source (source); + + thread_mutex_unlock (&source->lock); + + global_lock(); + global.sources--; + global_unlock(); + + /* release our hold on the lock so the main thread can continue cleaning up */ + thread_rwlock_unlock(source->shutdown_rwlock); +} + + +void add_authenticated_client (source_t *source, client_t *client) +{ + /* lets add the client to the pending list */ + client->next = source->pending_clients; + source->pending_clients = client; + + client->predata_size = 4096; + client->predata = calloc (1, client->predata_size); + sock_set_blocking (client->con->sock, SOCK_NONBLOCK); + sock_set_nodelay (client->con->sock); + if (source->running == 0 && source->on_demand) + { + /* enable on-demand relay to start, wake up the slave thread */ + DEBUG0("kicking off on-demand relay"); + source->on_demand_req = 1; + slave_recheck(); + } + DEBUG1 ("Added client to pending on %s", source->mount); + source->check_pending = 1; + stats_event_inc (NULL, "clients"); + stats_event_inc (source->mount, "clients"); +} + + +/* try to add client to a pending list. return + * 0 for success + * -1 too many clients + * -2 mount needs authentication + * -3 mount is unavailable + */ +static int _add_client (char *passed_mount, client_t *client, int initial_connection) +{ + source_t *source; + char *mount = passed_mount; + int ret; + + while (1) + { + source = source_find_mount (mount); + if (passed_mount != mount) + free (mount); + if (source == NULL) + return -3; + if (initial_connection && source->no_mount + && strcmp (source->mount, passed_mount) == 0) + return -3; + thread_mutex_lock (&source->lock); + + if (source->running || source->on_demand) + { + DEBUG2 ("max on %s is %d", source->mount, source->max_listeners); + DEBUG2 ("pending %d, current %d", source->new_listeners, source->listeners); + if (source->max_listeners == -1) + break; + if (source->new_listeners + source->listeners < source->max_listeners) + break; + + INFO2 ("max listeners (%d) reached on %s", source->max_listeners, source->mount); + if (source->fallback_when_full == 0 || source->fallback_mount == NULL) + { + thread_mutex_unlock (&source->lock); + return -1; + } + if (source->fallback_mount) + mount = strdup (source->fallback_mount); + else + mount = NULL; + } + + thread_mutex_unlock (&source->lock); + } + + if (auth_check_client (source, client) != AUTH_OK) + { + thread_mutex_unlock (&source->lock); + INFO0 ("listener failed to authenticate"); + return -2; + } + source->new_listeners++; + + thread_mutex_unlock (&source->lock); + return ret; +} + + +void add_client (char *mount, client_t *client) +{ + int added = -3; + + if (mount) + { + thread_mutex_lock (&move_clients_mutex); + avl_tree_rlock (global.source_tree); + added = _add_client (mount, client, 1); + avl_tree_unlock (global.source_tree); + thread_mutex_unlock (&move_clients_mutex); + } + switch (added) + { + case -1: + client_send_404 (client, "Too many clients on this mountpoint. Try again later."); + DEBUG1 ("max clients on %s", mount); + break; + case -2: + client_send_401 (client); + break; + case -3: + client_send_404 (client, "The file you requested could not be found"); + break; + default: + return; + } + /* failed client, drop global count */ + global_lock(); + global.clients--; + global_unlock(); +} + + +static void process_pending_clients (source_t *source) +{ + unsigned count = 0; + client_t *client = source->pending_clients; + + while (client) + { + client_t *to_go = client; + int drop_client = 0; + + client = client->next; + /* do we need to handle http style headers */ + if (to_go->respcode == 0) + { + DEBUG0("processing pending client headers"); + + format_prepare_headers (source, to_go); + if (source->format->create_client_data && + source->format->create_client_data (source, to_go) < 0) + drop_client = 1; + } + if (drop_client) + { + /* shouldn't happen, but don't stall */ + ERROR0 ("dropping pending client"); + to_go->respcode = 200; + source_free_client (source, to_go); + } + else + { + to_go->next = source->active_clients; + source->active_clients = to_go; + if (*source->active_clients_tail == to_go) + source->active_clients_tail = &to_go->next; +// *source->active_clients_tail = to_go; + //source->active_clients_tail = &to_go->next; + count++; + } + source->new_listeners--; + } + source->pending_clients = NULL; + source->pending_clients_tail = &source->pending_clients; + source->check_pending = 0; + + if (count) + { + DEBUG1("Adding %d client(s)", count); + source->listeners += count; + } +} + + +void source_main(source_t *source) +{ + long bytes; + int listeners = 0; + + source_init (source); + + bytes = 0; + listeners = 0; + + while (global.running == ICE_RUNNING && source->running) + { + int remove_from_q; + + get_next_buffer (source); + + remove_from_q = 0; + + /* lets see if we have too much data in the queue, but do not + remove it until later */ + if (source->queue_size > source->queue_size_limit) + remove_from_q = 1; + + /* add pending clients */ + if (source->check_pending) + process_pending_clients (source); + + process_listeners (source, 0, remove_from_q); + + /* has the listener count changed */ + if (source->listeners != listeners) + { + INFO2("listener count on %s now %d", source->mount, source->listeners); + stats_event_args (source->mount, "listeners", "%d", source->listeners); + if (source->listeners == 0 && source->on_demand) + source->running = 0; + listeners = source->listeners; + } + + if (remove_from_q) + { + refbuf_t *to_go = source->stream_data; + /* associated data is shared so don't release it if the next refbuf refers to it */ + if (to_go->next) + { + source->stream_data = to_go->next; + if (to_go->associated == source->stream_data->associated) + to_go->associated = NULL; + source->queue_size -= to_go->len; + if (source->burst_point == to_go) + { + source->burst_point = to_go->next; + source->burst_size -= to_go->len; + } + if (source->format->prerelease) + source->format->prerelease (source, to_go); + refbuf_release (to_go); + // DEBUG1 ("releasing %p", to_go); + } + else + WARN0("possible queue length error"); + } + } + source->running = 0; + + source_shutdown (source); +} + + +static int _compare_clients(void *compare_arg, void *a, void *b) +{ + client_t *clienta = (client_t *)a; + client_t *clientb = (client_t *)b; + + connection_t *cona = clienta->con; + connection_t *conb = clientb->con; + + if (cona->id < conb->id) return -1; + if (cona->id > conb->id) return 1; + + return 0; +} + + +int source_free_client (source_t *source, client_t *client) +{ + global_lock(); + global.clients--; + global_unlock(); + stats_event_dec(NULL, "clients"); + + if (source && source->authenticator && source->authenticator->release_client) + { + source->authenticator->release_client (source, client); + return 0; + } + /* if no response has been sent then send a 404 */ + if (client->respcode == 0) + client_send_404 (client, "Mount unavailable"); + else + client_destroy (client); + + return 1; +} + + +static void parse_audio_info (source_t *source, const char *s) +{ + const char *start = s; + unsigned len; + + while (start != NULL && *start != '\0') + { + if ((s = strchr (start, ';')) == NULL) + len = strlen (start); + else + { + len = (int)(s - start); + s++; /* skip passed the ';' */ + } + if (len) + { + char name[200], value[200]; + char *esc; + + sscanf (start, "%199[^=]=%199[^;\r\n]", name, value); + esc = util_url_unescape (value); + if (esc) + { + util_dict_set (source->audio_info, name, esc); + stats_event (source->mount, name, value); + free (esc); + } + } + start = s; + } +} + + +void source_apply_mount (source_t *source, mount_proxy *mountinfo) +{ + DEBUG1("Applying mount information for \"%s\"", source->mount); + source->max_listeners = mountinfo->max_listeners; + source->fallback_override = mountinfo->fallback_override; + source->no_mount = mountinfo->no_mount; + if (mountinfo->fallback_mount) + { + source->fallback_mount = strdup (mountinfo->fallback_mount); + DEBUG1 ("fallback %s", mountinfo->fallback_mount); + } + if (mountinfo->auth_type != NULL) + { + source->authenticator = auth_get_authenticator( + mountinfo->auth_type, mountinfo->auth_options); + stats_event(source->mount, "authenticator", mountinfo->auth_type); + } + if (mountinfo->dumpfile) + { + DEBUG1("Dumping stream to %s", mountinfo->dumpfile); + source->dumpfilename = strdup (mountinfo->dumpfile); + } + if (mountinfo->queue_size_limit) + { + source->queue_size_limit = mountinfo->queue_size_limit; + DEBUG1 ("queue size to %u", source->queue_size_limit); + } + if (mountinfo->source_timeout) + { + source->timeout = mountinfo->source_timeout; + DEBUG1 ("source timeout to %u", source->timeout); + } + if (mountinfo->burst_size) + { + source->burst_size_limit = mountinfo->burst_size; + DEBUG1 ("burst size to %u", source->burst_size_limit); + } + if (mountinfo->fallback_when_full) + { + source->fallback_when_full = mountinfo->fallback_when_full; + DEBUG1 ("fallback_when_full to %u", source->fallback_when_full); + } + if (mountinfo->no_yp) + { + source->yp_prevent = 1; + DEBUG0("prevent YP listings"); + } + if (mountinfo->on_connect) + { + source->on_connect = strdup(mountinfo->on_connect); + DEBUG1 ("connect script \"%s\"", source->on_connect); + } + if (mountinfo->on_disconnect) + { + source->on_disconnect = strdup(mountinfo->on_disconnect); + DEBUG1 ("disconnect script \"%s\"", source->on_disconnect); + } +} + + +void *source_client_thread (void *arg) +{ + source_t *source = arg; + const char ok_msg[] = "HTTP/1.0 200 OK\r\n\r\n"; + int bytes; + + source->client->respcode = 200; + bytes = sock_write_bytes (source->client->con->sock, ok_msg, sizeof (ok_msg)-1); + if (bytes < (int)sizeof (ok_msg)-1) + { + global_lock(); + global.sources--; + global_unlock(); + WARN0 ("Error writing 200 OK message to source client"); + } + else + { + source->client->con->sent_bytes += bytes; + + stats_event_inc(NULL, "source_client_connections"); + source_main (source); + } + source_free_source (source); + return NULL; +} + + +#ifndef _WIN32 +static void source_run_script (char *command, char *mountpoint) +{ + pid_t pid, external_pid; + + /* do a fork twice so that the command has init as parent */ + external_pid = fork(); + switch (external_pid) + { + case 0: + switch (pid = fork ()) + { + case -1: + ERROR2 ("Unable to fork %s (%s)", command, strerror (errno)); + break; + case 0: /* child */ + DEBUG1 ("Starting command %s", command); + execl (command, command, mountpoint, NULL); + ERROR2 ("Unable to run command %s (%s)", command, strerror (errno)); + exit(0); + default: /* parent */ + break; + } + exit (0); + case -1: + ERROR1 ("Unable to fork %s", strerror (errno)); + break; + default: /* parent */ + waitpid (external_pid, NULL, 0); + break; + } +} +#endif diff --git a/src/source.h b/src/source.h new file mode 100644 index 00000000..0bae57e4 --- /dev/null +++ b/src/source.h @@ -0,0 +1,105 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __SOURCE_H__ +#define __SOURCE_H__ + +#include "cfgfile.h" +#include "yp.h" +#include "util.h" +#include "format.h" + +#include + +struct auth_tag; + +typedef struct source_tag +{ + client_t *client; + connection_t *con; + http_parser_t *parser; + + char *mount; + + /* If this source drops, try to move all clients to this fallback */ + char *fallback_mount; + + /* set to zero to request the source to shutdown without causing a global + * shutdown */ + int running; + + struct _format_plugin_tag *format; + + client_t *active_clients; + client_t **active_clients_tail; + client_t *first_normal_client; + + int check_pending; + client_t *pending_clients; + client_t **pending_clients_tail; + long new_listeners; + + rwlock_t *shutdown_rwlock; + util_dict *audio_info; + + char *dumpfilename; /* Name of a file to dump incoming stream to */ + FILE *dumpfile; + + long listeners; + long max_listeners; + int yp_public; + int yp_prevent; + struct auth_tag *authenticator; + int fallback_override; + int fallback_when_full; + int no_mount; + unsigned queue_size_limit; + unsigned timeout; /* source timeout in seconds */ + + int on_demand; + int on_demand_req; + + time_t last_read; + char *on_connect; + char *on_disconnect; + + mutex_t lock; + unsigned queue_size; + unsigned burst_size; + unsigned burst_size_limit; + refbuf_t *burst_point; + + refbuf_t *stream_data, *stream_data_tail; + +} source_t; + +source_t *source_reserve (const char *mount); +void *source_client_thread (void *arg); +void source_apply_mount (source_t *source, mount_proxy *mountinfo); +void source_clear_source (source_t *source); +source_t *source_find_mount(const char *mount); +source_t *source_find_mount_raw(const char *mount); +client_t *source_find_client(source_t *source, int id); +int source_compare_sources(void *arg, void *a, void *b); +void source_free_source(source_t *source); +void source_move_clients (source_t *source, source_t *dest); +int source_remove_client(void *key); +void source_main(source_t *source); +void add_client (char *mount, client_t *client); +void add_authenticated_client (source_t *source, client_t *client); +int source_free_client (source_t *source, client_t *client); + +extern mutex_t move_clients_mutex; + +#endif + + diff --git a/src/stats.c b/src/stats.c new file mode 100644 index 00000000..fe579ae4 --- /dev/null +++ b/src/stats.c @@ -0,0 +1,858 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "connection.h" + +#include "global.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" +#include "xslt.h" + +#ifdef _WIN32 +#define vsnprintf _vsnprintf +#endif + +typedef struct _event_listener_tag +{ + stats_event_t **queue; + mutex_t *mutex; + + struct _event_listener_tag *next; +} event_listener_t; + +int _stats_running = 0; +thread_type *_stats_thread_id; +int _stats_threads = 0; + +stats_t _stats; +mutex_t _stats_mutex; + +stats_event_t *_global_event_queue; +mutex_t _global_event_mutex; + +cond_t _event_signal_cond; + +event_listener_t *_event_listeners; + + + +static void *_stats_thread(void *arg); +static int _compare_stats(void *a, void *b, void *arg); +static int _compare_source_stats(void *a, void *b, void *arg); +static int _free_stats(void *key); +static int _free_source_stats(void *key); +static void _add_event_to_queue(stats_event_t *event, stats_event_t **queue); +static stats_node_t *_find_node(avl_tree *tree, char *name); +static stats_source_t *_find_source(avl_tree *tree, char *source); +static void _free_event(stats_event_t *event); + +void stats_initialize() +{ + _event_listeners = NULL; + + /* set up global struct */ + _stats.global_tree = avl_tree_new(_compare_stats, NULL); + _stats.source_tree = avl_tree_new(_compare_source_stats, NULL); + + /* set up global mutex */ + thread_mutex_create("stats", &_stats_mutex); + + /* set up event signaler */ + thread_cond_create(&_event_signal_cond); + + /* set up stats queues */ + _global_event_queue = NULL; + thread_mutex_create("stats_global_event", &_global_event_mutex); + + /* fire off the stats thread */ + _stats_running = 1; + _stats_thread_id = thread_create("Stats Thread", _stats_thread, NULL, THREAD_ATTACHED); +} + +void stats_shutdown() +{ + int n; + stats_event_t *event, *next; + + if(!_stats_running) /* We can't shutdown if we're not running. */ + return; + + /* wait for thread to exit */ + _stats_running = 0; + thread_join(_stats_thread_id); + + /* wait for other threads to shut down */ + do { + thread_sleep(300000); + thread_mutex_lock(&_stats_mutex); + n = _stats_threads; + thread_mutex_unlock(&_stats_mutex); + } while (n > 0); + + /* free the queues */ + + /* destroy the queue mutexes */ + thread_mutex_destroy(&_global_event_mutex); + + /* tear it all down */ + thread_cond_destroy(&_event_signal_cond); + thread_mutex_destroy(&_stats_mutex); + avl_tree_free(_stats.source_tree, _free_source_stats); + avl_tree_free(_stats.global_tree, _free_stats); + + event = _global_event_queue; + while(event) { + if(event->source) + free(event->source); + if(event->value) + free(event->value); + if(event->name) + free(event->name); + next = event->next; + free(event); + event = next; + } +} + +stats_t *stats_get_stats() +{ + /* lock global stats + + copy stats + + unlock global stats + + return copied stats */ + + return NULL; +} + +void stats_event(char *source, char *name, char *value) +{ + stats_event_t *node; + stats_event_t *event; + + if (name == NULL || strcmp(name, "") == 0) return; + + /* build event */ + event = (stats_event_t *)malloc(sizeof(stats_event_t)); + event->source = NULL; + if (source != NULL) event->source = (char *)strdup(source); + event->name = (char *)strdup(name); + event->value = NULL; + event->next = NULL; + if (value != NULL) event->value = (char *)strdup(value); + + /* queue event */ + thread_mutex_lock(&_global_event_mutex); + if (_global_event_queue == NULL) { + _global_event_queue = event; + } else { + node = _global_event_queue; + while (node->next) node = node->next; + node->next = event; + } + thread_mutex_unlock(&_global_event_mutex); +} + +void stats_event_args(char *source, char *name, char *format, ...) +{ + char buf[1024]; + va_list val; + + va_start(val, format); + vsnprintf(buf, 1024, format, val); + va_end(val); + + stats_event(source, name, buf); +} + +static char *_get_stats(char *source, char *name) +{ + stats_node_t *stats = NULL; + stats_source_t *src = NULL; + char *value = NULL; + + thread_mutex_lock(&_stats_mutex); + + if (source == NULL) { + stats = _find_node(_stats.global_tree, name); + } else { + src = _find_source(_stats.source_tree, source); + if (src) { + stats = _find_node(src->stats_tree, name); + } + } + + if (stats) value = (char *)strdup(stats->value); + + thread_mutex_unlock(&_stats_mutex); + + return value; +} + +char *stats_get_value(char *source, char *name) +{ + return(_get_stats(source, name)); +} +void stats_event_inc(char *source, char *name) +{ + char *old_value; + int new_value; + + old_value = _get_stats(source, name); + if (old_value != NULL) { + new_value = atoi(old_value); + free(old_value); + new_value++; + } else { + new_value = 1; + } + + stats_event_args(source, name, "%d", new_value); +} + +void stats_event_add(char *source, char *name, unsigned long value) +{ + char *old_value; + unsigned long new_value; + + old_value = _get_stats(source, name); + if (old_value != NULL) { + new_value = atol(old_value); + free(old_value); + new_value += value; + } else { + new_value = value; + } + + stats_event_args(source, name, "%ld", new_value); +} + +void stats_event_dec(char *source, char *name) +{ + char *old_value; + int new_value; + + old_value = _get_stats(source, name); + if (old_value != NULL) { + new_value = atoi(old_value); + free(old_value); + new_value--; + if (new_value < 0) new_value = 0; + } else { + new_value = 0; + } + + stats_event_args(source, name, "%d", new_value); +} + +/* note: you must call this function only when you have exclusive access +** to the avl_tree +*/ +static stats_node_t *_find_node(avl_tree *stats_tree, char *name) +{ + stats_node_t *stats; + avl_node *node; + int cmp; + + /* get the root node */ + node = stats_tree->root->right; + + while (node) { + stats = (stats_node_t *)node->key; + cmp = strcmp(name, stats->name); + if (cmp < 0) + node = node->left; + else if (cmp > 0) + node = node->right; + else + return stats; + } + + /* didn't find it */ + return NULL; +} + +/* note: you must call this function only when you have exclusive access +** to the avl_tree +*/ +static stats_source_t *_find_source(avl_tree *source_tree, char *source) +{ + stats_source_t *stats; + avl_node *node; + int cmp; + + /* get the root node */ + node = source_tree->root->right; + while (node) { + stats = (stats_source_t *)node->key; + cmp = strcmp(source, stats->source); + if (cmp < 0) + node = node->left; + else if (cmp > 0) + node = node->right; + else + return stats; + } + + /* didn't find it */ + return NULL; +} + +static stats_event_t *_copy_event(stats_event_t *event) +{ + stats_event_t *copy = (stats_event_t *)malloc(sizeof(stats_event_t)); + if (event->source) + copy->source = (char *)strdup(event->source); + else + copy->source = NULL; + copy->name = (char *)strdup(event->name); + if (event->value) + copy->value = (char *)strdup(event->value); + else + copy->value = NULL; + copy->next = NULL; + + return copy; +} + +static void *_stats_thread(void *arg) +{ + stats_event_t *event; + stats_event_t *copy; + stats_node_t *node; + stats_node_t *anode; + stats_source_t *snode; + stats_source_t *asnode; + event_listener_t *listener; + avl_node *avlnode; + + while (_stats_running) { + thread_mutex_lock(&_global_event_mutex); + if (_global_event_queue != NULL) { + /* grab the next event from the queue */ + event = _global_event_queue; + _global_event_queue = event->next; + event->next = NULL; + thread_mutex_unlock(&_global_event_mutex); + + thread_mutex_lock(&_stats_mutex); + if (event->source == NULL) { + /* we have a global event */ + if (event->value != NULL) { + /* adding/updating */ + node = _find_node(_stats.global_tree, event->name); + if (node == NULL) { + /* add node */ + anode = (stats_node_t *)malloc(sizeof(stats_node_t)); + anode->name = (char *)strdup(event->name); + anode->value = (char *)strdup(event->value); + + avl_insert(_stats.global_tree, (void *)anode); + } else { + /* update node */ + free(node->value); + node->value = (char *)strdup(event->value); + } + + } else { + /* we're deleting */ + node = _find_node(_stats.global_tree, event->name); + if (node != NULL) + avl_delete(_stats.global_tree, (void *)node, _free_stats); + } + } else { + /* we have a source event */ + + snode = _find_source(_stats.source_tree, event->source); + if (snode != NULL) { + /* this is a source we already have a tree for */ + if (event->value != NULL) { + /* we're add/updating */ + node = _find_node(snode->stats_tree, event->name); + if (node == NULL) { + /* adding node */ + anode = (stats_node_t *)malloc(sizeof(stats_node_t)); + anode->name = (char *)strdup(event->name); + anode->value = (char *)strdup(event->value); + + avl_insert(snode->stats_tree, (void *)anode); + } else { + /* updating node */ + free(node->value); + node->value = (char *)strdup(event->value); + } + } else { + /* we're deleting */ + node = _find_node(snode->stats_tree, event->name); + if (node != NULL) { + avl_delete(snode->stats_tree, (void *)node, _free_stats); + + avlnode = avl_get_first(snode->stats_tree); + if (avlnode == NULL) { + avl_delete(_stats.source_tree, (void *)snode, _free_source_stats); + } + } + } + } else { + /* this is a new source */ + asnode = (stats_source_t *)malloc(sizeof(stats_source_t)); + asnode->source = (char *)strdup(event->source); + asnode->stats_tree = avl_tree_new(_compare_stats, NULL); + + anode = (stats_node_t *)malloc(sizeof(stats_node_t)); + anode->name = (char *)strdup(event->name); + anode->value = (char *)strdup(event->value); + + avl_insert(asnode->stats_tree, (void *)anode); + + avl_insert(_stats.source_tree, (void *)asnode); + } + } + + /* now we have an event that's been processed into the running stats */ + /* this event should get copied to event listeners' queues */ + listener = _event_listeners; + while (listener) { + copy = _copy_event(event); + thread_mutex_lock(listener->mutex); + _add_event_to_queue(copy, listener->queue); + thread_mutex_unlock(listener->mutex); + + listener = listener->next; + } + thread_cond_broadcast(&_event_signal_cond); + + /* now we need to destroy the event */ + _free_event(event); + + thread_mutex_unlock(&_stats_mutex); + continue; + } else { + thread_mutex_unlock(&_global_event_mutex); + } + + thread_sleep(300000); + } + + /* wake the other threads so they can shut down cleanly */ + thread_cond_broadcast(&_event_signal_cond); + + return NULL; +} + +/* you must have the _stats_mutex locked here */ +static void _register_listener(stats_event_t **queue, mutex_t *mutex) +{ + event_listener_t *node; + event_listener_t *evli = (event_listener_t *)malloc(sizeof(event_listener_t)); + + evli->queue = queue; + evli->mutex = mutex; + evli->next = NULL; + + if (_event_listeners == NULL) { + _event_listeners = evli; + } else { + node = _event_listeners; + while (node->next) node = node->next; + node->next = evli; + } +} + +static stats_event_t *_make_event_from_node(stats_node_t *node, char *source) +{ + stats_event_t *event = (stats_event_t *)malloc(sizeof(stats_event_t)); + + if (source != NULL) + event->source = (char *)strdup(source); + else + event->source = NULL; + event->name = (char *)strdup(node->name); + event->value = (char *)strdup(node->value); + event->next = NULL; + + return event; +} + +static void _add_event_to_queue(stats_event_t *event, stats_event_t **queue) +{ + stats_event_t *node; + + if (*queue == NULL) { + *queue = event; + } else { + node = *queue; + while (node->next) node = node->next; + node->next = event; + } +} + +static stats_event_t *_get_event_from_queue(stats_event_t **queue) +{ + stats_event_t *event; + + if (*queue == NULL) return NULL; + + event = *queue; + *queue = (*queue)->next; + event->next = NULL; + + return event; +} + +static int _send_event_to_client(stats_event_t *event, connection_t *con) +{ + int ret; + + /* send data to the client!!!! */ + ret = sock_write(con->sock, "EVENT %s %s %s\n", (event->source != NULL) ? event->source : "global", event->name, event->value ? event->value : "null"); + + return (ret == -1) ? 0 : 1; +} + +void _dump_stats_to_queue(stats_event_t **queue) +{ + avl_node *node; + avl_node *node2; + stats_event_t *event; + stats_source_t *source; + + thread_mutex_lock(&_stats_mutex); + /* first we fill our queue with the current stats */ + /* start with the global stats */ + node = avl_get_first(_stats.global_tree); + while (node) { + event = _make_event_from_node((stats_node_t *)node->key, NULL); + _add_event_to_queue(event, queue); + + node = avl_get_next(node); + } + + /* now the stats for each source */ + node = avl_get_first(_stats.source_tree); + while (node) { + source = (stats_source_t *)node->key; + node2 = avl_get_first(source->stats_tree); + while (node2) { + event = _make_event_from_node((stats_node_t *)node2->key, source->source); + _add_event_to_queue(event, queue); + + node2 = avl_get_next(node2); + } + + node = avl_get_next(node); + } + thread_mutex_unlock(&_stats_mutex); +} + +/* factoring out code for stats loops +** this function copies all stats to queue, and registers +** the queue for all new events atomically. +** note: mutex must already be created! +*/ +static void _atomic_get_and_register(stats_event_t **queue, mutex_t *mutex) +{ + avl_node *node; + avl_node *node2; + stats_event_t *event; + stats_source_t *source; + + thread_mutex_lock(&_stats_mutex); + + /* first we fill our queue with the current stats */ + + /* start with the global stats */ + node = avl_get_first(_stats.global_tree); + while (node) { + event = _make_event_from_node((stats_node_t *)node->key, NULL); + _add_event_to_queue(event, queue); + + node = avl_get_next(node); + } + + /* now the stats for each source */ + node = avl_get_first(_stats.source_tree); + while (node) { + source = (stats_source_t *)node->key; + node2 = avl_get_first(source->stats_tree); + while (node2) { + event = _make_event_from_node((stats_node_t *)node2->key, source->source); + _add_event_to_queue(event, queue); + + node2 = avl_get_next(node2); + } + + node = avl_get_next(node); + } + + /* now we register to receive future event notices */ + _register_listener(queue, mutex); + + thread_mutex_unlock(&_stats_mutex); +} + +void *stats_connection(void *arg) +{ + stats_connection_t *statcon = (stats_connection_t *)arg; + stats_event_t *local_event_queue = NULL; + mutex_t local_event_mutex; + stats_event_t *event; + + /* increment the thread count */ + thread_mutex_lock(&_stats_mutex); + _stats_threads++; + thread_mutex_unlock(&_stats_mutex); + + thread_mutex_create("stats local event", &local_event_mutex); + + _atomic_get_and_register(&local_event_queue, &local_event_mutex); + + while (_stats_running) { + thread_mutex_lock(&local_event_mutex); + event = _get_event_from_queue(&local_event_queue); + if (event != NULL) { + if (!_send_event_to_client(event, statcon->con)) { + _free_event(event); + thread_mutex_unlock(&local_event_mutex); + break; + } + _free_event(event); + } else { + thread_mutex_unlock(&local_event_mutex); + thread_cond_wait(&_event_signal_cond); + continue; + } + + thread_mutex_unlock(&local_event_mutex); + } + + thread_mutex_destroy(&local_event_mutex); + + thread_mutex_lock(&_stats_mutex); + _stats_threads--; + thread_mutex_unlock(&_stats_mutex); + + return NULL; +} + +typedef struct _source_xml_tag { + char *mount; + xmlNodePtr node; + + struct _source_xml_tag *next; +} source_xml_t; + +static xmlNodePtr _find_xml_node(char *mount, source_xml_t **list, xmlNodePtr root) +{ + source_xml_t *node, *node2; + int found = 0; + + /* search for existing node */ + node = *list; + while (node) { + if (strcmp(node->mount, mount) == 0) { + found = 1; + break; + } + node = node->next; + } + + if (found) return node->node; + + /* if we didn't find it, we must build it and add it to the list */ + + /* build node */ + node = (source_xml_t *)malloc(sizeof(source_xml_t)); + node->mount = strdup(mount); + node->node = xmlNewChild(root, NULL, "source", NULL); + xmlSetProp(node->node, "mount", mount); + node->next = NULL; + + /* add node */ + if (*list == NULL) { + *list = node; + } else { + node2 = *list; + while (node2->next) node2 = node2->next; + node2->next = node; + } + + return node->node; +} + +void stats_transform_xslt(client_t *client, char *xslpath) +{ + xmlDocPtr doc; + + stats_get_xml(&doc); + + xslt_transform(doc, xslpath, client); + + xmlFreeDoc(doc); +} + +void stats_get_xml(xmlDocPtr *doc) +{ + stats_event_t *event; + stats_event_t *queue; + xmlNodePtr node, srcnode; + source_xml_t *src_nodes = NULL; + source_xml_t *next; + + queue = NULL; + _dump_stats_to_queue(&queue); + + *doc = xmlNewDoc("1.0"); + node = xmlNewDocNode(*doc, NULL, "icestats", NULL); + xmlDocSetRootElement(*doc, node); + + + event = _get_event_from_queue(&queue); + while (event) { + if (event->source == NULL) { + xmlNewChild(node, NULL, event->name, event->value); + } else { + srcnode = _find_xml_node(event->source, &src_nodes, node); + xmlNewChild(srcnode, NULL, event->name, event->value); + } + + _free_event(event); + event = _get_event_from_queue(&queue); + } + + while (src_nodes) { + next = src_nodes->next; + free(src_nodes->mount); + free(src_nodes); + src_nodes = next; + } +} +void stats_sendxml(client_t *client) +{ + int bytes; + stats_event_t *event; + stats_event_t *queue; + xmlDocPtr doc; + xmlNodePtr node, srcnode; + int len; + xmlChar *buff = NULL; + source_xml_t *snd; + source_xml_t *src_nodes = NULL; + + queue = NULL; + _dump_stats_to_queue(&queue); + + doc = xmlNewDoc("1.0"); + node = xmlNewDocNode(doc, NULL, "icestats", NULL); + xmlDocSetRootElement(doc, node); + + + event = _get_event_from_queue(&queue); + while (event) { + if (event->source == NULL) { + xmlNewChild(node, NULL, event->name, event->value); + } else { + srcnode = _find_xml_node(event->source, &src_nodes, node); + xmlNewChild(srcnode, NULL, event->name, event->value); + } + + _free_event(event); + event = _get_event_from_queue(&queue); + } + + xmlDocDumpMemory(doc, &buff, &len); + xmlFreeDoc(doc); + + client->respcode = 200; + bytes = sock_write(client->con->sock, "HTTP/1.0 200 OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "\r\n", len); + if (bytes > 0) client->con->sent_bytes += bytes; + else goto send_error; + + bytes = client_send_bytes (client, buff, (unsigned)len); + + send_error: + while (src_nodes) { + snd = src_nodes->next; + free(src_nodes->mount); + free(src_nodes); + src_nodes = snd; + } + if (buff) xmlFree(buff); +} + +static int _compare_stats(void *arg, void *a, void *b) +{ + stats_node_t *nodea = (stats_node_t *)a; + stats_node_t *nodeb = (stats_node_t *)b; + + return strcmp(nodea->name, nodeb->name); +} + +static int _compare_source_stats(void *arg, void *a, void *b) +{ + stats_source_t *nodea = (stats_source_t *)a; + stats_source_t *nodeb = (stats_source_t *)b; + + return strcmp(nodea->source, nodeb->source); +} + +static int _free_stats(void *key) +{ + stats_node_t *node = (stats_node_t *)key; + free(node->value); + free(node->name); + free(node); + + return 1; +} + +static int _free_source_stats(void *key) +{ + stats_source_t *node = (stats_source_t *)key; + avl_tree_free(node->stats_tree, _free_stats); + free(node->source); + free(node); + + return 1; +} + +static void _free_event(stats_event_t *event) +{ + if (event->source) free(event->source); + if (event->name) free(event->name); + if (event->value) free(event->value); + free(event); +} diff --git a/src/stats.h b/src/stats.h new file mode 100644 index 00000000..894af3df --- /dev/null +++ b/src/stats.h @@ -0,0 +1,99 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __STATS_H__ +#define __STATS_H__ + +#include "connection.h" +#include "httpp/httpp.h" +#include "client.h" +#include +#include +#include + + +typedef struct _stats_connection_tag +{ + connection_t *con; + http_parser_t *parser; +} stats_connection_t; + +typedef struct _stats_node_tag +{ + char *name; + char *value; +} stats_node_t; + +typedef struct _stats_event_tag +{ + char *source; + char *name; + char *value; + + struct _stats_event_tag *next; +} stats_event_t; + +typedef struct _stats_source_tag +{ + char *source; + avl_tree *stats_tree; +} stats_source_t; + +typedef struct _stats_tag +{ + avl_tree *global_tree; + + /* global stats + start_time + total_users + max_users + total_sources + max_sources + total_user_connections + total_source_connections + */ + + avl_tree *source_tree; + + /* stats by source, and for stats + start_time + total_users + max_users + */ + +} stats_t; + +void stats_initialize(); +void stats_shutdown(); + +stats_t *stats_get_stats(); + +void stats_event(char *source, char *name, char *value); +void stats_event_args(char *source, char *name, char *format, ...); +void stats_event_inc(char *source, char *name); +void stats_event_add(char *source, char *name, unsigned long value); +void stats_event_dec(char *source, char *name); + +void *stats_connection(void *arg); +void *stats_callback(void *arg); + +void stats_transform_xslt(client_t *client, char *xslpath); +void stats_sendxml(client_t *client); +void stats_get_xml(xmlDocPtr *doc); +char *stats_get_value(char *source, char *name); + +#endif /* __STATS_H__ */ + + + + + diff --git a/src/thread/.cvsignore b/src/thread/.cvsignore deleted file mode 100644 index 8550dbfc..00000000 --- a/src/thread/.cvsignore +++ /dev/null @@ -1,6 +0,0 @@ -Makefile -Makefile.in -.deps -.libs -*.la -*.lo diff --git a/src/thread/BUILDING b/src/thread/BUILDING deleted file mode 100644 index ebe84227..00000000 --- a/src/thread/BUILDING +++ /dev/null @@ -1,20 +0,0 @@ -defines that affect compilation - -_WIN32 - this should be defined for Win32 platforms - -DEBUG_MUTEXES - define this to turn on mutex debugging. this will log locks/unlocks. - -CHECK_MUTEXES (DEBUG_MUTEXES must also be defined) - checks to make sure mutex operations make sense. ie, multi_mutex is locked - when locking multiple mutexes, etc. - -THREAD_DEBUG (define to 1-4) - turns on the thread.log logging - - - - - - diff --git a/src/thread/COPYING b/src/thread/COPYING deleted file mode 100644 index 92b8903f..00000000 --- a/src/thread/COPYING +++ /dev/null @@ -1,481 +0,0 @@ - GNU LIBRARY GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 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. - -[This is the first released version of the library GPL. It is - numbered 2 because it goes with version 2 of the ordinary GPL.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Library General Public License, applies to some -specially designated Free Software Foundation software, and to any -other libraries whose authors decide to use it. You can use it for -your libraries, 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 library, or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link a program with the library, you must provide -complete object files to the recipients so that they can relink them -with the library, after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - Our method of protecting your rights has two steps: (1) copyright -the library, and (2) offer you this license which gives you legal -permission to copy, distribute and/or modify the library. - - Also, for each distributor's protection, we want to make certain -that everyone understands that there is no warranty for this free -library. If the library is modified by someone else and passed on, we -want its recipients to know that what they have is not the original -version, 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 companies distributing free -software will individually obtain patent licenses, thus in effect -transforming the program into proprietary software. To prevent this, -we have made it clear that any patent must be licensed for everyone's -free use or not licensed at all. - - Most GNU software, including some libraries, is covered by the ordinary -GNU General Public License, which was designed for utility programs. This -license, the GNU Library General Public License, applies to certain -designated libraries. This license is quite different from the ordinary -one; be sure to read it in full, and don't assume that anything in it is -the same as in the ordinary license. - - The reason we have a separate public license for some libraries is that -they blur the distinction we usually make between modifying or adding to a -program and simply using it. Linking a program with a library, without -changing the library, is in some sense simply using the library, and is -analogous to running a utility program or application program. However, in -a textual and legal sense, the linked executable is a combined work, a -derivative of the original library, and the ordinary General Public License -treats it as such. - - Because of this blurred distinction, using the ordinary General -Public License for libraries did not effectively promote software -sharing, because most developers did not use the libraries. We -concluded that weaker conditions might promote sharing better. - - However, unrestricted linking of non-free programs would deprive the -users of those programs of all benefit from the free status of the -libraries themselves. This Library General Public License is intended to -permit developers of non-free programs to use free libraries, while -preserving your freedom as a user of such programs to change the free -libraries that are incorporated in them. (We have not seen how to achieve -this as regards changes in header files, but we have achieved it as regards -changes in the actual functions of the Library.) The hope is that this -will lead to faster development of free libraries. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, while the latter only -works together with the library. - - Note that it is possible for a library to be covered by the ordinary -General Public License rather than by this special one. - - GNU LIBRARY GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library which -contains a notice placed by the copyright holder or other authorized -party saying it may be distributed under the terms of this Library -General Public License (also called "this License"). Each licensee is -addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also compile or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - c) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - d) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the 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. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Library General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - 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. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/src/thread/Makefile.am b/src/thread/Makefile.am deleted file mode 100644 index 5651a1bc..00000000 --- a/src/thread/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -## Process this with automake to create Makefile.in - -AUTOMAKE_OPTIONS = foreign - -EXTRA_DIST = BUILDING COPYING README TODO - -noinst_LTLIBRARIES = libicethread.la -noinst_HEADERS = thread.h - -libicethread_la_SOURCES = thread.c -libicethread_la_CFLAGS = @XIPH_CFLAGS@ - -INCLUDES = -I$(srcdir)/.. - -debug: - $(MAKE) all CFLAGS="@DEBUG@" - -profile: - $(MAKE) all CFLAGS="@PROFILE@" - diff --git a/src/thread/README b/src/thread/README deleted file mode 100644 index 0ccadb07..00000000 --- a/src/thread/README +++ /dev/null @@ -1,9 +0,0 @@ -This is the cross platform thread and syncronization library. - -It depends on the avl library. - -This is a massively cleaned and picked through version of the code from -the icecast 1.3.x base. It has not been heavily tested *YET*. But since -it's just cleanups, it really shouldn't have that many problems. - -jack. diff --git a/src/thread/TODO b/src/thread/TODO deleted file mode 100644 index 84f20108..00000000 --- a/src/thread/TODO +++ /dev/null @@ -1,5 +0,0 @@ -- make DEBUG_MUTEXES and CHECK_MUTEXES work - -- recursive locking/unlocking (easy) -- reader/writer locking (easy) -- make a mode were _log is disabled (normal mode) (easy) diff --git a/src/thread/thread.c b/src/thread/thread.c deleted file mode 100644 index 2ef5b27a..00000000 --- a/src/thread/thread.c +++ /dev/null @@ -1,852 +0,0 @@ -/* threads.c: Thread Abstraction Functions - * - * Copyright (c) 1999, 2000 the icecast team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include -#include -#include -#include -#include -#include - -#include - -#ifndef _WIN32 -#include -#include -#else -#include -#include -#include -#endif - -#include - -#include -#include -#ifdef THREAD_DEBUG -#include -#endif - -#ifdef _WIN32 -#define __FUNCTION__ __FILE__ -#endif - -#ifdef THREAD_DEBUG -#define CATMODULE "thread" -#define LOG_ERROR(y) log_write(_logid, 1, CATMODULE "/", __FUNCTION__, y) -#define LOG_ERROR1(y, z1) log_write(_logid, 1, CATMODULE "/", __FUNCTION__, y, z1) -#define LOG_ERROR3(y, z1, z2, z3) log_write(_logid, 1, CATMODULE "/", __FUNCTION__, y, z1, z2, z3) -#define LOG_ERROR7(y, z1, z2, z3, z4, z5, z6, z7) log_write(_logid, 1, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5, z6, z7) - -#define LOG_WARN(y) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y) -#define LOG_WARN3(y, z1, z2, z3) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y, z1, z2, z3) -#define LOG_WARN5(y, z1, z2, z3, z4, z5) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5) -#define LOG_WARN7(y, z1, z2, z3, z4, z5, z6, z7) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5, z6, z7) - -#define LOG_INFO(y) log_write(_logid, 3, CATMODULE "/", __FUNCTION__, y) -#define LOG_INFO4(y, z1, z2, z3, z4) log_write(_logid, 3, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4) -#define LOG_INFO5(y, z1, z2, z3, z4, z5) log_write(_logid, 3, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5) - -#define LOG_DEBUG(y) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y) -#define LOG_DEBUG2(y, z1, z2) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y, z1, z2) -#define LOG_DEBUG3(y, z1, z2, z3) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y, z1, z2, z3) -#define LOG_DEBUG4(y, z1, z2, z3, z4) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4) -#define LOG_DEBUG5(y, z1, z2, z3, z4, z5) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5) -#endif - -/* thread starting structure */ -typedef struct thread_start_tag { - /* the real start routine and arg */ - void *(*start_routine)(void *); - void *arg; - - /* the other stuff we need to make sure this thread is inserted into - ** the thread tree - */ - thread_type *thread; - pthread_t sys_thread; -} thread_start_t; - -static long _next_thread_id = 0; -static int _initialized = 0; -static avl_tree *_threadtree = NULL; - - -#ifdef THREAD_DEBUG - -/* this is x86 specific, but gets a very precise and low overhead - * timer, other platforms may have similar mechanisms - */ -#define rdtscll(val) \ - __asm__ __volatile__("rdtsc" : "=A" (val)) - -static inline unsigned long long get_count (void) -{ - unsigned long long ret; - - rdtscll(ret); - return ret; -} - -static int _logid = -1; -static long _next_mutex_id = 0; -static avl_tree *_mutextree = NULL; - -static mutex_t _threadtree_mutex = { -1, "unset", MUTEX_STATE_UNINIT, - (unsigned long long)0, NULL, -1, PTHREAD_MUTEX_INITIALIZER}; -static mutex_t _mutextree_mutex = { -1, "unset", MUTEX_STATE_UNINIT, - (unsigned long long)0, NULL, -1, PTHREAD_MUTEX_INITIALIZER}; -static mutex_t _library_mutex = { -1, NULL, MUTEX_STATE_UNINIT, - (unsigned long long)0, NULL, -1, PTHREAD_MUTEX_INITIALIZER}; - -static int _compare_mutexes(void *compare_arg, void *a, void *b); -static int _free_mutex(void *key); - -#else - -static mutex_t _threadtree_mutex = { PTHREAD_MUTEX_INITIALIZER }; -static mutex_t _library_mutex = { PTHREAD_MUTEX_INITIALIZER }; - -#endif - -/* INTERNAL FUNCTIONS */ - -static int _compare_threads(void *compare_arg, void *a, void *b); -static int _free_thread(void *key); - -/* mutex fuctions */ -static void _mutex_create(mutex_t *mutex); -static void _mutex_lock(mutex_t *mutex); -static void _mutex_unlock(mutex_t *mutex); - -/* misc thread stuff */ -static void *_start_routine(void *arg); -static void _catch_signals(void); -static void _block_signals(void); - -/* LIBRARY INITIALIZATION */ - -void thread_initialize(void) -{ - thread_type *thread; - - /* set up logging */ - -#ifdef THREAD_DEBUG - /* create all the internal mutexes, and initialize the mutex tree */ - - _mutextree = avl_tree_new(_compare_mutexes, NULL); - - /* we have to create this one by hand, because there's no - ** mutextree_mutex to lock yet! - */ - _mutex_create(&_mutextree_mutex); - - _mutextree_mutex.mutex_id = _next_mutex_id++; - avl_insert(_mutextree, (void *)&_mutextree_mutex); - - log_initialize(); - _logid = log_open("thread.log"); - log_set_level(_logid, 4); -#endif - - thread_mutex_create("threadtree", &_threadtree_mutex); - thread_mutex_create("thread lib", &_library_mutex); - - /* initialize the thread tree and insert the main thread */ - - _threadtree = avl_tree_new(_compare_threads, NULL); - - thread = (thread_type *)malloc(sizeof(thread_type)); - - thread->thread_id = _next_thread_id++; - thread->line = 0; - thread->file = strdup("main.c"); - thread->sys_thread = pthread_self(); - thread->create_time = time(NULL); - thread->name = strdup("Main Thread"); - - avl_insert(_threadtree, (void *)thread); - - _catch_signals(); - - _initialized = 1; -} - -void thread_shutdown(void) -{ - if (_initialized == 1) { - thread_mutex_destroy(&_library_mutex); - thread_mutex_destroy(&_threadtree_mutex); -#ifdef THREAD_DEBUG - thread_mutex_destroy(&_mutextree_mutex); - - avl_tree_free(_mutextree, _free_mutex); -#endif - avl_tree_free(_threadtree, _free_thread); - } - -#ifdef THREAD_DEBUG - log_close(_logid); - log_shutdown(); -#endif - -} - -/* - * Signals should be handled by the main thread, nowhere else. - * I'm using POSIX signal interface here, until someone tells me - * that I should use signal/sigset instead - * - * This function only valid for non-Win32 - */ -static void _block_signals(void) -{ -#ifndef _WIN32 - sigset_t ss; - - sigfillset(&ss); - - /* These ones we want */ - sigdelset(&ss, SIGKILL); - sigdelset(&ss, SIGSTOP); - sigdelset(&ss, SIGSEGV); - sigdelset(&ss, SIGCHLD); - sigdelset(&ss, SIGBUS); - if (pthread_sigmask(SIG_BLOCK, &ss, NULL) != 0) { -#ifdef THREAD_DEBUG - LOG_ERROR("Pthread_sigmask() failed for blocking signals"); -#endif - } -#endif -} - -/* - * Let the calling thread catch all the relevant signals - * - * This function only valid for non-Win32 - */ -static void _catch_signals(void) -{ -#ifndef _WIN32 - sigset_t ss; - - sigemptyset(&ss); - - /* These ones should only be accepted by the signal handling thread (main thread) */ - sigaddset(&ss, SIGHUP); - sigaddset(&ss, SIGCHLD); - sigaddset(&ss, SIGINT); - sigaddset(&ss, SIGPIPE); - sigaddset(&ss, SIGTERM); - - if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL) != 0) { -#ifdef THREAD_DEBUG - LOG_ERROR("pthread_sigmask() failed for catching signals!"); -#endif - } -#endif -} - - -thread_type *thread_create_c(char *name, void *(*start_routine)(void *), - void *arg, int detached, int line, char *file) -{ - int ok = 1; - thread_type *thread = NULL; - thread_start_t *start = NULL; - pthread_attr_t attr; - - thread = (thread_type *)calloc(1, sizeof(thread_type)); - do { - if (thread == NULL) - break; - start = (thread_start_t *)calloc(1, sizeof(thread_start_t)); - if (start == NULL) - break; - if (pthread_attr_init (&attr) < 0) - break; - - thread->line = line; - thread->file = strdup(file); - - _mutex_lock (&_threadtree_mutex); - thread->thread_id = _next_thread_id++; - _mutex_unlock (&_threadtree_mutex); - - thread->name = strdup(name); - thread->create_time = time(NULL); - - start->start_routine = start_routine; - start->arg = arg; - start->thread = thread; - - pthread_attr_setinheritsched (&attr, PTHREAD_INHERIT_SCHED); - if (detached) - { - pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); - thread->detached = 1; - } - -#ifdef __OpenBSD__ - thread->running = 1; -#endif - if (pthread_create (&thread->sys_thread, &attr, _start_routine, start) == 0) - { - pthread_attr_destroy (&attr); - return thread; - } - else - pthread_attr_destroy (&attr); - } - while (0); - -#ifdef THREAD_DEBUG - LOG_ERROR1("Could not create new thread %s", name); -#endif - if (start) free (start); - if (thread) free (thread); - return NULL; -} - -/* _mutex_create -** -** creates a mutex -*/ -static void _mutex_create(mutex_t *mutex) -{ -#ifdef THREAD_DEBUG - mutex->thread_id = MUTEX_STATE_NEVERLOCKED; - mutex->line = -1; -#endif - - pthread_mutex_init(&mutex->sys_mutex, NULL); -} - -void thread_mutex_create_c(const char *name, mutex_t *mutex, int line, const char *file) -{ - _mutex_create(mutex); - -#ifdef THREAD_DEBUG - mutex->name = strdup (name); - _mutex_lock(&_mutextree_mutex); - mutex->mutex_id = _next_mutex_id++; - avl_insert(_mutextree, (void *)mutex); - _mutex_unlock(&_mutextree_mutex); - - LOG_DEBUG3 ("mutex %s created (%s:%d)", mutex->name, file, line); -#endif -} - -void thread_mutex_destroy (mutex_t *mutex) -{ - pthread_mutex_destroy(&mutex->sys_mutex); - -#ifdef THREAD_DEBUG - free (mutex->file); - _mutex_lock(&_mutextree_mutex); - avl_delete(_mutextree, mutex, _free_mutex); - _mutex_unlock(&_mutextree_mutex); -#endif -} - -void thread_mutex_lock_c(mutex_t *mutex, int line, char *file) -{ -#ifdef THREAD_DEBUG - mutex->lock_start = get_count(); - mutex->file = strdup (file); - mutex->line = line; -#if 0 - thread_type *th = thread_self(); - - if (!th) LOG_WARN("No mt record for %u in lock [%s:%d]", thread_self(), file, line); - - LOG_DEBUG5("Locking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); - -# ifdef CHECK_MUTEXES - /* Just a little sanity checking to make sure that we're locking - ** mutexes correctly - */ - - if (th) { - int locks = 0; - avl_node *node; - mutex_t *tmutex; - - _mutex_lock(&_mutextree_mutex); - - node = avl_get_first (_mutextree); - - while (node) { - tmutex = (mutex_t *)node->key; - - if (tmutex->mutex_id == mutex->mutex_id) { - if (tmutex->thread_id == th->thread_id) { - /* Deadlock, same thread can't lock the same mutex twice */ - LOG_ERROR7("DEADLOCK AVOIDED (%d == %d) on mutex [%s] in file %s line %d by thread %d [%s]", - tmutex->thread_id, th->thread_id, mutex->name ? mutex->name : "undefined", file, line, th->thread_id, th->name); - - _mutex_unlock(&_mutextree_mutex); - return; - } - } else if (tmutex->thread_id == th->thread_id) { - /* Mutex locked by this thread (not this mutex) */ - locks++; - } - - node = avl_get_next(node); - } - - if (locks > 0) { - /* Has already got a mutex locked */ - if (_multi_mutex.thread_id != th->thread_id) { - /* Tries to lock two mutexes, but has not got the double mutex, norty boy! */ - LOG_WARN("(%d != %d) Thread %d [%s] tries to lock a second mutex [%s] in file %s line %d, without locking double mutex!", - _multi_mutex.thread_id, th->thread_id, th->thread_id, th->name, mutex->name ? mutex->name : "undefined", file, line); - } - } - - _mutex_unlock(&_mutextree_mutex); - } -# endif /* CHECK_MUTEXES */ - - _mutex_lock(mutex); - - _mutex_lock(&_mutextree_mutex); - - LOG_DEBUG2("Locked %p by thread %d", mutex, th ? th->thread_id : -1); - mutex->line = line; - if (th) { - mutex->thread_id = th->thread_id; - } - - _mutex_unlock(&_mutextree_mutex); -#endif - _mutex_lock(mutex); -#endif /* DEBUG_MUTEXES */ -} - -void thread_mutex_unlock_c(mutex_t *mutex, int line, char *file) -{ -#if 0 - thread_type *th = thread_self(); - - if (!th) { - LOG_ERROR3("No record for %u in unlock [%s:%d]", thread_self(), file, line); - } - - LOG_DEBUG5("Unlocking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); - - mutex->line = line; - -# ifdef CHECK_MUTEXES - if (th) { - int locks = 0; - avl_node *node; - mutex_t *tmutex; - - _mutex_lock(&_mutextree_mutex); - - while (node) { - tmutex = (mutex_t *)node->key; - - if (tmutex->mutex_id == mutex->mutex_id) { - if (tmutex->thread_id != th->thread_id) { - LOG_ERROR7("ILLEGAL UNLOCK (%d != %d) on mutex [%s] in file %s line %d by thread %d [%s]", tmutex->thread_id, th->thread_id, - mutex->name ? mutex->name : "undefined", file, line, th->thread_id, th->name); - _mutex_unlock(&_mutextree_mutex); - return; - } - } else if (tmutex->thread_id == th->thread_id) { - locks++; - } - - node = avl_get_next (node); - } - - if ((locks > 0) && (_multi_mutex.thread_id != th->thread_id)) { - /* Don't have double mutex, has more than this mutex left */ - - LOG_WARN("(%d != %d) Thread %d [%s] tries to unlock a mutex [%s] in file %s line %d, without owning double mutex!", - _multi_mutex.thread_id, th->thread_id, th->thread_id, th->name, mutex->name ? mutex->name : "undefined", file, line); - } - - _mutex_unlock(&_mutextree_mutex); - } -# endif /* CHECK_MUTEXES */ - - _mutex_unlock(mutex); - - _mutex_lock(&_mutextree_mutex); - - LOG_DEBUG2("Unlocked %p by thread %d", mutex, th ? th->thread_id : -1); - mutex->line = -1; - if (mutex->thread_id == th->thread_id) { - mutex->thread_id = MUTEX_STATE_NOTLOCKED; - } - - _mutex_unlock(&_mutextree_mutex); -#endif /* DEBUG_MUTEXES */ - _mutex_unlock(mutex); -#ifdef THREAD_DEBUG - LOG_DEBUG4 ("lock %s, at %s:%d lasted %llu", mutex->name, mutex->file, - mutex->line, get_count() - mutex->lock_start); - free (mutex->file); - mutex->file = NULL; -#endif -} - -void thread_cond_create_c(cond_t *cond, int line, char *file) -{ - pthread_cond_init(&cond->sys_cond, NULL); - pthread_mutex_init(&cond->cond_mutex, NULL); -} - -void thread_cond_destroy(cond_t *cond) -{ - pthread_mutex_destroy(&cond->cond_mutex); - pthread_cond_destroy(&cond->sys_cond); -} - -void thread_cond_signal_c(cond_t *cond, int line, char *file) -{ - pthread_cond_signal(&cond->sys_cond); -} - -void thread_cond_broadcast_c(cond_t *cond, int line, char *file) -{ - pthread_cond_broadcast(&cond->sys_cond); -} - -void thread_cond_timedwait_c(cond_t *cond, int millis, int line, char *file) -{ - struct timespec time; - - time.tv_sec = millis/1000; - time.tv_nsec = (millis - time.tv_sec*1000)*1000000; - - pthread_mutex_lock(&cond->cond_mutex); - pthread_cond_timedwait(&cond->sys_cond, &cond->cond_mutex, &time); - pthread_mutex_unlock(&cond->cond_mutex); -} - -void thread_cond_wait_c(cond_t *cond, int line, char *file) -{ - pthread_mutex_lock(&cond->cond_mutex); - pthread_cond_wait(&cond->sys_cond, &cond->cond_mutex); - pthread_mutex_unlock(&cond->cond_mutex); -} - -void thread_rwlock_create_c(rwlock_t *rwlock, int line, char *file) -{ - pthread_rwlock_init(&rwlock->sys_rwlock, NULL); -} - -void thread_rwlock_destroy(rwlock_t *rwlock) -{ - pthread_rwlock_destroy(&rwlock->sys_rwlock); -} - -void thread_rwlock_rlock_c(rwlock_t *rwlock, int line, char *file) -{ - pthread_rwlock_rdlock(&rwlock->sys_rwlock); -} - -void thread_rwlock_wlock_c(rwlock_t *rwlock, int line, char *file) -{ - pthread_rwlock_wrlock(&rwlock->sys_rwlock); -} - -void thread_rwlock_unlock_c(rwlock_t *rwlock, int line, char *file) -{ - pthread_rwlock_unlock(&rwlock->sys_rwlock); -} - -void thread_exit_c(long val, int line, char *file) -{ - thread_type *th = thread_self(); - -#if defined(DEBUG_MUTEXES) && defined(CHECK_MUTEXES) - if (th) { - avl_node *node; - mutex_t *tmutex; - char name[40]; - - _mutex_lock(&_mutextree_mutex); - - while (node) { - tmutex = (mutex_t *)node->key; - - if (tmutex->thread_id == th->thread_id) { - LOG_WARN("Thread %d [%s] exiting in file %s line %d, without unlocking mutex [%s]", - th->thread_id, th->name, file, line, mutex_to_string(tmutex, name)); - } - - node = avl_get_next (node); - } - - _mutex_unlock(&_mutextree_mutex); - } -#endif -#ifdef __OpenBSD__ - thread->running = 0; -#endif - - if (th && th->detached) - { -#ifdef THREAD_DEBUG - LOG_DEBUG4("Removing thread %d [%s] started at [%s:%d]", th->thread_id, - th->name, th->file, th->line); - - _mutex_lock(&_threadtree_mutex); - avl_delete(_threadtree, th, _free_thread); - _mutex_unlock(&_threadtree_mutex); -#endif - } - - pthread_exit ((void*)val); -} - -/* sleep for a number of microseconds */ -void thread_sleep(unsigned long len) -{ -#ifdef _WIN32 - Sleep(len / 1000); -#else -# ifdef HAVE_NANOSLEEP - struct timespec time_sleep; - struct timespec time_remaining; - int ret; - - time_sleep.tv_sec = len / 1000000; - time_sleep.tv_nsec = (len % 1000000) * 1000; - - ret = nanosleep(&time_sleep, &time_remaining); - while (ret != 0 && errno == EINTR) { - time_sleep.tv_sec = time_remaining.tv_sec; - time_sleep.tv_nsec = time_remaining.tv_nsec; - - ret = nanosleep(&time_sleep, &time_remaining); - } -# else - struct timeval tv; - - tv.tv_sec = len / 1000000; - tv.tv_usec = (len % 1000000); - - select(0, NULL, NULL, NULL, &tv); -# endif -#endif -} - -static void *_start_routine(void *arg) -{ - thread_start_t *start = (thread_start_t *)arg; - void *(*start_routine)(void *) = start->start_routine; - void *real_arg = start->arg; - thread_type *thread = start->thread; - - _block_signals(); - - /* insert thread into thread tree here */ - _mutex_lock(&_threadtree_mutex); - thread->sys_thread = pthread_self(); - avl_insert(_threadtree, (void *)thread); - _mutex_unlock(&_threadtree_mutex); - -#ifdef THREAD_DEBUG - LOG_DEBUG4("Added thread %d [%s] started at [%s:%d]", thread->thread_id, - thread->name, thread->file, thread->line); -#endif - - pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); - free (start); - - (start_routine)(real_arg); - -#ifdef __OpenBSD__ - thread->running = 0; -#endif - - if (thread->detached) - { - _mutex_lock (&_threadtree_mutex); - avl_delete (_threadtree, thread, _free_thread); - _mutex_unlock (&_threadtree_mutex); - } - - return NULL; -} - -thread_type *thread_self(void) -{ - avl_node *node; - thread_type *th; - pthread_t sys_thread = pthread_self(); - - _mutex_lock(&_threadtree_mutex); - - if (_threadtree == NULL) { -#ifdef THREAD_DEBUG - LOG_WARN("Thread tree is empty, this must be wrong!"); -#endif - _mutex_unlock(&_threadtree_mutex); - return NULL; - } - - node = avl_get_first(_threadtree); - - while (node) { - th = (thread_type *)node->key; - - if (th && pthread_equal(sys_thread, th->sys_thread)) { - _mutex_unlock(&_threadtree_mutex); - return th; - } - - node = avl_get_next(node); - } - _mutex_unlock(&_threadtree_mutex); - - -#ifdef THREAD_DEBUG - LOG_ERROR("Nonexistant thread alive..."); -#endif - - return NULL; -} - -void thread_rename(const char *name) -{ - thread_type *th; - - th = thread_self(); - if (th->name) free(th->name); - - th->name = strdup(name); -} - -static void _mutex_lock(mutex_t *mutex) -{ - pthread_mutex_lock(&mutex->sys_mutex); -} - -static void _mutex_unlock(mutex_t *mutex) -{ - pthread_mutex_unlock(&mutex->sys_mutex); -} - - -void thread_library_lock(void) -{ - _mutex_lock(&_library_mutex); -} - -void thread_library_unlock(void) -{ - _mutex_unlock(&_library_mutex); -} - -void thread_join(thread_type *thread) -{ - void *ret; - int i; - -#ifdef __OpenBSD__ - /* openbsd masks signals while waiting */ - while (thread->running) - thread_sleep (200000); -#endif - i = pthread_join(thread->sys_thread, &ret); - _mutex_lock(&_threadtree_mutex); - avl_delete(_threadtree, thread, _free_thread); - _mutex_unlock(&_threadtree_mutex); -} - -/* AVL tree functions */ - -#ifdef THREAD_DEBUG -static int _compare_mutexes(void *compare_arg, void *a, void *b) -{ - mutex_t *m1, *m2; - - m1 = (mutex_t *)a; - m2 = (mutex_t *)b; - - if (m1->mutex_id > m2->mutex_id) - return 1; - if (m1->mutex_id < m2->mutex_id) - return -1; - return 0; -} -#endif - -static int _compare_threads(void *compare_arg, void *a, void *b) -{ - thread_type *t1, *t2; - - t1 = (thread_type *)a; - t2 = (thread_type *)b; - - if (t1->thread_id > t2->thread_id) - return 1; - if (t1->thread_id < t2->thread_id) - return -1; - return 0; -} - -#ifdef THREAD_DEBUG -static int _free_mutex(void *key) -{ - mutex_t *m; - - m = (mutex_t *)key; - - if (m && m->file) { - free(m->file); - m->file = NULL; - } - - /* all mutexes are static. don't need to free them */ - - return 1; -} -#endif - -static int _free_thread(void *key) -{ - thread_type *t; - - t = (thread_type *)key; - - if (t->file) - free(t->file); - if (t->name) - free(t->name); - - free(t); - - return 1; -} - - diff --git a/src/thread/thread.h b/src/thread/thread.h deleted file mode 100644 index 7c1f542f..00000000 --- a/src/thread/thread.h +++ /dev/null @@ -1,191 +0,0 @@ -/* thread.h - * - Thread Abstraction Function Headers - * - * Copyright (c) 1999, 2000 the icecast team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef __THREAD_H__ -#define __THREAD_H__ - -#include - -/* renamed from thread_t due to conflict on OS X */ - -typedef struct { - /* the local id for the thread, and it's name */ - long thread_id; - char *name; - - /* the time the thread was created */ - time_t create_time; - - /* the file and line which created this thread */ - char *file; - int line; - - /* is the thread running detached? */ - int detached; - -#ifdef __OpenBSD__ - int running; -#endif - - /* the system specific thread */ - pthread_t sys_thread; -} thread_type; - -typedef struct { -#ifdef THREAD_DEBUG - /* the local id and name of the mutex */ - long mutex_id; - char *name; - - /* the thread which is currently locking this mutex */ - long thread_id; - - /* time the lock was taken */ - unsigned long long lock_start; - - /* the file and line where the mutex was locked */ - char *file; - int line; - -#endif - - /* the system specific mutex */ - pthread_mutex_t sys_mutex; -} mutex_t; - -typedef struct { -#ifdef THREAD_DEBUG - long cond_id; - char *name; -#endif - - pthread_mutex_t cond_mutex; - pthread_cond_t sys_cond; -} cond_t; - -typedef struct { -#ifdef THREAD_DEBUG - long rwlock_id; - char *name; - - /* information on which thread and where in the code - ** this rwlock was write locked - */ - long thread_id; - char *file; - int line; -#endif - - pthread_rwlock_t sys_rwlock; -} rwlock_t; - -#define thread_create(n,x,y,z) thread_create_c(n,x,y,z,__LINE__,__FILE__) -#define thread_mutex_create(name,x) thread_mutex_create_c(name,x,__LINE__,__FILE__) -#define thread_mutex_lock(x) thread_mutex_lock_c(x,__LINE__,__FILE__) -#define thread_mutex_unlock(x) thread_mutex_unlock_c(x,__LINE__,__FILE__) -#define thread_cond_create(x) thread_cond_create_c(x,__LINE__,__FILE__) -#define thread_cond_signal(x) thread_cond_signal_c(x,__LINE__,__FILE__) -#define thread_cond_broadcast(x) thread_cond_broadcast_c(x,__LINE__,__FILE__) -#define thread_cond_wait(x) thread_cond_wait_c(x,__LINE__,__FILE__) -#define thread_cond_timedwait(x,t) thread_cond_wait_c(x,t,__LINE__,__FILE__) -#define thread_rwlock_create(x) thread_rwlock_create_c(x,__LINE__,__FILE__) -#define thread_rwlock_rlock(x) thread_rwlock_rlock_c(x,__LINE__,__FILE__) -#define thread_rwlock_wlock(x) thread_rwlock_wlock_c(x,__LINE__,__FILE__) -#define thread_rwlock_unlock(x) thread_rwlock_unlock_c(x,__LINE__,__FILE__) -#define thread_exit(x) thread_exit_c(x,__LINE__,__FILE__) - -#define MUTEX_STATE_NOTLOCKED -1 -#define MUTEX_STATE_NEVERLOCKED -2 -#define MUTEX_STATE_UNINIT -3 -#define THREAD_DETACHED 1 -#define THREAD_ATTACHED 0 - -#ifdef _mangle -# define thread_initialize _mangle(thread_initialize) -# define thread_initialize_with_log_id _mangle(thread_initialize_with_log_id) -# define thread_shutdown _mangle(thread_shutdown) -# define thread_create_c _mangle(thread_create_c) -# define thread_mutex_create_c _mangle(thread_mutex_create) -# define thread_mutex_lock_c _mangle(thread_mutex_lock_c) -# define thread_mutex_unlock_c _mangle(thread_mutex_unlock_c) -# define thread_mutex_destroy _mangle(thread_mutex_destroy) -# define thread_cond_create_c _mangle(thread_cond_create_c) -# define thread_cond_signal_c _mangle(thread_cond_signal_c) -# define thread_cond_broadcast_c _mangle(thread_cond_broadcast_c) -# define thread_cond_wait_c _mangle(thread_cond_wait_c) -# define thread_cond_timedwait_c _mangle(thread_cond_timedwait_c) -# define thread_cond_destroy _mangle(thread_cond_destroy) -# define thread_rwlock_create_c _mangle(thread_rwlock_create_c) -# define thread_rwlock_rlock_c _mangle(thread_rwlock_rlock_c) -# define thread_rwlock_wlock_c _mangle(thread_rwlock_wlock_c) -# define thread_rwlock_unlock_c _mangle(thread_rwlock_unlock_c) -# define thread_rwlock_destroy _mangle(thread_rwlock_destroy) -# define thread_exit_c _mangle(thread_exit_c) -# define thread_sleep _mangle(thread_sleep) -# define thread_library_lock _mangle(thread_library_lock) -# define thread_library_unlock _mangle(thread_library_unlock) -# define thread_self _mangle(thread_self) -# define thread_rename _mangle(thread_rename) -# define thread_join _mangle(thread_join) -#endif - -/* init/shutdown of the library */ -void thread_initialize(void); -void thread_initialize_with_log_id(int log_id); -void thread_shutdown(void); - -/* creation, destruction, locking, unlocking, signalling and waiting */ -thread_type *thread_create_c(char *name, void *(*start_routine)(void *), - void *arg, int detached, int line, char *file); -void thread_mutex_create_c(const char *name, mutex_t *mutex, int line, const char *file); -void thread_mutex_lock_c(mutex_t *mutex, int line, char *file); -void thread_mutex_unlock_c(mutex_t *mutex, int line, char *file); -void thread_mutex_destroy(mutex_t *mutex); -void thread_cond_create_c(cond_t *cond, int line, char *file); -void thread_cond_signal_c(cond_t *cond, int line, char *file); -void thread_cond_broadcast_c(cond_t *cond, int line, char *file); -void thread_cond_wait_c(cond_t *cond, int line, char *file); -void thread_cond_timedwait_c(cond_t *cond, int millis, int line, char *file); -void thread_cond_destroy(cond_t *cond); -void thread_rwlock_create_c(rwlock_t *rwlock, int line, char *file); -void thread_rwlock_rlock_c(rwlock_t *rwlock, int line, char *file); -void thread_rwlock_wlock_c(rwlock_t *rwlock, int line, char *file); -void thread_rwlock_unlock_c(rwlock_t *rwlock, int line, char *file); -void thread_rwlock_destroy(rwlock_t *rwlock); -void thread_exit_c(long val, int line, char *file); - -/* sleeping */ -void thread_sleep(unsigned long len); - -/* for using library functions which aren't threadsafe */ -void thread_library_lock(void); -void thread_library_unlock(void); -#define PROTECT_CODE(code) { thread_library_lock(); code; thread_library_unlock(); } - -/* thread information functions */ -thread_type *thread_self(void); - -/* renames current thread */ -void thread_rename(const char *name); - -/* waits until thread_exit is called for another thread */ -void thread_join(thread_type *thread); - -#endif /* __THREAD_H__ */ diff --git a/src/util.c b/src/util.c new file mode 100644 index 00000000..2f244099 --- /dev/null +++ b/src/util.c @@ -0,0 +1,622 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#ifdef HAVE_POLL +#include +#endif +#else +#include +#include +#include +#define snprintf _snprintf +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif + +#include "net/sock.h" +#include "thread/thread.h" + +#include "cfgfile.h" +#include "util.h" +#include "os.h" +#include "refbuf.h" +#include "connection.h" +#include "client.h" + +#define CATMODULE "util" + +#include "logging.h" + +/* Abstract out an interface to use either poll or select depending on which + * is available (poll is preferred) to watch a single fd. + * + * timeout is in milliseconds. + * + * returns > 0 if activity on the fd occurs before the timeout. + * 0 if no activity occurs + * < 0 for error. + */ +int util_timed_wait_for_fd(int fd, int timeout) +{ +#ifdef HAVE_POLL + struct pollfd ufds; + + ufds.fd = fd; + ufds.events = POLLIN; + ufds.revents = 0; + + return poll(&ufds, 1, timeout); +#else + fd_set rfds; + struct timeval tv, *p=NULL; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + if(timeout >= 0) { + tv.tv_sec = timeout/1000; + tv.tv_usec = (timeout % 1000)*1000; + p = &tv; + } + return select(fd+1, &rfds, NULL, NULL, p); +#endif +} + +int util_read_header(int sock, char *buff, unsigned long len) +{ + int read_bytes, ret; + unsigned long pos; + char c; + ice_config_t *config; + int header_timeout; + + config = config_get_config(); + header_timeout = config->header_timeout; + config_release_config(); + + read_bytes = 1; + pos = 0; + ret = 0; + + while ((read_bytes == 1) && (pos < (len - 1))) { + read_bytes = 0; + + if (util_timed_wait_for_fd(sock, header_timeout*1000) > 0) { + + if ((read_bytes = recv(sock, &c, 1, 0))) { + if (c != '\r') buff[pos++] = c; + if ((pos > 1) && (buff[pos - 1] == '\n' && buff[pos - 2] == '\n')) { + ret = 1; + break; + } + } + } else { + break; + } + } + + if (ret) buff[pos] = '\0'; + + return ret; +} + +char *util_get_extension(char *path) { + char *ext = strrchr(path, '.'); + + if(ext == NULL) + return ""; + else + return ext+1; +} + +int util_check_valid_extension(char *uri) { + int ret = 0; + char *p2; + + if (uri) { + p2 = strrchr(uri, '.'); + if (p2) { + p2++; + if (strncmp(p2, "xsl", strlen("xsl")) == 0) { + /* Build the full path for the request, concatenating the webroot from the config. + ** Here would be also a good time to prevent accesses like '../../../../etc/passwd' or somesuch. + */ + ret = XSLT_CONTENT; + } + if (strncmp(p2, "htm", strlen("htm")) == 0) { + /* Build the full path for the request, concatenating the webroot from the config. + ** Here would be also a good time to prevent accesses like '../../../../etc/passwd' or somesuch. + */ + ret = HTML_CONTENT; + } + if (strncmp(p2, "html", strlen("html")) == 0) { + /* Build the full path for the request, concatenating the webroot from the config. + ** Here would be also a good time to prevent accesses like '../../../../etc/passwd' or somesuch. + */ + ret = HTML_CONTENT; + } + + } + } + return ret; +} + +static int hex(char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if(c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +static int verify_path(char *path) { + int dir = 0, indotseq = 0; + + while(*path) { + if(*path == '/' || *path == '\\') { + if(indotseq) + return 0; + if(dir) + return 0; + dir = 1; + path++; + continue; + } + + if(dir || indotseq) { + if(*path == '.') + indotseq = 1; + else + indotseq = 0; + } + + dir = 0; + path++; + } + + return 1; +} + +char *util_get_path_from_uri(char *uri) { + char *path = util_normalise_uri(uri); + char *fullpath; + + if(!path) + return NULL; + else { + fullpath = util_get_path_from_normalised_uri(path); + free(path); + return fullpath; + } +} + +char *util_get_path_from_normalised_uri(char *uri) { + char *fullpath; + char *webroot; + ice_config_t *config = config_get_config(); + + webroot = config->webroot_dir; + config_release_config(); + + fullpath = malloc(strlen(uri) + strlen(webroot) + 1); + strcpy(fullpath, webroot); + + strcat(fullpath, uri); + + return fullpath; +} + +static char hexchars[16] = { + '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' +}; + +static char safechars[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +char *util_url_escape(char *src) +{ + int len = strlen(src); + /* Efficiency not a big concern here, keep the code simple/conservative */ + char *dst = calloc(1, len*3 + 1); + unsigned char *source = src; + int i,j=0; + + for(i=0; i < len; i++) { + if(safechars[source[i]]) { + dst[j++] = source[i]; + } + else { + dst[j] = '%'; + dst[j+1] = hexchars[ (source[i] >> 4) & 0xf ]; + dst[j+2] = hexchars[ source[i] & 0xf ]; + j+= 3; + } + } + + dst[j] = 0; + return dst; +} + +char *util_url_unescape(char *src) +{ + int len = strlen(src); + unsigned char *decoded; + int i; + char *dst; + int done = 0; + + decoded = calloc(1, len + 1); + + dst = decoded; + + for(i=0; i < len; i++) { + switch(src[i]) { + case '%': + if(i+2 >= len) { + free(decoded); + return NULL; + } + if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) { + free(decoded); + return NULL; + } + + *dst++ = hex(src[i+1]) * 16 + hex(src[i+2]); + i+= 2; + break; + case '#': + done = 1; + break; + case 0: + ERROR0("Fatal internal logic error in util_url_unescape()"); + free(decoded); + return NULL; + break; + default: + *dst++ = src[i]; + break; + } + if(done) + break; + } + + *dst = 0; /* null terminator */ + + return decoded; +} + +/* Get an absolute path (from the webroot dir) from a URI. Return NULL if the + * path contains 'disallowed' sequences like foo/../ (which could be used to + * escape from the webroot) or if it cannot be URI-decoded. + * Caller should free the path. + */ +char *util_normalise_uri(char *uri) { + char *path; + + if(uri[0] != '/') + return NULL; + + path = util_url_unescape(uri); + + if(path == NULL) { + WARN1("Error decoding URI: %s\n", uri); + return NULL; + } + + /* We now have a full URI-decoded path. Check it for allowability */ + if(verify_path(path)) + return path; + else { + WARN1("Rejecting invalid path \"%s\"", path); + free(path); + return NULL; + } +} + +static char base64table[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' +}; + +static signed char base64decode[256] = { + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -1, -2, -2, + -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2, + -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2 +}; + +char *util_bin_to_hex(unsigned char *data, int len) +{ + char *hex = malloc(len*2 + 1); + int i; + + for(i = 0; i < len; i++) { + hex[i*2] = hexchars[(data[i]&0xf0) >> 4]; + hex[i*2+1] = hexchars[data[i]&0x0f]; + } + + hex[len*2] = 0; + + return hex; +} + +/* This isn't efficient, but it doesn't need to be */ +char *util_base64_encode(char *data) +{ + int len = strlen(data); + char *out = malloc(len*4/3 + 4); + char *result = out; + int chunk; + + while(len > 0) { + chunk = (len >3)?3:len; + *out++ = base64table[(*data & 0xFC)>>2]; + *out++ = base64table[((*data & 0x03)<<4) | ((*(data+1) & 0xF0) >> 4)]; + switch(chunk) { + case 3: + *out++ = base64table[((*(data+1) & 0x0F)<<2) | ((*(data+2) & 0xC0)>>6)]; + *out++ = base64table[(*(data+2)) & 0x3F]; + break; + case 2: + *out++ = base64table[((*(data+1) & 0x0F)<<2)]; + *out++ = '='; + break; + case 1: + *out++ = '='; + *out++ = '='; + break; + } + data += chunk; + len -= chunk; + } + *out = 0; + + return result; +} + +char *util_base64_decode(unsigned char *input) +{ + int len = strlen(input); + char *out = malloc(len*3/4 + 5); + char *result = out; + signed char vals[4]; + + while(len > 0) { + if(len < 4) + { + free(result); + return NULL; /* Invalid Base64 data */ + } + + vals[0] = base64decode[*input++]; + vals[1] = base64decode[*input++]; + vals[2] = base64decode[*input++]; + vals[3] = base64decode[*input++]; + + if(vals[0] < 0 || vals[1] < 0 || vals[2] < -1 || vals[3] < -1) { + len -=4; + continue; + } + + *out++ = vals[0]<<2 | vals[1]>>4; + if(vals[2] >= 0) + *out++ = ((vals[1]&0x0F)<<4) | (vals[2]>>2); + else + *out++ = 0; + + if(vals[3] >= 0) + *out++ = ((vals[2]&0x03)<<6) | (vals[3]); + else + *out++ = 0; + + len -= 4; + } + *out = 0; + + return result; +} + +util_dict *util_dict_new(void) +{ + return (util_dict *)calloc(1, sizeof(util_dict)); +} + +void util_dict_free(util_dict *dict) +{ + util_dict *next; + + while (dict) { + next = dict->next; + + if (dict->key) + free (dict->key); + if (dict->val) + free (dict->val); + free (dict); + + dict = next; + } +} + +const char *util_dict_get(util_dict *dict, const char *key) +{ + while (dict) { + if (!strcmp(key, dict->key)) + return dict->val; + dict = dict->next; + } + return NULL; +} + +int util_dict_set(util_dict *dict, const char *key, const char *val) +{ + util_dict *prev; + + if (!dict || !key) { + ERROR0("NULL values passed to util_dict_set()"); + return 0; + } + + prev = NULL; + while (dict) { + if (!dict->key || !strcmp(dict->key, key)) + break; + prev = dict; + dict = dict->next; + } + + if (!dict) { + dict = util_dict_new(); + if (!dict) { + ERROR0("unable to allocate new dictionary"); + return 0; + } + if (prev) + prev->next = dict; + } + + if (dict->key) + free (dict->val); + else if (!(dict->key = strdup(key))) { + if (prev) + prev->next = NULL; + util_dict_free (dict); + + ERROR0("unable to allocate new dictionary key"); + return 0; + } + + dict->val = strdup(val); + if (!dict->val) { + ERROR0("unable to allocate new dictionary value"); + return 0; + } + + return 1; +} + +/* given a dictionary, URL-encode each val and + stringify it in order as key=val&key=val... if val + is set, or just key&key if val is NULL. + TODO: Memory management needs overhaul. */ +char *util_dict_urlencode(util_dict *dict, char delim) +{ + char *res, *tmp; + char *enc; + int start = 1; + + for (res = NULL; dict; dict = dict->next) { + /* encode key */ + if (!dict->key) + continue; + if (start) { + if (!(res = malloc(strlen(dict->key) + 1))) { + return NULL; + } + sprintf(res, "%s", dict->key); + start = 0; + } else { + if (!(tmp = realloc(res, strlen(res) + strlen(dict->key) + 2))) { + free(res); + return NULL; + } else + res = tmp; + sprintf(res + strlen(res), "%c%s", delim, dict->key); + } + + /* encode value */ + if (!dict->val) + continue; + if (!(enc = util_url_escape(dict->val))) { + free(res); + return NULL; + } + + if (!(tmp = realloc(res, strlen(res) + strlen(enc) + 2))) { + free(enc); + free(res); + return NULL; + } else + res = tmp; + sprintf(res + strlen(res), "=%s", enc); + free(enc); + } + + return res; +} + +#ifndef HAVE_LOCALTIME_R +struct tm *localtime_r (const time_t *timep, struct tm *result) +{ + static mutex_t localtime_lock; + static int initialised = 0; + struct tm *tm; + + if (initialised == 0) + { + thread_mutex_create (&localtime_lock); + initialised = 1; + } + thread_mutex_lock (&localtime_lock); + tm = localtime (timep); + memcpy (result, tm, sizeof (*result)); + thread_mutex_unlock (&localtime_lock); + return result; +} +#endif diff --git a/src/util.h b/src/util.h new file mode 100644 index 00000000..1fd5d002 --- /dev/null +++ b/src/util.h @@ -0,0 +1,52 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#define XSLT_CONTENT 1 +#define HTML_CONTENT 2 + +int util_timed_wait_for_fd(int fd, int timeout); +int util_read_header(int sock, char *buff, unsigned long len); +int util_check_valid_extension(char *uri); +char *util_get_extension(char *path); +char *util_get_path_from_uri(char *uri); +char *util_get_path_from_normalised_uri(char *uri); +char *util_normalise_uri(char *uri); +char *util_base64_encode(char *data); +char *util_base64_decode(unsigned char *input); +char *util_bin_to_hex(unsigned char *data, int len); + +char *util_url_unescape(char *src); +char *util_url_escape(char *src); + +/* String dictionary type, without support for NULL keys, or multiple + * instances of the same key */ +typedef struct _util_dict { + char *key; + char *val; + struct _util_dict *next; +} util_dict; + +util_dict *util_dict_new(void); +void util_dict_free(util_dict *dict); +/* dict, key must not be NULL. */ +int util_dict_set(util_dict *dict, const char *key, const char *val); +const char *util_dict_get(util_dict *dict, const char *key); +char *util_dict_urlencode(util_dict *dict, char delim); + +#ifndef HAVE_LOCALTIME_R +struct tm *localtime_r (const time_t *timep, struct tm *result); +#endif + +#endif /* __UTIL_H__ */ diff --git a/src/xslt.c b/src/xslt.c new file mode 100644 index 00000000..26a0f678 --- /dev/null +++ b/src/xslt.c @@ -0,0 +1,191 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + + +#include "thread/thread.h" +#include "avl/avl.h" +#include "httpp/httpp.h" +#include "net/sock.h" + +#include "connection.h" + +#include "global.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" + +#define CATMODULE "xslt" + +#include "logging.h" + +typedef struct { + char *filename; + time_t last_modified; + time_t cache_age; + xsltStylesheetPtr stylesheet; +} stylesheet_cache_t; + +/* Keep it small... */ +#define CACHESIZE 3 + +stylesheet_cache_t cache[CACHESIZE]; +mutex_t xsltlock; + +void xslt_initialize() +{ + memset(cache, 0, sizeof(stylesheet_cache_t)*CACHESIZE); + thread_mutex_create("xslt", &xsltlock); +} + +void xslt_shutdown() { + int i; + + for(i=0; i < CACHESIZE; i++) { + if(cache[i].filename) + free(cache[i].filename); + if(cache[i].stylesheet) + xsltFreeStylesheet(cache[i].stylesheet); + } + + xsltCleanupGlobals(); +} + +static int evict_cache_entry() { + int i, age=0, oldest=0; + + for(i=0; i < CACHESIZE; i++) { + if(cache[i].cache_age > age) { + age = cache[i].cache_age; + oldest = i; + } + } + + xsltFreeStylesheet(cache[oldest].stylesheet); + free(cache[oldest].filename); + + return oldest; +} + +static xsltStylesheetPtr xslt_get_stylesheet(char *fn) { + int i; + int empty = -1; + struct stat file; + + if(stat(fn, &file)) { + DEBUG1("Error checking for stylesheet file: %s", strerror(errno)); + return NULL; + } + + for(i=0; i < CACHESIZE; i++) { + if(cache[i].filename) + { +#ifdef _WIN32 + if(!stricmp(fn, cache[i].filename)) +#else + if(!strcmp(fn, cache[i].filename)) +#endif + { + if(file.st_mtime > cache[i].last_modified) + { + xsltFreeStylesheet(cache[i].stylesheet); + + cache[i].last_modified = file.st_mtime; + cache[i].stylesheet = xsltParseStylesheetFile(fn); + cache[i].cache_age = time(NULL); + } + DEBUG1("Using cached sheet %i", i); + return cache[i].stylesheet; + } + } + else + empty = i; + } + + if(empty>=0) + i = empty; + else + i = evict_cache_entry(); + + cache[i].last_modified = file.st_mtime; + cache[i].filename = strdup(fn); + cache[i].stylesheet = xsltParseStylesheetFile(fn); + cache[i].cache_age = time(NULL); + return cache[i].stylesheet; +} + +void xslt_transform(xmlDocPtr doc, char *xslfilename, client_t *client) +{ + xmlOutputBufferPtr outputBuffer; + xmlDocPtr res; + xsltStylesheetPtr cur; + const char *params[16 + 1]; + size_t count,bytes; + + params[0] = NULL; + + xmlSubstituteEntitiesDefault(1); + xmlLoadExtDtdDefaultValue = 1; + + thread_mutex_lock(&xsltlock); + cur = xslt_get_stylesheet(xslfilename); + thread_mutex_unlock(&xsltlock); + + if (cur == NULL) { + bytes = sock_write_string(client->con->sock, + (char *)"Could not parse XSLT file"); + if(bytes > 0) client->con->sent_bytes += bytes; + + return; + } + + res = xsltApplyStylesheet(cur, doc, params); + + outputBuffer = xmlAllocOutputBuffer(NULL); + + count = xsltSaveResultTo(outputBuffer, res, cur); + + /* Add null byte to end. */ + bytes = xmlOutputBufferWrite(outputBuffer, 1, ""); + + if(sock_write_string(client->con->sock, + (char *)outputBuffer->buffer->content)) + client->con->sent_bytes += bytes; + + xmlOutputBufferClose(outputBuffer); + xmlFreeDoc(res); +} + diff --git a/src/xslt.h b/src/xslt.h new file mode 100644 index 00000000..8ad54766 --- /dev/null +++ b/src/xslt.h @@ -0,0 +1,40 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "thread/thread.h" +#include "avl/avl.h" +#include "httpp/httpp.h" +#include "net/sock.h" + + +#include "connection.h" + +#include "global.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" + + +void xslt_transform(xmlDocPtr doc, char *xslfilename, client_t *client); +void xslt_initialize(); +void xslt_shutdown(); + diff --git a/src/yp.c b/src/yp.c new file mode 100644 index 00000000..ea84e4f4 --- /dev/null +++ b/src/yp.c @@ -0,0 +1,903 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "logging.h" +#include "format.h" +#include "source.h" +#include "cfgfile.h" +#include "stats.h" +#include + +#ifdef WIN32 +#define snprintf _snprintf +#endif + +#define CATMODULE "yp" + +struct yp_server +{ + char *url; + unsigned url_timeout; + unsigned touch_interval; + int remove; + + CURL *curl; + struct ypdata_tag *mounts, *pending_mounts; + struct yp_server *next; + char curl_error[CURL_ERROR_SIZE]; +}; + + + +typedef struct ypdata_tag +{ + int remove; + int cmd_ok; + + char *sid; + char *mount; + char *url; + char *listen_url; + char *server_name; + char *server_desc; + char *server_genre; + char *cluster_password; + char *bitrate; + char *audio_info; + char *server_type; + char *current_song; + + struct yp_server *server; + time_t next_update; + unsigned touch_interval; + char *error_msg; + unsigned (*process)(struct ypdata_tag *yp, char *s, unsigned len); + + struct ypdata_tag *next; +} ypdata_t; + + +static rwlock_t yp_lock; +static mutex_t yp_pending_lock; + +static struct yp_server *active_yps = NULL, *pending_yps = NULL; +static int yp_update = 0; +static int yp_running; +static time_t now; +static thread_type *yp_thread; + +static void *yp_update_thread(void *arg); +static void add_yp_info (ypdata_t *yp, char *stat_name, void *info, int type); +static unsigned do_yp_remove (ypdata_t *yp, char *s, unsigned len); +static unsigned do_yp_add (ypdata_t *yp, char *s, unsigned len); +static unsigned do_yp_touch (ypdata_t *yp, char *s, unsigned len); +static void yp_destroy_ypdata(ypdata_t *ypdata); + + +/* curl callback used to parse headers coming back from the YP server */ +static int handle_returned_header (void *ptr, size_t size, size_t nmemb, void *stream) +{ + ypdata_t *yp = stream; + unsigned bytes = size * nmemb; + + /* DEBUG2 ("header from YP is \"%.*s\"", bytes, ptr); */ + if (strncmp (ptr, "YPResponse: 1", 13) == 0) + yp->cmd_ok = 1; + + if (strncmp (ptr, "YPMessage: ", 11) == 0) + { + unsigned len = bytes - 11; + free (yp->error_msg); + yp->error_msg = calloc (1, len); + if (yp->error_msg) + sscanf (ptr, "YPMessage: %[^\r\n]", yp->error_msg); + } + + if (yp->process == do_yp_add) + { + if (strncmp (ptr, "SID: ", 5) == 0) + { + unsigned len = bytes - 5; + free (yp->sid); + yp->sid = calloc (1, len); + if (yp->sid) + sscanf (ptr, "SID: %[^\r\n]", yp->sid); + } + if (strncmp (ptr, "TouchFreq: ", 11) == 0) + { + unsigned secs; + sscanf (ptr, "TouchFreq: %u", &secs); + if (secs < 30) + secs = 30; + DEBUG1 ("server touch interval is %u", secs); + yp->touch_interval = secs; + } + } + return (int)bytes; +} + + +/* capture returned data, but don't do anything with it, shouldn't be any */ +static int handle_returned_data (void *ptr, size_t size, size_t nmemb, void *stream) +{ + return (int)(size*nmemb); +} + + +/* search the active and pending YP server lists */ +static struct yp_server *find_yp_server (const char *url) +{ + struct yp_server *server; + + server = active_yps; + while (server) + { + if (strcmp (server->url, url) == 0) + return server; + server = server->next; + } + server = pending_yps; + while (server) + { + if (strcmp (server->url, url) == 0) + break; + server = server->next; + } + return server; +} + + +static void destroy_yp_server (struct yp_server *server) +{ + if (server == NULL) + return; + DEBUG1 ("Removing YP server entry for %s", server->url); + if (server->curl) + curl_easy_cleanup (server->curl); + if (server->mounts) WARN0 ("active ypdata not freed up"); + if (server->pending_mounts) WARN0 ("pending ypdata not freed up"); + free (server->url); + free (server); +} + + + +/* search for a ypdata entry corresponding to a specific mountpoint */ +static ypdata_t *find_yp_mount (struct yp_server *server, const char *mount) +{ + ypdata_t *yp = server->mounts; + while (yp) + { + if (strcmp (yp->mount, mount) == 0) + break; + yp = yp->next; + } + return yp; +} + + +void yp_recheck_config (ice_config_t *config) +{ + int i; + struct yp_server *server; + + DEBUG0("Updating YP configuration"); + thread_rwlock_rlock (&yp_lock); + + server = active_yps; + while (server) + { + server->remove = 1; + server = server->next; + } + /* for each yp url in config, check to see if one exists + if not, then add it. */ + for (i=0 ; i < config->num_yp_directories; i++) + { + server = find_yp_server (config->yp_url[i]); + if (server == NULL) + { + server = calloc (1, sizeof (struct yp_server)); + + if (server == NULL) + { + destroy_yp_server (server); + break; + } + server->url = strdup (config->yp_url[i]); + server->url_timeout = config->yp_url_timeout[i]; + server->touch_interval = config->yp_touch_interval[i]; + server->curl = curl_easy_init(); + if (server->curl == NULL) + { + destroy_yp_server (server); + break; + } + if (server->touch_interval < 30) + server->touch_interval = 30; + curl_easy_setopt (server->curl, CURLOPT_URL, server->url); + curl_easy_setopt (server->curl, CURLOPT_HEADERFUNCTION, handle_returned_header); + curl_easy_setopt (server->curl, CURLOPT_WRITEFUNCTION, handle_returned_data); + curl_easy_setopt (server->curl, CURLOPT_WRITEDATA, server->curl); + curl_easy_setopt (server->curl, CURLOPT_TIMEOUT, server->url_timeout); + curl_easy_setopt (server->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt (server->curl, CURLOPT_ERRORBUFFER, &(server->curl_error[0])); + server->next = pending_yps; + pending_yps = server; + INFO3 ("Adding new YP server \"%s\" (timeout %ds, default interval %ds)", + server->url, server->url_timeout, server->touch_interval); + } + else + { + server->remove = 0; + } + } + thread_rwlock_unlock (&yp_lock); + yp_update = 1; +} + + +void yp_initialize() +{ + ice_config_t *config = config_get_config(); + thread_rwlock_create (&yp_lock); + thread_mutex_create ("yp", &yp_pending_lock); + yp_recheck_config (config); + config_release_config (); + yp_thread = thread_create("YP Touch Thread", yp_update_thread, + (void *)NULL, THREAD_ATTACHED); +} + + + +/* handler for curl, checks if successful handling occurred */ +static int send_to_yp (const char *cmd, ypdata_t *yp, char *post) +{ + int curlcode; + struct yp_server *server = yp->server; + + /* DEBUG2 ("send YP (%s):%s", cmd, post); */ + yp->cmd_ok = 0; + curl_easy_setopt (server->curl, CURLOPT_POSTFIELDS, post); + curl_easy_setopt (server->curl, CURLOPT_WRITEHEADER, yp); + curlcode = curl_easy_perform (server->curl); + if (curlcode) + { + yp->process = do_yp_add; + yp->next_update += 60; + ERROR2 ("connection to %s failed with \"%s\"", server->url, server->curl_error); + return -1; + } + if (yp->cmd_ok == 0) + { + if (yp->error_msg == NULL) + yp->error_msg = strdup ("no response from server"); + yp->process = do_yp_add; + yp->next_update += 60; + ERROR3 ("YP %s on %s failed: %s", cmd, server->url, yp->error_msg); + return -1; + } + DEBUG2 ("YP %s at %s succeeded", cmd, server->url); + return 0; +} + + +/* routines for building and issues requests to the YP server */ +static unsigned do_yp_remove (ypdata_t *yp, char *s, unsigned len) +{ + if (yp->sid) + { + int ret = snprintf (s, len, "action=remove&sid=%s", yp->sid); + if (ret >= (signed)len) + return ret+1; + + INFO1 ("clearing up YP entry for %s", yp->mount); + send_to_yp ("remove", yp, s); + free (yp->sid); + yp->sid = NULL; + } + yp_update = 1; + yp->remove = 1; + yp->process = do_yp_add; + + return 0; +} + + +static unsigned do_yp_add (ypdata_t *yp, char *s, unsigned len) +{ + int ret; + + ret = snprintf (s, len, "action=add&sn=%s&genre=%s&cpswd=%s&desc=" + "%s&url=%s&listenurl=%s&type=%s&b=%s&%s\r\n", + yp->server_name, yp->server_genre, yp->cluster_password, + yp->server_desc, yp->url, yp->listen_url, + yp->server_type, yp->bitrate, yp->audio_info); + if (ret >= (signed)len) + return ret+1; + if (send_to_yp ("add", yp, s) == 0) + { + yp->process = do_yp_touch; + /* force first touch in 5 secs */ + yp->next_update = time(NULL) + 5; + } + + return 0; +} + + +static unsigned do_yp_touch (ypdata_t *yp, char *s, unsigned len) +{ + unsigned listeners = 0; + char *val, *artist, *title; + int ret; + + artist = (char *)stats_get_value (yp->mount, "artist"); + title = (char *)stats_get_value (yp->mount, "title"); + if (artist || title) + { + char *song; + char *separator = " - "; + if (artist == NULL) + { + artist = strdup(""); + separator = ""; + } + if (title == NULL) title = strdup(""); + song = malloc (strlen (artist) + strlen (title) + strlen (separator) +1); + if (song) + { + sprintf (song, "%s%s%s", artist, separator, title); + add_yp_info(yp, "yp_currently_playing", song, YP_CURRENT_SONG); + free (song); + } + } + free (artist); + free (title); + val = (char *)stats_get_value (yp->mount, "listeners"); + if (val) + { + listeners = atoi (val); + free (val); + } + ret = snprintf (s, len, "action=touch&sid=%s&st=%s&listeners=%u\r\n", + yp->sid, yp->current_song, listeners); + + if (ret >= (signed)len) + return ret+1; /* space required for above text and nul*/ + + send_to_yp ("touch", yp, s); + return 0; +} + + + +static void process_ypdata (struct yp_server *server, ypdata_t *yp) +{ + unsigned len = 512; + char *s = NULL, *tmp; + + if (now < yp->next_update) + return; + yp->next_update = now + yp->touch_interval; + + /* loop just in case the memory area isn't big enough */ + while (1) + { + unsigned ret; + if ((tmp = realloc (s, len)) == NULL) + return; + s = tmp; + + ret = yp->process (yp, s, len); + if (ret == 0) + { + free (s); + return; + } + len = ret; + } +} + + +static void yp_process_server (struct yp_server *server) +{ + ypdata_t *yp; + + /* DEBUG1("processing yp server %s", server->url); */ + yp = server->mounts; + while (yp) + { + now = time (NULL); + process_ypdata (server, yp); + yp = yp->next; + } +} + + + +static ypdata_t *create_yp_entry (source_t *source) +{ + ypdata_t *yp; + char *s; + + if (source->running == 0 || source->yp_public == 0) + return NULL; + yp = calloc (1, sizeof (ypdata_t)); + do + { + unsigned len = 512; + int ret; + char *url; + ice_config_t *config; + + if (yp == NULL) + break; + yp->mount = strdup (source->mount); + yp->server_name = strdup (""); + yp->server_desc = strdup (""); + yp->server_genre = strdup (""); + yp->bitrate = strdup (""); + yp->server_desc = strdup (""); + yp->server_type = strdup (""); + yp->cluster_password = strdup (""); + yp->url = strdup (""); + yp->current_song = strdup (""); + yp->audio_info = strdup (""); + yp->process = do_yp_add; + + url = malloc (len); + if (url == NULL) + break; + config = config_get_config(); + ret = snprintf (url, len, "http://%s:%d%s", config->hostname, config->port, source->mount); + if (ret >= (signed)len) + { + s = realloc (url, ++ret); + if (s) url = s; + snprintf (url, ret, "http://%s:%d%s", config->hostname, config->port, source->mount); + } + config_release_config(); + yp->listen_url = util_url_escape (url); + free (url); + if (yp->listen_url == NULL) + break; + + /* ice-* is icecast, icy-* is shoutcast */ + add_yp_info (yp, "server_type", source->format->format_description, YP_SERVER_TYPE); + if ((s = httpp_getvar(source->parser, "ice-name"))) { + add_yp_info (yp, "server_name", s, YP_SERVER_NAME); + } + if ((s = httpp_getvar(source->parser, "icy-name"))) { + add_yp_info (yp, "server_name", s, YP_SERVER_NAME); + } + if ((s = httpp_getvar(source->parser, "ice-url"))) { + add_yp_info(yp, "server_url", s, YP_SERVER_URL); + } + if ((s = httpp_getvar(source->parser, "icy-url"))) { + add_yp_info(yp, "server_url", s, YP_SERVER_URL); + } + if ((s = httpp_getvar(source->parser, "ice-genre"))) { + add_yp_info(yp, "genre", s, YP_SERVER_GENRE); + } + if ((s = httpp_getvar(source->parser, "icy-genre"))) { + add_yp_info(yp, "genre", s, YP_SERVER_GENRE); + } + if ((s = httpp_getvar(source->parser, "ice-bitrate"))) { + add_yp_info(yp, "bitrate", s, YP_BITRATE); + } + if ((s = httpp_getvar(source->parser, "icy-br"))) { + add_yp_info(yp, "bitrate", s, YP_BITRATE); + } + if ((s = httpp_getvar(source->parser, "ice-description"))) { + add_yp_info(yp, "server_description", s, YP_SERVER_DESC); + } + s = util_dict_urlencode (source->audio_info, '&'); + if (s) + add_yp_info (yp, "audio_info", s, YP_AUDIO_INFO); + free(s); + return yp; + } while (0); + + yp_destroy_ypdata (yp); + return NULL; +} + + +/* Check for changes in the YP servers configured */ +static void check_servers () +{ + struct yp_server *server = active_yps, **server_p = &active_yps; + + while (server) + { + if (server->remove) + { + struct yp_server *to_go = server; + DEBUG1 ("YP server \"%s\"removed", server->url); + *server_p = server->next; + server = server->next; + destroy_yp_server (to_go); + continue; + } + server_p = &server->next; + server = server->next; + } + /* add new server entries */ + while (pending_yps) + { + avl_node *node; + + server = pending_yps; + pending_yps = server->next; + + DEBUG1("Add pending yps %s", server->url); + server->next = active_yps; + active_yps = server; + + /* new YP server configured, need to populate with existing sources */ + avl_tree_rlock (global.source_tree); + node = avl_get_first (global.source_tree); + while (node) + { + ypdata_t *yp; + + source_t *source = node->key; + thread_mutex_lock (&source->lock); + if ((yp = create_yp_entry (source)) != NULL) + { + DEBUG1 ("Adding existing mount %s", source->mount); + yp->server = server; + yp->touch_interval = server->touch_interval; + yp->next = server->mounts; + server->mounts = yp; + } + thread_mutex_unlock (&source->lock); + node = avl_get_next (node); + } + avl_tree_unlock (global.source_tree); + } +} + + +static void add_pending_yp (struct yp_server *server) +{ + ypdata_t *current, *yp; + unsigned count = 0; + + if (server->pending_mounts == NULL) + return; + current = server->mounts; + server->mounts = server->pending_mounts; + server->pending_mounts = NULL; + yp = server->mounts; + while (1) + { + count++; + if (yp->next == NULL) + break; + yp = yp->next; + } + yp->next = current; + DEBUG2 ("%u YP entries added to %s", count, server->url); +} + + +static void delete_marked_yp (struct yp_server *server) +{ + ypdata_t *yp = server->mounts, **prev = &server->mounts; + + while (yp) + { + if (yp->remove) + { + ypdata_t *to_go = yp; + DEBUG2 ("removed %s from YP server %s", yp->mount, server->url); + *prev = yp->next; + yp = yp->next; + yp_destroy_ypdata (to_go); + continue; + } + prev = &yp->next; + yp = yp->next; + } +} + + +static void *yp_update_thread(void *arg) +{ + INFO0("YP update thread started"); + + yp_running = 1; + while (yp_running) + { + struct yp_server *server; + + thread_sleep (200000); + + /* do the YP communication */ + thread_rwlock_rlock (&yp_lock); + server = active_yps; + while (server) + { + /* DEBUG1 ("trying %s", server->url); */ + yp_process_server (server); + server = server->next; + } + thread_rwlock_unlock (&yp_lock); + + /* update the local YP structure */ + if (yp_update) + { + thread_rwlock_wlock (&yp_lock); + check_servers (); + server = active_yps; + while (server) + { + /* DEBUG1 ("Checking yps %s", server->url); */ + add_pending_yp (server); + delete_marked_yp (server); + server = server->next; + } + yp_update = 0; + thread_rwlock_unlock (&yp_lock); + } + } + thread_rwlock_destroy (&yp_lock); + thread_mutex_destroy (&yp_pending_lock); + /* free server and ypdata left */ + while (active_yps) + { + struct yp_server *server = active_yps; + active_yps = server->next; + destroy_yp_server (server); + } + return NULL; +} + + + +static void yp_destroy_ypdata(ypdata_t *ypdata) +{ + if (ypdata) { + if (ypdata->mount) { + free (ypdata->mount); + } + if (ypdata->url) { + free (ypdata->url); + } + if (ypdata->sid) { + free(ypdata->sid); + } + if (ypdata->server_name) { + free(ypdata->server_name); + } + if (ypdata->server_desc) { + free(ypdata->server_desc); + } + if (ypdata->server_genre) { + free(ypdata->server_genre); + } + if (ypdata->cluster_password) { + free(ypdata->cluster_password); + } + if (ypdata->listen_url) { + free(ypdata->listen_url); + } + if (ypdata->current_song) { + free(ypdata->current_song); + } + if (ypdata->bitrate) { + free(ypdata->bitrate); + } + if (ypdata->server_type) { + free(ypdata->server_type); + } + if (ypdata->audio_info) { + free(ypdata->audio_info); + } + free (ypdata->error_msg); + free (ypdata); + } +} + +static void add_yp_info (ypdata_t *yp, char *stat_name, void *info, int type) +{ + char *escaped; + + if (!info) + return; + + /* DEBUG2 ("stat %s with %s", stat_name, info); */ + switch (type) + { + case YP_SERVER_NAME: + escaped = util_url_escape(info); + if (escaped) + { + if (yp->server_name) + free (yp->server_name); + yp->server_name = escaped; + stats_event (yp->mount, stat_name, (char *)info); + } + break; + case YP_SERVER_DESC: + escaped = util_url_escape(info); + if (escaped) + { + if (yp->server_desc) + free (yp->server_desc); + yp->server_desc = escaped; + stats_event(yp->mount, stat_name, (char *)info); + } + break; + case YP_SERVER_GENRE: + escaped = util_url_escape(info); + if (escaped) + { + if (yp->server_genre) + free (yp->server_genre); + yp->server_genre = escaped; + stats_event (yp->mount, stat_name, (char *)info); + } + break; + case YP_SERVER_URL: + escaped = util_url_escape(info); + if (escaped) + { + if (yp->url) + free (yp->url); + yp->url = escaped; + stats_event (yp->mount, stat_name, (char *)info); + } + break; + case YP_BITRATE: + escaped = util_url_escape(info); + if (escaped) + { + if (yp->bitrate) + free (yp->bitrate); + yp->bitrate = escaped; + stats_event (yp->mount, stat_name, (char *)info); + } + break; + case YP_AUDIO_INFO: + if (yp->audio_info) + free (yp->audio_info); + yp->audio_info = strdup (info); + break; + case YP_SERVER_TYPE: + escaped = util_url_escape(info); + if (escaped) + { + if (yp->server_type) + free (yp->server_type); + yp->server_type = escaped; + } + break; + case YP_CURRENT_SONG: + escaped = util_url_escape(info); + if (escaped) + { + if (yp->current_song) + free (yp->current_song); + yp->current_song = escaped; + stats_event (yp->mount, "yp_currently_playing", (char *)info); + } + break; + } +} + + +/* Add YP entries to active servers */ +void yp_add (source_t *source) +{ + struct yp_server *server; + + /* make sure YP thread is not modifying the lists */ + thread_rwlock_rlock (&yp_lock); + + /* make sure we don't race against another yp_add */ + thread_mutex_lock (&yp_pending_lock); + server = active_yps; + while (server) + { + ypdata_t *yp; + /* add new ypdata to each servers pending yp */ + if ((yp = create_yp_entry (source)) != NULL) + { + DEBUG2 ("Adding %s to %s", source->mount, server->url); + yp->server = server; + yp->touch_interval = server->touch_interval; + yp->next = server->pending_mounts; + server->pending_mounts = yp; + yp_update = 1; + } + server = server->next; + } + thread_mutex_unlock (&yp_pending_lock); + thread_rwlock_unlock (&yp_lock); + /* DEBUG1 ("Added %s to YP ", source->mount); */ +} + + + +/* Mark an existing entry in the YP list as to be marked for deletion */ +void yp_remove (const char *mount) +{ + struct yp_server *server = active_yps; + + thread_rwlock_rlock (&yp_lock); + while (server) + { + ypdata_t *yp = find_yp_mount (server, mount); + if (yp) + { + DEBUG2 ("mark %s on YP %s", mount, server->url); + yp->process = do_yp_remove; + yp->next_update = 0; + } + server = server->next; + } + thread_rwlock_unlock (&yp_lock); +} + + +/* This is similar to yp_remove, but we force a touch + * attempt */ +void yp_touch (const char *mount) +{ + struct yp_server *server = active_yps; + time_t trigger; + + thread_rwlock_rlock (&yp_lock); + /* do update in 3 secs, give stats chance to update */ + trigger = time(NULL) + 3; + while (server) + { + ypdata_t *yp = find_yp_mount (server, mount); + if (yp) + { + /* only force if touch */ + if (yp->process == do_yp_touch) + yp->next_update = trigger; + } + server = server->next; + } + thread_rwlock_unlock (&yp_lock); +} + + +void yp_shutdown () +{ + yp_running = 0; + yp_update = 1; + if (yp_thread) + thread_join (yp_thread); + curl_global_cleanup(); + INFO0 ("YP thread down"); +} + diff --git a/src/yp.h b/src/yp.h new file mode 100644 index 00000000..87d590d9 --- /dev/null +++ b/src/yp.h @@ -0,0 +1,53 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2000-2004, Jack Moffitt , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +#ifndef __YP_H__ +#define __YP_H__ + +#include + +#define YP_SERVER_NAME 1 +#define YP_SERVER_DESC 2 +#define YP_SERVER_GENRE 3 +#define YP_SERVER_URL 4 +#define YP_BITRATE 5 +#define YP_AUDIO_INFO 6 +#define YP_SERVER_TYPE 7 +#define YP_CURRENT_SONG 8 + +struct source_tag; + +#define YP_ADD_ALL -1 + +#ifdef USE_YP +void yp_add (struct source_tag *source); +void yp_remove (const char *mount); +void yp_touch (const char *mount); +void yp_recheck_config (ice_config_t *config); +void yp_initialize(); +void yp_shutdown(); + +#else + +#define yp_add(x) do{}while(0) +#define yp_remove(x) do{}while(0) +#define yp_touch(x) do{}while(0) +#define yp_recheck_config(x) do{}while(0) +#define yp_initialize() do{}while(0) +#define yp_shutdown() do{}while(0) + +#endif /* USE_YP */ + +#endif + + diff --git a/web/Makefile.am b/web/Makefile.am new file mode 100644 index 00000000..4ecf9714 --- /dev/null +++ b/web/Makefile.am @@ -0,0 +1,14 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = foreign + +webdir = $(pkgdatadir)/web +dist_web_DATA = status.xsl \ + status2.xsl \ + corner_bottomleft.jpg \ + corner_bottomright.jpg \ + corner_topleft.jpg \ + corner_topright.jpg \ + icecast.png \ + key.gif \ + style.css diff --git a/web/auth.xsl b/web/auth.xsl new file mode 100755 index 00000000..d6a8fdd0 --- /dev/null +++ b/web/auth.xsl @@ -0,0 +1,56 @@ + + + + + +Icecast Streaming Media Server + + + + + + + + +
    +

    Authorization Page

    +
    +
    + +
    +
    + + + + + +

    ()

    +
    + + + + +
    Username :
    Password :
    + +
    +
    +
    + +

    - Not Connected

    +
    +
    +

    +

    +
    +
    +
    + +
    +
    +



    +
    +
    Support icecast development at www.icecast.org
    + + +
    +
    diff --git a/web/corner_bottomleft.jpg b/web/corner_bottomleft.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ca66106df3e77f1926f6d0ba514526d14f0de686 GIT binary patch literal 8164 zcmeHMdu&_P89(=4`}!F>&Z8|%t8UUXMpONa-6U4hG|wh1rX@|%NuAST1zK-uT zaidMN4Cpoqp%xed4b~TB160(O4KXA}p=Teq>-z(!--HY!VykN1 z9~YxMw`E$CMQK{ha-D83@_W63kk=RTc5*&w_~8%K5p$4I$5Rx94$>vN_Jhn)8!JL$ zDMs^+E%%>VvE)Csy4-)jLi3Yqr%SeJK{Z$Dv$7n93&=|5a@SxO(aNXDMko~D0~RRd zJh~5AjYgx%Xf>Iv4vX31XtrCe_U2WMjm?dXs~lF6v`QteFR408LAc04vKM5$_1D2A{tF_DEyv>|Q_HwExhFfi-Q-6Q`pcf!w`s z4;t1U*m~vSXE7W8 zKYaf5Pk!343sQ;_WLPZPAOldk+k5ZH`HS&(^Zo0YiK6^>@IDBP-*>Q|o2^ zqmm0>?tyd31?9KM%8)S~s1Kd_)4BAo-Z*}xU)u9KL(+ci!aa{Xf4uMTrEAvhD{l`! zzUou&V({p9|8@E0L+5w??0Z-5;O#F?O|Q#*e8+Rro$`|pe#q=p(4X6$QO+p~#w|-r zqXU*k*T7g)^V!-n90({0x~9Zjp&RbPa*-56qK+au~g$6di;qJ2d{Dp zeFMs(PLOb1OB~|O-+@Lb3mez6iTNWIFDp|19~nUgn?`thBNX29mM9~kFb;qTy;04Y z=4#*~vc;%9Z^k4u28B9TOd?pDYBr-OltD5QAg2(I;>d+MK!&%?>r*+e*uO%jT%jn? zoWx`%pGrZ6r@}_Xd?qe!j_!&9rS+SL$v&WPaG*)pp;KZeiQ71h^9i|}9+1VfMq^jd zL~^<)$22RYgcvVvON~oFQ5ECpi452ckPUS_44;e0d>j~Pa(9;3l+CI#=3vQKI>|wGqvy}$bE(PU9g{GW1}+A_ zk`tDad|Eh>(POYAxhhy@XLrAxD1h6F-hMK1qQ3QR=mJ_ z3$lP#!AV#<+G|Jq<;5F+w5z8;3yw(G>q*SwknD%8UPMvY@;S5xO+#CTERj|g)EfVT z28TM48$n1zh?AICPZtB6GVm5a!K;45=(H?nLmp2?a!2sXw>u`LJ%vct<8ymGsHd-x zjl^bnnTztUxAr>!^2*PhoDlDIZV!gN;p`wkEsW0R_^tD|O~&SDV%>3P-v)C}A2HY?lKGs(@lvd}b#S5;iwhx;qASZ>)a9E!pX3q z%?L8*cXzq{u8xj%Tp|}q^K)WuhV!|DPB58^CqffLBbq^=dY#(D=H}+ya~*Cmm-P6$ zySqJJzsK))frd-koslC2S4LU`;}J)M9i?s+cnCWP959fXBSQ_rbaFyg7Q_q}fn!qc zb>{OzJQVN-ySk#C>sbjyUxoM#dNzY zb$0Xk!mFp>JNo9MFU+hszw^Y}_bs=6F#E&ZhrT&<&&Y@AQyXXXn@)afpYhJmTlSv6 x>*W)%uWz{UtYvZa@$Z~`>gk8KxA&ZCqXUusGWzQ5{I%B_j!ku6>N%pE`!~qj+mQeO literal 0 HcmV?d00001 diff --git a/web/corner_bottomright.jpg b/web/corner_bottomright.jpg new file mode 100644 index 0000000000000000000000000000000000000000..77174428ddbd36556b95e81c227860ccfaa9e347 GIT binary patch literal 8155 zcmeHMdu&_P8UOCRe#f!nJo*|`Hz|#`X@178Q!8mm^Jt7}8q&1M9&GdD+A*TdVSjXmLD{rUy8F(#wquu| z&71n4k9E#H-}8LucfWJ)^*zd4${(TSy3x=mFf0RO_ygq(cW|VbN&tkz@Oc0*fSzdu z4tos#fLQ}t+6Ra-pQ-e3Vm@2aVNu5e*g`v2iSbtYckWMtreymdUSGnBBNDIbbucbQ z1%7-+l*RmvnBxOZH+bD{U(oFdx&yq2c6k)3LPZY7}7_JnX zt~!2Z#Zvst+Uocbi&C6SH(T+|A||xd#H=ib;dwCAxqNLX46O23uuux(x1l|wybb%n zY%my%2D8yX}4G`?WD1^ zt0uPp#)0xHuo=J#R*vZc*2Zu)M)?Cyn^8_O0~n>2p@mKu;l>2fXmxsn(bR-#P%C7B z<;ubqU^EQJYFMpaXV7xy9u(R*O=qh|J9tB+t?TAJUS0cvhkvqp-8CH(C!#$*Y458; z`t|p1fB(z}vAlo9Bgb#=9{$c`eB`-#dBe)rcAORd_T;|jUoZUW!%_ReNAGy*{*%A^ z^GA>U^o2Lxxwtd2>&`=uKmFpH=ROW>nM%&>KYa9=Q@?-r?>1mrj8;Qq((AN-8bYV1 zRf7@S+|{P_?m5s-A~-Se>ia#{M9)Zj?;G0Q5zG5NSg#`i=)3*Ty@o-|uZ)lE*dPmM zD$<+b=F4CAjSpV6;-R|^UHZWV@!X?Fl?COzVe8Vm=)rZ- zyEzortf#l9rF4@j7b4@%mt<Rh0b9IGYOTB(b zX(Mg1VN@#U<5($)~vBlL$q$SS7-+Q3K@lyVmur2HO zDaWzIq0#&UX@DH=TFVyhJtAIKr2IcJiVSfLWN)Js+3%Jp1EmNKhzWbGT1%R1kqdNx zQ7zXHk_%y@&XtgW3sbEo*a2CPA%!&!0>r@qK9rF?^YT*7E8ExTR4bGPr638JEu_fo-xj?ohE&OJA|`u~BFO=hxIL%EY?Aaz*(xODYI?IQW=b>(1tyX+WjUdB z`E)8K;LI!QjBio7hZIp*} zbEF`PTZF72MPwn444AwnCzO;;sxsjaNhp)#@p7f-&lRNfkd7+#bL}p53 zB7K5uj*2oS)F=2&c#CH)SEuXIMw|gt+Mmdqh?=+@m6{33?!|_HRbDG@60`%UJWYmqWF-|DeG`0Gxp7 zjOk5BP@gP%e8S)x0E6%Ntz$E?oC~^K*}OAC-nyMJG2<#maxRb4?Sj66VlEP!6=Xgt z;J(^#zwonP+WAzx-#+CJyTiF5VJ0=UTN1YKzJ4;cdp5Q)ZXeiW>KmZ09O%Z8NTp># z%JV`#*55TW(M80?AWAZV9N~+ZbT%I>_S>mJ5c`Dfvh!3$p6$0!hsSvtH?!H4%zK@^ zPOqb<=L>v7ie!X2QJUpFPQM*pO7TQ+VtBOV5UGB9X<~D8bI!RQrzj;|o{bwfy4+ru z*XuwHNB)+q94R`o`E@uR4J0*PF{{BNHBI5bflNFdY8a;@rE+pg%<>U@M9Tg4LLn6o z`rQ8B-e|z*@OgYOhsP7|bwu5PghTLc2zdPg!HaUci7(GPk_(5IAF+G~G8q?+%JY55 z2#15iVyuu6vT|s6lZseejN!1k@;{|uZ_F=5W8Me}!2c-$^pX2p>kOymA`n*13+Jlx z5LN`(Rk>*-oGV`%gVcp_1Fi;Kbs z7SG}|q-b?oT68+S-k{g(j7F0Q&p>Mn!*QHeqc!Su#%7bz)NCbZn^rs_#ZtPMRjSsm zLRFn@;$h&@*(R%;1UytCXW3N+Pc`wVvx-5w3^~Xo(O38kPC{qXwzhS8pi6JN+yH8i z{X+kOAKic45ucj>+>3WzvRt_F*zK!-Gkk39;NsQm?mckg_+#v?@z?_FS56;(;&A5| zzjbzg>X}`u-}&)Nrwb4KWrF?n)Kx3T7c#3hjZN%1b?ia5<&NVE|LlJGqw8Kc^z!uL r^U~Tk_W$tW+;_im&yCin-W|I7ZsF8d`q(#HLhA>{zrD{epe+0w@R8T` literal 0 HcmV?d00001 diff --git a/web/corner_topleft.jpg b/web/corner_topleft.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7bac781e98132b394a2db6d7bcbeeff82322399f GIT binary patch literal 8149 zcmeHMdvF`Y8Q;@Mr-x<9vgHuyG+Z3VN+^2TvTPA!{7P(yaIodXLxEI}ldM?MDR-w> z$+XiD@|aGhKzNjCr(^4i!ekUX|xS zoR4zsni*c=#Th=w23#KG^LV;~9&gYSV7;W%*$wK5-bX3p$udIwG$p^*eRR2x6`?Q` zt;WVw#!oFM$4@P)j32O2zR#3Y!q6rD%;-qti169c}Iep^erqZuaU1?ufLsZM@gV*!MhfaBxX`>%`G$XP2<; z%_05LhbBKb^|L+~0q={pY{T|LOcn$0Pf9{OHkR zzxwlqrw_gQ)_WhVO>Elvjqkkh+OOZg7+AF~Ik)SvXAU2K`!D~nA&my2)smR>jLuI& zSnO@qLIfMzT6Dg9_t&MsMcJ#SjYksjc z_U@tMKh|bGUjMUC_P=C5;7SO!_0DGm6}a1W3}*!a~GSmGi_mzg6P+2>(uKi#mS7 zah5qGntuX~P!2Y%c?*38i{}-||Bs9ygIy!Mtq}@ub!EyxD2xMOqPaz>s^%i#BC@q8 zme*mD9)m`iD<%=FNu`?56v`qArJ$w}hvLYIydcBd<>jT4SG2FvsZ=Nm)F3gL&8O4w zz*AwOd_Ef&$DD5`249gzXM8LFNTW5X4`^+|!x=c-r?FQk&GESJq^z{Wf>iM@{#9?*@E zyu`2KvYZf+xHvFScyo?Zl}(B==3vQKCdtC%YR{j}3+eF4bQp$G!NuTLb3$U0tKu+` zv=ojct9X_e2X`y z*N^n~FJ8SPUD^E?<4lA-p5!V{#R1sjc@%}Mo<(cW4D=r&xuqc3B2vQkR zo8-E3s_5Z#fzzLYpZ3k8Gm?}Gy4_jP6~RBYT`@l6E<|!}ugl{`EBXt$NNkpq*eC~k zYMVh89NRJ*>y115Z#J#yC$5~}#+gW^B~B1o zPK@=n4NbIRaUlqj3@1g{LMELRgM~f^F$h8*v)vAss7SMYj_J@ER)UReHYKq>SC7l* z?CiXeO$d<;H^&RJtk>mtfJ-5s2u=*IR2>4<=TIj$H#g^+>vZu#((Uc-?R9&6ZlBKy z8cuO@R*DpyS#b%BM*>Mrm&~g0NKF$sU?3Aqh8l+H2&tTu;JIq)0nP_61~M5pjLNbrfDsA> zhxu4O!)2wh;hPo2;(QE-%@zMa3i`W!Ts-FS;sE@g5x@%keyg3qwA>iNih03Y6&}C} z0J}>s4Tp2}OMQ@Z-`G3tW_`Xy8D#T^sQ_viI^QpMfyaj)5B3GK4QlX(oYoUcJ zI4v$ZMn?+6==BD@o-rCtCb;^nE)-4EI<3ygFvdoc(bQ+A2muz&fBk0Jf^tp}dJAhbWcx9NLFOV literal 0 HcmV?d00001 diff --git a/web/corner_topright.jpg b/web/corner_topright.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a33a0325ad5fb91e43e7cc5e697418d77c118107 GIT binary patch literal 8151 zcmeHMZEzb$8GcWvZ^^Rdk2I8-aB&lhf$H0`Y>i#l@kcC(n$%Ge7luis(@9n==@jV{ zD;a(yZksSurZX*}w3HulOJQjLv}p@J82XW>l)x}Fg%A?xfRnUPl7QRNKp3XSyL%^D zO5Fj$bu_5s1#F=cYk~1q`-cvvL95z+3!gv1iX#%Q%5x+k z#szN6j3|l488OcVT^{gxJiQ^0H{=O&UfS{Yp*pZf8D%_K1~{sz`fWYRF87Hd7CpnN zvFU5$XI3r8&#bA9AF)v5WO_7J-gBkb_s~P%`Tmc8{r1)1#vQ4-!{7Ms4_-U-$G_V_qrqskG$w;y7oZ`m z^|os53)-Iwt!8)tLl$GKrE5wzv+7ROKu@^35v8NBLjCwxScD9Op8JM)MD( z0rI$CtytJU5%G#5`TvnoWQc1ZyBej)PPa@MDMfHVOf4On;<8j~C8Xxd*buNPYsG`S*Rk$`)p!6lPxR)A{`S=y zcB0cfTubs0cX*nqBoRk&gBKx=8$AbGUs z3!eQ9{;qGIoROq_$nDM*T`}@Y+r^7ncR7}KdtDwk3=Ws`F@9E%xVV5j>X75FKYY=_ zr4vJr9f61^k{=aj(v$lO!q)wFMEU)*{6NAne4BZ2n7VSJ8)q_|k%U5#6N>y$_vlnN z5tlmy=>; zXRde)jz7#@z&kfT&eCqliRK!1Nc=yCRX zdwHkVo9K7OJ;9_?=Y5H;&42gUE2p-kukvRu&0qF@<>FU0)39US5I6mKWH7(^Q)^5E3z0{!*)~r9-V-`~ z?7i64Tc6k+EieamJ=8P((`O~l)3Wqtzxcw!-H***dhTBN_3*VQ^TLItxkFE${o?7F n-+k}t3y$L-zH)S5>hpVoPr~Jay_VO`TS}J=XP3e+%4h!#E;!h* literal 0 HcmV?d00001 diff --git a/web/icecast.png b/web/icecast.png new file mode 100644 index 0000000000000000000000000000000000000000..327f29f04d52d98606644032a085ffadef4f3691 GIT binary patch literal 5611 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 Ub=U=0djZYmxz5+);aVi+e_-zpd;kCd literal 0 HcmV?d00001 diff --git a/web/key.gif b/web/key.gif new file mode 100644 index 0000000000000000000000000000000000000000..497d6f6d7b9c0448df002acadb1261639fa8cb94 GIT binary patch literal 1517 zcmVnH)(c;n4;?mQ{020Ok z5xxKpy#Nl^0TILj5yJry%K{O{0uj*z70?3`%>xs`0~5gm5z_<}(*zdK1QgE%6~6-# zy#o=w1QF2%7t93|!37k*1r)yp6TAfy)CL&T1{cx>8Nmh>*#{cH1{b~t6xs+I#0MC| z2N=Hx7{3P=$p{(82o}c)7|#kEz6lq?3L3`?8OsbE(+(cW4IaP@8@LP^zYQF~4j#!6 z9o`cqzz`qG6CuYFAixtNz9TNwEjz#}HrGK--ceo7OI6`lV$4%q&sbvIVr$bgNoIKjNXx#;Fq1*lbPJ4soA2Y-m|yozQNeGu|*m%s5HJJW16>RL@IV+E!%eYIxOWa@>4|(|m>IsIK9tuG0)9 z!WJvQ7An6MD!>;m!y7TUA2P%zJK{c7&t-Dd9XPrlHM$@+ydyfjCqB9)J-{nQxhq7u zE=IgBNxLvgx-CfBsHNP#z1gp?+qSmh$;sZ!%i++_-_FkA)6?SB)!@|B;@8*V*Vp3N z+2Y#T$-~38v9YhJs=vCry|}o%x3{RIq^6>xrJZJ((1{e$(V|3zk`f|hcrhTCPoKuGU>`zt_^tt~CmkkGn4pFf9tYH@ zr3*{fq(_JC*j+=g$-t8d6(*P|6~|YL1JXvu%g4Ae<9+t*xtmPS-afknX*MvVhr)*t zRx|`I6Dkz0FqUA({QS-MC?+HZ*66e`XdCPmu`|zfgfe6MFQhQMss~As|A? z7CS4VN4HZ)jwerTvadPAq8U5gGzGo?Xrzshk0`|_lsTS!jDz zzdwJbRXE&ZkwD=A!%aA`L@+{n>mjEE8XFK`$}!1QsLV3VAj1qZ!wBP{Fvb{@N-wmC z^Ak-8{K3sPu|RMIgU9JIffsPxB8enY zOfd(%LP#Roz_ZRY>% + + + + +Icecast Streaming Media Server + + + + + + + + +
    +

    Icecast Status Page

    +
    +
    + +
    +
    + + + +

    + +() + +

    + + + + + + + + + + + + + + + + + + + + + + + + +
    Stream Title:
    Stream Description:
    Stream Type:
    Bitrate:
    Stream Listeners:
    Stream Genre:
    Stream URL:
    Current Song: + -
    Listen: + + +Click to Listen + + +Click to Listen + + +
    +
    + +

    - Not Connected

    +
    +
    +

    +

    +
    +
    +
    + +
    +
    +



    +
    +
    Support icecast development at www.icecast.org
    + + +
    + diff --git a/web/status2.xsl b/web/status2.xsl new file mode 100755 index 00000000..9a6fb757 --- /dev/null +++ b/web/status2.xsl @@ -0,0 +1,12 @@ + + + +
    +MountPoint,Connections,Stream Name,Current Listeners,Description,Currently Playing,Stream URL 
    +Global,Client: Source: ,,,,
    +
    +,,,,, - ,
    +
    +
    +
    +
    diff --git a/web/style.css b/web/style.css new file mode 100644 index 00000000..5a450ee0 --- /dev/null +++ b/web/style.css @@ -0,0 +1,198 @@ +/****************************************************************************** + + This file styles the bar that goes across the top of all Xiph.Org + pages. + + The style that comes from this was first (to my knowledge) at + http://alistapart.com/stories/practicalcss/ in the + "Splitting the Difference" section. + +******************************************************************************/ + +/* This effect doesn't work at all if all content is pinched in a bit. */ +html, body { + margin: 0; + padding: 0; +} + +.xiphnav { + font-family: Verdana, sans-serif; + font-weight: normal; + padding: .25em; + margin-bottom: .5em; + border-bottom: 1px solid #000; + color: #000; + background: #aaa; +} +h2 { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: bold; + font-size: 250%; + color: #FFFFFF; +} +.nav { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: bold; + font-size: 110%; + color: #FFFFFF; +} +.nav:hover { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: bold; + color: #F8EF64; +} +.xiphnav_a { + text-decoration: none; + font-weight: normal; + color: #000; +} +.news { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: normal; + color: #FFFFFF; +} +.newsheader { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: normal; + font-size: 110%; + color: #F8EF64; + background: #444444; +} +.streamtd { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: normal; + font-size: 85%; + color: #FFFFFF; + padding:15px; +} +.streamtd_alt { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: normal; + font-size: 85%; + color: #FFFFFF; +} + +.streamtd_alt_2 { + font-family: Verdana, sans-serif; + text-decoration: underline; + font-weight: normal; + font-size: 85%; + color: #FFFFFF; +} + +td { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: normal; + color: #FFFFFF; +} +.roundcont { + width: 90%; + background-color: #656565; + color: #fff; +} + +.newscontent { + margin: 0 20px; +} +.newscontent h3 { + margin: 10px 0px; + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: bold; + font-size: 110%; + color: #F8EF64; + border-bottom: 3px dashed #000000; +} +.newscontent h4 { + margin: 10px 0px; + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: bold; + font-size: 110%; + color: #F8EF64; +} +.newscontent p { + margin: 0 0; + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: none; + font-size: 90%; +} +.newscontent td { + margin: 0 0; + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: none; + font-size: 90%; +} +.newscontent td.streamdata { + margin: 0 0; + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: none; + font-size: 90%; + color: #F8EF64; +} +.newscontent a { + font-family: Verdana, sans-serif; + text-decoration: underline; + font-weight: bold; + color: #F8EF64; +} +.newscontent a:hover { + font-family: Verdana, sans-serif; + text-decoration: underline; + font-weight: bold; + color: #FFFFFF; +} +.newscontent a.nav2 { + font-family: Verdana, sans-serif; + text-decoration: none; + font-weight: bold; + background: #444444; + color: #F8EF64; +} +.newscontent a.nav2:hover { + font-family: Verdana, sans-serif; + text-decoration: none; + background: #777777; + font-weight: bold; + color: #FFFFFF; +} +.poster { + font-family: Verdana, sans-serif; + margin: 0px 0px; + display: block; + text-decoration: none; + font-size: 100%; + color: #F8EF64; + border-top: 3px dashed #000000; +} +.roundcont p { + margin: 10px 50px; +} + +.roundtop { + background: url(corner_topright.jpg) no-repeat top right; +} + +.roundbottom { + background: url(corner_bottomright.jpg) no-repeat top right; +} + +img.corner { + width: 15px; + height: 15px; + border: none; + display: block !important; +} + + diff --git a/win32/ConfigTab.cpp b/win32/ConfigTab.cpp new file mode 100644 index 00000000..2ad7fe53 --- /dev/null +++ b/win32/ConfigTab.cpp @@ -0,0 +1,79 @@ +// ConfigTab.cpp : implementation file +// + +#include "stdafx.h" +#include "Icecast2win.h" +#include "ConfigTab.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CConfigTab dialog + + +CConfigTab::CConfigTab(CWnd* pParent /*=NULL*/) + : CTabPageSSL(CConfigTab::IDD, pParent) +{ + //{{AFX_DATA_INIT(CConfigTab) + m_Config = _T(""); + //}}AFX_DATA_INIT +} + + +void CConfigTab::DoDataExchange(CDataExchange* pDX) +{ + CTabPageSSL::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CConfigTab) + DDX_Control(pDX, IDC_CONFIG, m_ConfigCtrl); + DDX_Text(pDX, IDC_CONFIG, m_Config); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CConfigTab, CTabPageSSL) + //{{AFX_MSG_MAP(CConfigTab) + ON_EN_KILLFOCUS(IDC_CONFIG, OnKillfocusConfig) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CConfigTab message handlers + +void CConfigTab::SaveConfiguration() +{ + // TODO: Add your command handler code here + // TODO: Add your control notification handler code here + FILE *filep; + char buffer[2046] = ""; + CIcecast2winApp *myApp = (CIcecast2winApp *)AfxGetApp(); + + UpdateData(TRUE); + if (m_Config != "") { + filep = fopen(myApp->m_configFile, "w"); + if (filep) { + fputs(LPCSTR(m_Config), filep); + fclose(filep); + } + } +} + +BOOL CConfigTab::OnInitDialog() +{ + CTabPageSSL::OnInitDialog(); + + // TODO: Add extra initialization here + AddAnchor(IDC_CONFIG, TOP_LEFT, BOTTOM_RIGHT); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CConfigTab::OnKillfocusConfig() +{ + // TODO: Add your control notification handler code here + SaveConfiguration(); +} diff --git a/win32/ConfigTab.h b/win32/ConfigTab.h new file mode 100644 index 00000000..6dc5a2b5 --- /dev/null +++ b/win32/ConfigTab.h @@ -0,0 +1,50 @@ +#if !defined(AFX_CONFIGTAB_H__D8B0CC28_59FA_44E8_91A4_377C64F67DCF__INCLUDED_) +#define AFX_CONFIGTAB_H__D8B0CC28_59FA_44E8_91A4_377C64F67DCF__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// ConfigTab.h : header file +// +#include "TabPageSSL.h" + +///////////////////////////////////////////////////////////////////////////// +// CConfigTab dialog + +class CConfigTab : public CTabPageSSL +{ +// Construction +public: + void SaveConfiguration(); + CConfigTab(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CConfigTab) + enum { IDD = IDD_CONFIGDIALOG }; + CEdit m_ConfigCtrl; + CString m_Config; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CConfigTab) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CConfigTab) + virtual BOOL OnInitDialog(); + afx_msg void OnKillfocusConfig(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CONFIGTAB_H__D8B0CC28_59FA_44E8_91A4_377C64F67DCF__INCLUDED_) diff --git a/win32/Icecast2win.clw b/win32/Icecast2win.clw new file mode 100755 index 00000000..eef4a237 --- /dev/null +++ b/win32/Icecast2win.clw @@ -0,0 +1,152 @@ +; CLW file contains information for the MFC ClassWizard + +[General Info] +Version=1 +LastClass=CStatus +LastTemplate=CDialog +NewFileInclude1=#include "stdafx.h" +NewFileInclude2=#include "Icecast2win.h" + +ClassCount=6 +Class1=CIcecast2winApp +Class2=CIcecast2winDlg +Class3=CAboutDlg + +ResourceCount=10 +Resource1=IDD_SSTATUS +Resource2=IDR_MAINFRAME +Resource3=IDD_ABOUTBOX +Resource4=IDR_MENU2 +Class4=CStatus +Resource5=IDR_TRAY +Class5=CConfigTab +Class6=CStatsTab +Resource6=IDR_MENU4 +Resource7=IDD_ICECAST2WIN_DIALOG +Resource8=IDD_CONFIGDIALOG +Resource9=IDR_MENU3 +Resource10=IDD_STATSDIALOG + +[CLS:CIcecast2winApp] +Type=0 +HeaderFile=Icecast2win.h +ImplementationFile=Icecast2win.cpp +Filter=N + +[CLS:CIcecast2winDlg] +Type=0 +HeaderFile=Icecast2winDlg.h +ImplementationFile=Icecast2winDlg.cpp +Filter=C +LastObject=ID_ABOUT_HELP +BaseClass=CResizableDialog +VirtualFilter=dWC + +[CLS:CAboutDlg] +Type=0 +HeaderFile=Icecast2winDlg.h +ImplementationFile=Icecast2winDlg.cpp +Filter=D + +[DLG:IDD_ABOUTBOX] +Type=1 +Class=CAboutDlg +ControlCount=3 +Control1=IDC_STATIC,static,1342177283 +Control2=IDC_STATIC,static,1342308480 +Control3=IDOK,button,1342373889 + +[DLG:IDD_ICECAST2WIN_DIALOG] +Type=1 +Class=CIcecast2winDlg +ControlCount=8 +Control1=IDC_MAINTAB,SysTabControl32,1342177280 +Control2=IDC_START,button,1342242816 +Control3=IDC_AUTOSTART,button,1342251011 +Control4=IDC_STATIC,static,1342177294 +Control5=IDC_SERVERSTATUS,static,1342177294 +Control6=IDC_STATIC_SS,static,1342308865 +Control7=IDC_STATICBLACK,static,1342177294 +Control8=IDC_HIDESYSTRAY,button,1342242816 + +[DLG:IDD_SSTATUS] +Type=1 +Class=CStatus +ControlCount=5 +Control1=IDC_FILLER2,static,1342308352 +Control2=IDC_GLOBALSTAT_LIST,SysListView32,1350631425 +Control3=IDC_STATIC_GS,static,1342308353 +Control4=IDC_STATIC_RUN,static,1342308352 +Control5=IDC_RUNNINGFOR,static,1342308352 + +[CLS:CStatus] +Type=0 +HeaderFile=Status.h +ImplementationFile=Status.cpp +BaseClass=CTabPageSSL +Filter=D +LastObject=ID_POPUP_ADDTOGLOBALSTATLIST +VirtualFilter=dWC + +[DLG:IDD_CONFIGDIALOG] +Type=1 +Class=CConfigTab +ControlCount=1 +Control1=IDC_CONFIG,edit,1352732868 + +[CLS:CConfigTab] +Type=0 +HeaderFile=ConfigTab.h +ImplementationFile=ConfigTab.cpp +BaseClass=CTabPageSSL +Filter=D +VirtualFilter=dWC +LastObject=IDC_CONFIG + +[DLG:IDD_STATSDIALOG] +Type=1 +Class=CStatsTab +ControlCount=5 +Control1=IDC_STATSLIST,SysListView32,1350631425 +Control2=IDC_SOURCELIST,SysListView32,1350631425 +Control3=IDC_FILLER1,static,1342308352 +Control4=IDC_STATIC_SLS,static,1342308353 +Control5=IDC_STATIC,static,1342308352 + +[CLS:CStatsTab] +Type=0 +HeaderFile=StatsTab.h +ImplementationFile=StatsTab.cpp +BaseClass=CTabPageSSL +Filter=D +VirtualFilter=dWC +LastObject=IDC_SOURCELIST + +[MNU:IDR_MENU2] +Type=1 +Class=? +Command1=ID_POPUP_ADDTOGLOBALSTATLIST +CommandCount=1 + +[MNU:IDR_MENU3] +Type=1 +Class=? +Command1=ID__DELETEFROMGLOBALSTATS +Command2=ID__MAKETHISSTATTHEWINDOWTITLE +CommandCount=2 + +[MNU:IDR_TRAY] +Type=1 +Class=CIcecast2winDlg +Command1=ID_BLANK_RESTORE +CommandCount=1 + +[MNU:IDR_MENU4] +Type=1 +Class=CIcecast2winDlg +Command1=ID_FILE_EXIT +Command2=ID_FILE_EDITCONFIGURATION +Command3=ID_ABOUT_HELP +Command4=ID_ABOUT_CREDITS +CommandCount=4 + diff --git a/win32/Icecast2win.cpp b/win32/Icecast2win.cpp new file mode 100644 index 00000000..2dd64d79 --- /dev/null +++ b/win32/Icecast2win.cpp @@ -0,0 +1,89 @@ +// Icecast2win.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "Icecast2win.h" +#include "Icecast2winDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CIcecast2winApp + +BEGIN_MESSAGE_MAP(CIcecast2winApp, CWinApp) + //{{AFX_MSG_MAP(CIcecast2winApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +#include "colors.h" +///////////////////////////////////////////////////////////////////////////// +// CIcecast2winApp construction + +CIcecast2winApp::CIcecast2winApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CIcecast2winApp object + +CIcecast2winApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CIcecast2winApp initialization + +BOOL CIcecast2winApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + + if (strlen(m_lpCmdLine) > 0) { + strcpy(m_configFile, m_lpCmdLine); + } + else { + strcpy(m_configFile, ".\\icecast.xml"); + } + + + +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif + + CIcecast2winDlg dlg; + m_pMainWnd = &dlg; + +// SetDialogBkColor(BGCOLOR,TEXTCOLOR); + + m_pIconList[0] = LoadIcon (MAKEINTRESOURCE(IDR_MAINFRAME)); + + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/win32/Icecast2win.dsp b/win32/Icecast2win.dsp new file mode 100644 index 00000000..555ac2e3 --- /dev/null +++ b/win32/Icecast2win.dsp @@ -0,0 +1,270 @@ +# Microsoft Developer Studio Project File - Name="Icecast2win" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=Icecast2win - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Icecast2win.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Icecast2win.mak" CFG="Icecast2win - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Icecast2win - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "Icecast2win - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Icecast2win - Win32 Release" + +# PROP BASE Use_MFC 5 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 5 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../src" /I "../src/httpp" /I "../src/thread" /I "../src/log" /I "../src/avl" /I "../src/net" /I "src/timings" /I "../" /I "../../libxslt/include" /I "../../iconv/include" /I "../../libxml2/include" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /subsystem:windows /machine:I386 +# ADD LINK32 Releaseicecast\icecast.lib ..\..\curl\lib\Release\libcurl.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\ogg_static.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\vorbis_static.lib ..\..\libxml2\lib\libxml2.lib ..\..\libxslt\lib\libxslt.lib ..\..\iconv\lib\iconv.lib ..\..\pthreads\pthreadVSE.lib ws2_32.lib winmm.lib /nologo /subsystem:windows /machine:I386 /nodefaultlib:"libc.lib" /out:"Release/Icecast2.exe" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Icecast2win - Win32 Debug" + +# PROP BASE Use_MFC 5 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 6 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../src" /I "../src/httpp" /I "../src/thread" /I "../src/log" /I "../src/avl" /I "../src/net" /I "src/timings" /I "../" /I "../../libxslt/include" /I "../../iconv/include" /I "../../libxml2/include" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_WIN32" /D "_AFXDLL" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" /d "_AFXDLL" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 Debugicecast\icecast.lib ..\..\curl\lib\Debug\libcurl.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\ogg_static_d.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\vorbis_static_d.lib ..\..\libxml2\lib\libxml2.lib ..\..\libxslt\lib\libxslt.lib ..\..\iconv\lib\iconv.lib ..\..\pthreads\pthreadVSE.lib ws2_32.lib winmm.lib /nologo /subsystem:windows /incremental:no /debug /machine:I386 /nodefaultlib:"libcd.lib" /nodefaultlib:"LIBCMTD.lib" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "Icecast2win - Win32 Release" +# Name "Icecast2win - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\ConfigTab.cpp +# End Source File +# Begin Source File + +SOURCE=.\Icecast2win.cpp +# End Source File +# Begin Source File + +SOURCE=.\Icecast2win.rc +# End Source File +# Begin Source File + +SOURCE=.\Icecast2winDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\ResizableDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\ResizableDialog.h +# End Source File +# Begin Source File + +SOURCE=.\StatsTab.cpp +# End Source File +# Begin Source File + +SOURCE=.\Status.cpp +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"stdafx.h" +# End Source File +# Begin Source File + +SOURCE=.\TabCtrlSSL.cpp +# End Source File +# Begin Source File + +SOURCE=.\TabCtrlSSL.h +# End Source File +# Begin Source File + +SOURCE=.\TabPageSSL.cpp +# End Source File +# Begin Source File + +SOURCE=.\TabPageSSL.h +# End Source File +# Begin Source File + +SOURCE=.\Traynot.cpp +# End Source File +# Begin Source File + +SOURCE=.\TRAYNOT.H +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\colors.h +# End Source File +# Begin Source File + +SOURCE=.\ConfigTab.h +# End Source File +# Begin Source File + +SOURCE=.\Icecast2win.h +# End Source File +# Begin Source File + +SOURCE=.\Icecast2winDlg.h +# End Source File +# Begin Source File + +SOURCE=.\Resource.h +# End Source File +# Begin Source File + +SOURCE=.\StatsTab.h +# End Source File +# Begin Source File + +SOURCE=.\Status.h +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\bitmap1.bmp +# End Source File +# Begin Source File + +SOURCE=.\bitmap2.bmp +# End Source File +# Begin Source File + +SOURCE=.\black.bmp +# End Source File +# Begin Source File + +SOURCE=.\cursor1.cur +# End Source File +# Begin Source File + +SOURCE=.\cursor2.cur +# End Source File +# Begin Source File + +SOURCE=.\green1.ico +# End Source File +# Begin Source File + +SOURCE=.\icecast.ico +# End Source File +# Begin Source File + +SOURCE=.\Icecast2.ico +# End Source File +# Begin Source File + +SOURCE=.\res\Icecast2.ico +# End Source File +# Begin Source File + +SOURCE=.\icecast2logo2.bmp +# End Source File +# Begin Source File + +SOURCE=.\res\Icecast2win.ico +# End Source File +# Begin Source File + +SOURCE=.\res\Icecast2win.rc2 +# End Source File +# Begin Source File + +SOURCE=.\ico00001.ico +# End Source File +# Begin Source File + +SOURCE=.\icon1.ico +# End Source File +# Begin Source File + +SOURCE=.\icon2.ico +# End Source File +# Begin Source File + +SOURCE=.\running.bmp +# End Source File +# Begin Source File + +SOURCE=.\stopped.bmp +# End Source File +# End Group +# Begin Source File + +SOURCE=.\ReadMe.txt +# End Source File +# End Target +# End Project diff --git a/win32/Icecast2win.dsw b/win32/Icecast2win.dsw new file mode 100644 index 00000000..cc99fe00 --- /dev/null +++ b/win32/Icecast2win.dsw @@ -0,0 +1,47 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Icecast2win"=.\Icecast2win.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name libxml + End Project Dependency + Begin Project Dependency + Project_Dep_Name icecast + End Project Dependency +}}} + +############################################################################### + +Project: "icecast"=.\icecast.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/win32/Icecast2win.h b/win32/Icecast2win.h new file mode 100644 index 00000000..4f1788b3 --- /dev/null +++ b/win32/Icecast2win.h @@ -0,0 +1,52 @@ +// Icecast2win.h : main header file for the ICECAST2WIN application +// + +#if !defined(AFX_ICECAST2WIN_H__76A528C9_A424_4417_BFDF_0E556A9EE4F1__INCLUDED_) +#define AFX_ICECAST2WIN_H__76A528C9_A424_4417_BFDF_0E556A9EE4F1__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ +#error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CIcecast2winApp: +// See Icecast2win.cpp for the implementation of this class +// + +class CIcecast2winApp : public CWinApp +{ +public: + char m_configFile[1024]; + HICON m_pIconList[2]; + CIcecast2winApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CIcecast2winApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CIcecast2winApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +extern CIcecast2winApp theApp; +#endif // !defined(AFX_ICECAST2WIN_H__76A528C9_A424_4417_BFDF_0E556A9EE4F1__INCLUDED_) diff --git a/win32/Icecast2win.rc b/win32/Icecast2win.rc new file mode 100644 index 00000000..af62d9f9 --- /dev/null +++ b/win32/Icecast2win.rc @@ -0,0 +1,301 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\Icecast2win.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 235, 55 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About Icecast2win" +FONT 8, "MS Sans Serif" +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 + LTEXT "Icecast 2 Win32",IDC_STATIC,40,10,119,8,SS_NOPREFIX + DEFPUSHBUTTON "OK",IDOK,178,7,50,14,WS_GROUP +END + +IDD_ICECAST2WIN_DIALOG DIALOGEX 0, 0, 318, 249 +STYLE WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "icecast2" +MENU IDR_MENU4 +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Tab1",IDC_MAINTAB,"SysTabControl32",0x0,0,48,318,201 + PUSHBUTTON "Start Server",IDC_START,17,31,56,14 + CONTROL "Start Server on Application Startup",IDC_AUTOSTART, + "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,76, + 29,81,17 + CONTROL 159,IDC_STATIC,"Static",SS_BITMAP,0,0,117,20 + CONTROL 151,IDC_SERVERSTATUS,"Static",SS_BITMAP,173,32,65,18, + WS_EX_CLIENTEDGE + CTEXT "Server Status",IDC_STATIC_SS,172,23,65,10, + SS_CENTERIMAGE + CONTROL 150,IDC_STATICBLACK,"Static",SS_BITMAP,116,0,200,20 + PUSHBUTTON "Hide To Systray",IDC_HIDESYSTRAY,257,22,58,12 +END + +IDD_SSTATUS DIALOG DISCARDABLE 0, 0, 320, 210 +STYLE WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "",IDC_FILLER2,0,199,319,11 + CONTROL "List1",IDC_GLOBALSTAT_LIST,"SysListView32",LVS_REPORT | + WS_BORDER | WS_TABSTOP,9,34,300,147 + CTEXT "Global Statistics",IDC_STATIC_GS,9,6,300,10 + LTEXT "Server Has Been Running For ",IDC_STATIC_RUN,46,184,100, + 8 + LTEXT "",IDC_RUNNINGFOR,149,184,156,8 +END + +IDD_CONFIGDIALOG DIALOG DISCARDABLE 0, 0, 320, 210 +STYLE WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_CONFIG,0,0,320,210,ES_MULTILINE | ES_AUTOVSCROLL | + ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL +END + +IDD_STATSDIALOG DIALOG DISCARDABLE 0, 0, 320, 210 +STYLE WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "List1",IDC_STATSLIST,"SysListView32",LVS_REPORT | + WS_BORDER | WS_TABSTOP,142,34,149,160 + CONTROL "List2",IDC_SOURCELIST,"SysListView32",LVS_REPORT | + WS_BORDER | WS_TABSTOP,9,34,111,159 + LTEXT "",IDC_FILLER1,0,199,320,11 + CTEXT "Source Level Statistics",IDC_STATIC_SLS,9,7,300,10 + LTEXT "Click source to view statistics",IDC_STATIC,18,25,111,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "Icecast2win MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "Icecast2win\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "Icecast2win.EXE\0" + VALUE "ProductName", "Icecast2win Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 228 + TOPMARGIN, 7 + BOTTOMMARGIN, 48 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_BITMAP4 BITMAP DISCARDABLE "black.bmp" +IDB_BITMAP5 BITMAP DISCARDABLE "stopped.bmp" +IDB_BITMAP6 BITMAP DISCARDABLE "running.bmp" +IDB_BITMAP7 BITMAP DISCARDABLE "icecast2logo2.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "icecast.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MENU2 MENU DISCARDABLE +BEGIN + POPUP "Popup" + BEGIN + MENUITEM "Add To Global Stat List", ID_POPUP_ADDTOGLOBALSTATLIST + END +END + +IDR_MENU3 MENU DISCARDABLE +BEGIN + POPUP " " + BEGIN + MENUITEM "Delete From Global Stats", ID__DELETEFROMGLOBALSTATS + MENUITEM "Make this stat the window title", + ID__MAKETHISSTATTHEWINDOWTITLE + + END +END + +IDR_TRAY MENU DISCARDABLE +BEGIN + POPUP "Blank" + BEGIN + MENUITEM "Restore", ID_BLANK_RESTORE + END +END + +IDR_MENU4 MENU DISCARDABLE +BEGIN + POPUP "&File" + BEGIN + MENUITEM "E&xit", ID_FILE_EXIT + END + POPUP "C&onfiguration" + BEGIN + MENUITEM "&Edit Configuration", ID_FILE_EDITCONFIGURATION + END + POPUP "&About" + BEGIN + MENUITEM "H&elp", ID_ABOUT_HELP + MENUITEM "Cr&edits", ID_ABOUT_CREDITS + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_ABOUTBOX "&About Icecast2win..." +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\Icecast2win.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/win32/Icecast2winDlg.cpp b/win32/Icecast2winDlg.cpp new file mode 100644 index 00000000..989dd99a --- /dev/null +++ b/win32/Icecast2winDlg.cpp @@ -0,0 +1,1183 @@ +// Icecast2winDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Icecast2win.h" +#include "Icecast2winDlg.h" +#include +#include "ResizableDialog.h" + +#include +#include +#include +#include + +extern "C" { +#include "thread.h" +#include "avl.h" +#include "log.h" +#include "global.h" +#include "httpp.h" +#include "sock.h" +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "stats.h" +} + + +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define ICECAST_VERSION "2.x" +CEdit *g_accessControl; +CEdit *g_errorControl; +CIcecast2winDlg *g_mainDialog; +bool g_tailAccess = false; +bool g_tailError = false; +CString gConfigurationSave; + +char gTitleSource[1024] = ""; +char gTitleName[1024] = ""; + +#define MAXSTATSPERSOURCE 30 +#define MAXSOURCES 1024 + +typedef struct tagElement { + CString name; + CString value; + int titleFlag; +} Element; + +typedef struct tagElementAdditional { + CString source; + CString name; + CString value; + int titleFlag; +} ElementAdditional; + + +typedef struct tagMainElement { + CString source; + long numStats; + Element stats[MAXSTATSPERSOURCE]; + int populated; +} MainElement; + +typedef struct tagMainElementAdditional { + long numStats; + ElementAdditional stats[MAXSTATSPERSOURCE]; +} MainElementAdditional; + + +MainElement gStats[MAXSOURCES]; +MainElement gGlobalStats; +MainElementAdditional gAdditionalGlobalStats; + +long numMainStats; + +extern "C" { + int main(int argc, char **argv); +} + + +void AddToAdditionalGlobalStats(CString source, CString name) { + int foundit = 0; + for (int i=0;iUpdateStatsLists(); +} + +void ClearTitleAdditionalGlobalStats(CString source, CString name) { + int foundit = 0; + int i,j; + for (i=0;iUpdateStatsLists(); +} +void AddToTitleAdditionalGlobalStats(CString source, CString name) { + int foundit = 0; + int i,j; + for (i=0;iUpdateStatsLists(); +} + +void RemoveFromAdditionalGlobalStats(CString source, CString name) { + int foundit = 0; + for (int i=0;iUpdateStatsLists(); +} +///////////////////////////////////////////////////////////////////////////// +// CAboutDlg dialog used for App About + +class CAboutDlg : public CResizableDialog +{ +public: + CAboutDlg(); + +// Dialog Data + //{{AFX_DATA(CAboutDlg) + enum { IDD = IDD_ABOUTBOX }; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAboutDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + //{{AFX_MSG(CAboutDlg) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +CAboutDlg::CAboutDlg() : CResizableDialog(CAboutDlg::IDD) +{ + //{{AFX_DATA_INIT(CAboutDlg) + //}}AFX_DATA_INIT +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CResizableDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAboutDlg) + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CResizableDialog) + //{{AFX_MSG_MAP(CAboutDlg) + // No message handlers + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +///////////////////////////////////////////////////////////////////////////// +// CIcecast2winDlg dialog + +CIcecast2winDlg::CIcecast2winDlg(CWnd* pParent /*=NULL*/) + : CResizableDialog(CIcecast2winDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CIcecast2winDlg) + m_AccessEdit = _T(""); + m_ErrorEdit = _T(""); + m_ConfigEdit = _T(""); + m_ServerStatus = _T(""); + m_SourcesConnected = _T(""); + m_NumClients = _T(""); + m_StatsEdit = _T(""); + m_Autostart = FALSE; + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + m_pTray = NULL; +} + +void CIcecast2winDlg::DoDataExchange(CDataExchange* pDX) +{ + CResizableDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CIcecast2winDlg) + DDX_Control(pDX, IDC_STATIC_SS, m_SS); + DDX_Control(pDX, IDC_SERVERSTATUS, m_ServerStatusBitmap); + DDX_Control(pDX, IDC_START, m_StartButton); + DDX_Control(pDX, IDC_MAINTAB, m_MainTab); + DDX_Check(pDX, IDC_AUTOSTART, m_Autostart); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CIcecast2winDlg, CResizableDialog) + //{{AFX_MSG_MAP(CIcecast2winDlg) + ON_WM_SYSCOMMAND() + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_NOTIFY(TCN_SELCHANGE, IDC_MAINTAB, OnSelchangeMaintab) + ON_COMMAND(ID_FILE_EXIT, OnFileExit) + ON_WM_TIMER() + ON_COMMAND(ID_FILE_STARTSERVER, OnFileStartserver) + ON_COMMAND(ID_FILE_STOPSERVER, OnFileStopserver) + ON_BN_CLICKED(IDC_START, OnStart) + ON_WM_CLOSE() + ON_WM_SIZE() + ON_BN_CLICKED(IDC_HIDESYSTRAY, OnHidesystray) + ON_COMMAND(ID_BLANK_RESTORE, OnBlankRestore) + ON_MESSAGE(WM_TRAY_NOTIFY, OnTrayNotify) + ON_WM_DESTROY() + ON_COMMAND(ID_FILE_EDITCONFIGURATION, OnFileEditconfiguration) + ON_COMMAND(ID_ABOUT_HELP, OnAboutHelp) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CIcecast2winDlg message handlers + +#include "colors.h" + +BOOL CIcecast2winDlg::OnInitDialog() +{ + CResizableDialog::OnInitDialog(); + + // Add "About..." menu item to system menu. + + // IDM_ABOUTBOX must be in the system command range. + ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); + ASSERT(IDM_ABOUTBOX < 0xF000); + + CMenu* pSysMenu = GetSystemMenu(FALSE); + if (pSysMenu != NULL) + { + CString strAboutMenu; + strAboutMenu.LoadString(IDS_ABOUTBOX); + if (!strAboutMenu.IsEmpty()) + { + pSysMenu->AppendMenu(MF_SEPARATOR); + pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); + } + } + + + g_mainDialog = this; + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // TODO: Add extra initialization here + config_read(); + + statsTab.m_colSource0Width = m_colSource0Width; + statsTab.m_colStats0Width = m_colStats0Width; + statsTab.m_colStats1Width = m_colStats1Width; + statusTab.m_colStats0Width = m_colGStats0Width; + statusTab.m_colStats1Width = m_colGStats1Width; + statusTab.m_colStats2Width = m_colGStats2Width; + + statsTab.Create(IDD_STATSDIALOG, this); + statusTab.Create(IDD_SSTATUS, this); + + int nPageID = 0; + m_MainTab.AddSSLPage (_T("Server Status"), nPageID, (CTabPageSSL *)&statusTab); + nPageID++; + m_MainTab.AddSSLPage (_T("Source Level Stats"), nPageID, (CTabPageSSL *)&statsTab); + nPageID++; + + + labelFont.CreateFont(24,0, 0, 0, FW_BOLD, 0, 0, 0, 0, OUT_TT_PRECIS, 0, PROOF_QUALITY, 0, "Arial"); + + runningBitmap.LoadBitmap(IDB_BITMAP6); + stoppedBitmap.LoadBitmap(IDB_BITMAP5); + + UpdateData(FALSE); + + LoadConfig(); + + AddAnchor(IDC_MAINTAB, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_STATICBLACK, TOP_LEFT, TOP_RIGHT); + + EnableSaveRestore("icecast2win", "positions"); + + m_pTray = NULL; + + char version[255] = ""; + sprintf(version, "Icecast2 Version %s", ICECAST_VERSION); + SetWindowText(version); + + + if (m_Autostart) { + OnStart(); + } + return TRUE; // return TRUE unless you set the focus to a control +} + +void CIcecast2winDlg::OnSysCommand(UINT nID, LPARAM lParam) +{ + if ((nID & 0xFFF0) == IDM_ABOUTBOX) + { + CAboutDlg dlgAbout; + dlgAbout.DoModal(); + } + else + { + CResizableDialog::OnSysCommand(nID, lParam); + } +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CIcecast2winDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CResizableDialog::OnPaint(); + } +} + + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CIcecast2winDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CIcecast2winDlg::OnSelchangeMaintab(NMHDR* pNMHDR, LRESULT* pResult) +{ + // TODO: Add your control notification handler code here + + *pResult = 0; +} + +void CIcecast2winDlg::LoadConfig() +{ + FILE *filep; + char buffer[2046] = ""; + CIcecast2winApp *myApp = (CIcecast2winApp *)AfxGetApp(); + + configTab.m_Config = ""; + filep = fopen(myApp->m_configFile, "r"); + if (filep) { + fclose(filep); + } + else { + MessageBox("Unable to load config file (" + CString(myApp->m_configFile) + ") unable to start", NULL, MB_OK); + } + +} + + +void CIcecast2winDlg::OnFileExit() +{ + // TODO: Add your command handler code here + DestroyWindow(); +} + + + +void CIcecast2winDlg::getTag(char *pbuf, char *ptag, char *dest) +{ + char openTag[256] = ""; + char closeTag[256] = ""; + + sprintf(openTag, "<%s>", ptag); + sprintf(closeTag, "", ptag); + + char *p1; + p1 = strstr(pbuf, openTag); + if (p1) { + p1 = p1 + strlen(openTag); + char *p2; + p2 = strstr(p1, closeTag); + if (p2) { + strncpy(dest, p1, p2-p1); + } + } +} + + +void CIcecast2winDlg::EnableControl(UINT control) +{ + CWnd *pWnd; + pWnd = GetDlgItem(control); + ::ShowWindow(pWnd->GetSafeHwnd(), SW_SHOW); +} + +void CIcecast2winDlg::DisableControl(UINT control) +{ + CWnd *pWnd; + pWnd = GetDlgItem(control); + ::ShowWindow(pWnd->GetSafeHwnd(), SW_HIDE); +} + + +void AddUpdateStatistic(int sourceIndex, char *name, char *value) +{ + for (int j=0;jxmlChildrenNode; + while (children != NULL) { + if (!strcmp((char *)children->name, "listeners")) { + listenerInd = 1; + } + ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, children->xmlChildrenNode, 1); + AddUpdateStatistic(sourceIndex, (char *)children->name, ls_xmlContentPtr); + xmlFree(ls_xmlContentPtr); + children = children->next; + } + if (!listenerInd) { + /* If no listeners, then the source has been disconnected */ + gStats[sourceIndex].populated = 0; + gStats[sourceIndex].numStats = 0; + g_mainDialog->statsTab.m_SourceListCtrl.DeleteAllItems(); + g_mainDialog->statsTab.m_StatsListCtrl.DeleteAllItems(); + } +} + +bool g_collectingStats = false; + +void StartStats(void *dummy) +{ + while (global.running != ICE_RUNNING) { + Sleep(500); + } + while (global.running == ICE_RUNNING) { + if (global.running == ICE_RUNNING) { + for (int j=0;jxmlChildrenNode; + char* ls_xmlContentPtr2 = NULL; + + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar *)"source"))) { + ls_xmlContentPtr2 = (char *)xmlGetProp(cur, (unsigned char *)"mount"); + UpdateSourceData(doc, cur, ls_xmlContentPtr2); + } + else { + /* A Global stat */ + ls_xmlContentPtr2 = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + AddUpdateStatistic(0, (char *)cur->name, ls_xmlContentPtr2); + } + if (ls_xmlContentPtr2) { + xmlFree(ls_xmlContentPtr2); + } + + cur = cur->next; + } + xmlFreeDoc(doc); + xmlCleanupParser(); + g_mainDialog->UpdateStatsLists(); + Sleep(5000); + } + if (global.running != ICE_RUNNING) { + numMainStats = 0; + g_mainDialog->statusTab.m_GlobalStatList.DeleteAllItems(); + g_mainDialog->statsTab.m_SourceListCtrl.DeleteAllItems(); + g_mainDialog->statsTab.m_StatsListCtrl.DeleteAllItems(); + _endthread(); + } + } + _endthread(); +} +void CIcecast2winDlg::OnTimer(UINT nIDEvent) +{ + // TODO: Add your message handler code here and/or call default + if (nIDEvent == 0) { + if (global.running == ICE_RUNNING) { + char buffer[255] = ""; + CString tmp; + // Get info from stats... + m_ServerStatusBitmap.SetBitmap(HBITMAP(runningBitmap)); + sprintf(buffer, "%d", global.sources); + tmp = buffer; + if (tmp != statusTab.m_Sources) { + statusTab.m_Sources = tmp; + statusTab.UpdateData(FALSE); + } + sprintf(buffer, "%d", global.clients); + tmp = buffer; + if (tmp != statusTab.m_Clients) { + statusTab.m_Clients = tmp; + statusTab.UpdateData(FALSE); + } + + m_StartButton.GetWindowText(tmp); + + if (tmp == "Start Server") { + m_StartButton.SetWindowText("Stop Server"); + m_StartButton.SetState(0); + } + time_t currentTime; + time(¤tTime); + time_t runningTime = currentTime - serverStart; + + CTimeSpan runningFor(runningTime); + + char timespan[1024] = ""; + sprintf(timespan, "%d Days, %d Hours, %d Minutes, %d Seconds", runningFor.GetDays(), runningFor.GetHours(), runningFor.GetMinutes(), runningFor.GetSeconds()); + statusTab.m_RunningFor = timespan; + statusTab.UpdateData(FALSE); + + SetTimer(0, 500, NULL); + } + else { + statusTab.m_Sources = "0"; + statusTab.m_Clients = "0"; + m_ServerStatusBitmap.SetBitmap(HBITMAP(stoppedBitmap)); + m_StartButton.SetWindowText("Start Server"); + m_StartButton.SetState(0); + UpdateData(FALSE); + statusTab.m_RunningFor = "Not running"; + statusTab.UpdateData(FALSE); + } + } + + CResizableDialog::OnTimer(nIDEvent); +} + +char g_configFile[1024] = ""; +char g_progName[255] = "icecast2"; + +void StartServer(void *configfile) +{ + int argc = 3; + char* argv[3]; + + strcpy(g_configFile, (char *)configfile); + + argv[0] = g_progName; + argv[1] = "-c"; + argv[2] = g_configFile; + time(&(g_mainDialog->serverStart)); + + int ret = main(argc, (char **)argv); + if (ret) { + MessageBox(NULL, "Unable to start server", NULL, MB_OK); + } + global.running = ICE_HALTING; + _endthread(); + + +} +void CIcecast2winDlg::OnFileStartserver() +{ + // TODO: Add your command handler code here + CIcecast2winApp *myApp = (CIcecast2winApp *)AfxGetApp(); + + if (gConfigurationSave == "") { + gConfigurationSave = m_ConfigEdit; + } + + if (global.running == ICE_RUNNING) { + MessageBox("Server already running", "Error", MB_OK); + } + else { + m_ConfigEditCtrl.SetReadOnly(TRUE); + LoadConfig(); + SetTimer(0, 500, NULL); + _beginthread(StartServer, 0, (void *)(LPCSTR)myApp->m_configFile); + _beginthread(StartStats, 0, (void *)NULL); + } +} + +void CIcecast2winDlg::OnFileStopserver() +{ + // TODO: Add your command handler code here + ; +} + +bool infocus = false; + +void CIcecast2winDlg::StopServer() +{ + KillTimer(0); + global.running = ICE_HALTING; + m_StartButton.SetWindowText("Start Server"); + m_StartButton.SetState(0); + m_ServerStatusBitmap.SetBitmap(HBITMAP(stoppedBitmap)); + statusTab.m_RunningFor = "Not running"; + statusTab.UpdateData(FALSE); + + + +} + + +void CIcecast2winDlg::OnStart() +{ + CIcecast2winApp *myApp = (CIcecast2winApp *)AfxGetApp(); + + // TODO: Add your control notification handler code here + + if (global.running == ICE_RUNNING) { + StopServer(); + } + else { + SetTimer(0, 500, NULL); + _beginthread(StartServer, 0, (void *)(LPCSTR)myApp->m_configFile); + _beginthread(StartStats, 0, (void *)NULL); + } + +} + +void CIcecast2winDlg::UpdateStatsLists() +{ + char item[1024] = ""; + int l = 0; + + // Global Stats are index of 0 + for (int k=0;k < gStats[0].numStats;k++) { + int inthere = 0; + for (l=0;l < statusTab.m_GlobalStatList.GetItemCount();l++) { + + statusTab.m_GlobalStatList.GetItemText(l, 1, item, sizeof(item)); + if (!strcmp(gStats[0].stats[k].name, item)) { + inthere = 1; + break; + } + } + if (!inthere) { + LVITEM lvi; + + lvi.mask = LVIF_IMAGE | LVIF_TEXT; + lvi.iItem = statsTab.m_SourceListCtrl.GetItemCount(); + lvi.iSubItem = 0; + //lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].source; + lvi.pszText = "Global Stat"; + statusTab.m_GlobalStatList.InsertItem(&lvi); + lvi.iSubItem = 1; + lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].stats[k].name; + statusTab.m_GlobalStatList.SetItem(&lvi); + lvi.iSubItem = 2; + lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].stats[k].value; + statusTab.m_GlobalStatList.SetItem(&lvi); + if ((!strcmp(gTitleSource, gStats[0].source)) && + (!strcmp(gTitleName, gStats[0].stats[k].name))) { + gStats[0].stats[k].titleFlag = 1; + } + + } + else { + LVITEM lvi; + + lvi.mask = LVIF_IMAGE | LVIF_TEXT; + lvi.iItem = l; + lvi.iSubItem = 2; + lvi.pszText = (LPTSTR)(LPCTSTR)gStats[0].stats[k].value; + statusTab.m_GlobalStatList.SetItem(&lvi); + } + if (gStats[0].stats[k].titleFlag) { + CString windowTitle = CString("Global Stat") + " - " + gStats[0].stats[k].name + " - " + gStats[0].stats[k].value; + SetWindowText(windowTitle); + if (m_pTray) { + m_pTray->SetTIP((LPSTR)(LPCSTR)windowTitle); + } + } + } + + for (int i=1;iSetTIP((LPSTR)(LPCSTR)windowTitle); + } + } + } + } + else { + } + } + } +} + +char gAppName[255] = "icecast2"; +char gConfigFile[255] = "icecast2.ini"; + +void CIcecast2winDlg::config_write() +{ + char buf[255] = ""; + char buf2[1024] = ""; + + UpdateData(TRUE); + + m_colSource0Width = statsTab.m_SourceListCtrl.GetColumnWidth(0); + m_colStats0Width = statsTab.m_StatsListCtrl.GetColumnWidth(0); + m_colStats1Width = statsTab.m_StatsListCtrl.GetColumnWidth(1); + m_colGStats0Width = statusTab.m_GlobalStatList.GetColumnWidth(0); + m_colGStats1Width = statusTab.m_GlobalStatList.GetColumnWidth(1); + m_colGStats2Width = statusTab.m_GlobalStatList.GetColumnWidth(2); + + + sprintf(buf, "%d", m_colSource0Width); + WritePrivateProfileString(gAppName, "col0SourceWidth", buf, gConfigFile); + sprintf(buf, "%d", m_colStats0Width); + WritePrivateProfileString(gAppName, "col0StatsWidth", buf, gConfigFile); + sprintf(buf, "%d", m_colStats1Width); + WritePrivateProfileString(gAppName, "col1StatsWidth", buf, gConfigFile); + sprintf(buf, "%d", m_colGStats0Width); + WritePrivateProfileString(gAppName, "col0GStatsWidth", buf, gConfigFile); + sprintf(buf, "%d", m_colGStats1Width); + WritePrivateProfileString(gAppName, "col1GStatsWidth", buf, gConfigFile); + sprintf(buf, "%d", m_colGStats2Width); + WritePrivateProfileString(gAppName, "col2GStatsWidth", buf, gConfigFile); + + if (m_Autostart) { + WritePrivateProfileString(gAppName, "AutoStart", "1", gConfigFile); + } + else { + WritePrivateProfileString(gAppName, "AutoStart", "0", gConfigFile); + } + + sprintf(buf, "%d", gAdditionalGlobalStats.numStats); + WritePrivateProfileString(gAppName, "numAdditionalStats", buf, gConfigFile); + + for (int i=0;i 0) { + char *p1 = strchr(buf, '|'); + if (p1) { + char tmpSource[1024] = ""; + char tmpName[1024] = ""; + memset(tmpSource, '\000', sizeof(tmpSource)); + memset(tmpName, '\000', sizeof(tmpName)); + + + strncpy(tmpSource, buf, p1-buf); + p1++; + strcpy(tmpName, p1); + + + strcpy(gTitleSource, tmpSource); + strcpy(gTitleName, tmpName); + } + } + +} + +void CIcecast2winDlg::OnClose() +{ + // TODO: Add your message handler code here and/or call default + config_write(); + CResizableDialog::OnClose(); +} + +void CIcecast2winDlg::OnSize(UINT nType, int cx, int cy) +{ + CResizableDialog::OnSize(nType, cx, cy); + + int border1 = 0; + int border2 = 78; + // TODO: Add your message handler code here + if (m_MainTab.m_hWnd) { + CRect rect; + GetClientRect (&rect); + m_MainTab.ResizeDialog(0, rect.Width()-border1, rect.Height()-border2); + m_MainTab.ResizeDialog(1, rect.Width()-border1, rect.Height()-border2); + } + +} + + +LONG CIcecast2winDlg::OnTrayNotify ( WPARAM wParam, LPARAM lParam ) +{ + switch (lParam) { + case WM_RBUTTONDOWN: + { + CMenu menu ; + // Load and Verify Menu + VERIFY(menu.LoadMenu(IDR_TRAY)); + CMenu* pPopup = menu.GetSubMenu (0) ; + ASSERT(pPopup != NULL); + + // Get the cursor position + POINT pt ; + GetCursorPos (&pt) ; + + // Fix Microsofts' BUG!!!! + SetForegroundWindow(); + + /////////////////////////////////// + // Display The Menu + pPopup->TrackPopupMenu(TPM_LEFTALIGN | + TPM_RIGHTBUTTON,pt.x, pt.y, AfxGetMainWnd()); + break ; + } + case WM_LBUTTONDBLCLK: + ////////////////////////////////// + // Unhide our Window + if (m_bHidden) { + ShowWindow (SW_RESTORE); + } + //OnUnHide() ; + break ; + } + + return (0) ; +} + +void CIcecast2winDlg::OnHidesystray() +{ + // TODO: Add your control notification handler code here + OnHide(); + theApp.HideApplication(); +} +void CIcecast2winDlg::OnHide() +{ + // TODO: Add your control notification handler code here + if (m_pTray == NULL) { + m_pTray = new CTrayNot (this,WM_TRAY_NOTIFY, NULL,theApp.m_pIconList); + } + m_pTray->SetState(0); + m_bHidden = TRUE; + +} + +void CIcecast2winDlg::OnBlankRestore() +{ + // TODO: Add your command handler code here + if (m_bHidden) { + ShowWindow (SW_RESTORE); + } + +} + +void CIcecast2winDlg::OnDestroy() +{ + CResizableDialog::OnDestroy(); + + if (m_pTray) { + delete m_pTray ; + m_pTray = NULL ; + } + // TODO: Add your message handler code here + +} + +void CIcecast2winDlg::OnFileEditconfiguration() +{ + // TODO: Add your command handler code here + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + + int ok = 1; + if (global.running == ICE_RUNNING) { + MessageBox("I'm sorry, but you cannot edit the configuration file while the server is running", NULL, MB_OK); + } + else { + // Start the child process. + if( !CreateProcess( NULL, // No module name (use command line). + "notepad icecast.xml", // Command line. + NULL, // Process handle not inheritable. + NULL, // Thread handle not inheritable. + FALSE, // Set handle inheritance to FALSE. + 0, // No creation flags. + NULL, // Use parent's environment block. + NULL, // Use parent's starting directory. + &si, // Pointer to STARTUPINFO structure. + &pi ) // Pointer to PROCESS_INFORMATION structure. + ) + { + ok = 0; + } + + // Wait until child process exits. + WaitForSingleObject( pi.hProcess, INFINITE ); + + // Close process and thread handles. + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + } + +} + +void CIcecast2winDlg::OnAboutHelp() +{ + // TODO: Add your command handler code here + ShellExecute(NULL, "open", "doc\\icecast2.chm", NULL, NULL, SW_SHOWNORMAL); +} diff --git a/win32/Icecast2winDlg.h b/win32/Icecast2winDlg.h new file mode 100644 index 00000000..0a5857e8 --- /dev/null +++ b/win32/Icecast2winDlg.h @@ -0,0 +1,124 @@ +// Icecast2winDlg.h : header file +// + +#if !defined(AFX_ICECAST2WINDLG_H__23B4DA8B_C9BC_49C8_A62C_37FC6BC5E54A__INCLUDED_) +#define AFX_ICECAST2WINDLG_H__23B4DA8B_C9BC_49C8_A62C_37FC6BC5E54A__INCLUDED_ + +#include "ResizableDialog.h" +#include "TabCtrlSSL.h" +#include "TabPageSSL.h" + +#include "ConfigTab.h" +#include "StatsTab.h" +#include "Status.h" +#include "TrayNot.h" + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CIcecast2winDlg dialog + +class CIcecast2winDlg : public CResizableDialog +{ +// Construction +public: + time_t serverStart; + void config_read(); + void config_write(); + void UpdateStatsLists(); + CConfigTab configTab; + CStatsTab statsTab; + CStatus statusTab; + int m_colSource0Width; + int m_colStats0Width; + int m_colStats1Width; + int m_colGStats0Width; + int m_colGStats1Width; + int m_colGStats2Width; + CFont labelFont; + CBitmap runningBitmap; + CBitmap stoppedBitmap; + CTrayNot* m_pTray; + BOOL m_bHidden; + int m_iconSwap; + + + + + + void StopServer(); + bool m_isRunning; + void DisableControl(UINT control); + void EnableControl(UINT control); + void getTag(char *pbuf, char *ptag, char *dest); + CString m_ErrorLog; + CString m_AccessLog; + void ParseConfig(); + void LoadConfig(); + CIcecast2winDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CIcecast2winDlg) + enum { IDD = IDD_ICECAST2WIN_DIALOG }; + CStatic m_SS; + CStatic m_ServerStatusBitmap; + CStatic m_iceLogo; + CButton m_StartButton; + CEdit m_StatsEditCtrl; + CEdit m_ConfigEditCtrl; + CEdit m_ErrorEditCtrl; + CEdit m_AccessEditCtrl; + CTabCtrlSSL m_MainTab; + CString m_AccessEdit; + CString m_ErrorEdit; + CString m_ConfigEdit; + CString m_ServerStatus; + CString m_SourcesConnected; + CString m_NumClients; + FILE *filep_accesslog; + FILE *filep_errorlog; + CString m_StatsEdit; + BOOL m_Autostart; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CIcecast2winDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CIcecast2winDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnSysCommand(UINT nID, LPARAM lParam); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnSelchangeMaintab(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnFileExit(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnFileStartserver(); + afx_msg void OnFileStopserver(); + afx_msg void OnStart(); + afx_msg void OnClose(); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnHidesystray(); + afx_msg void OnHide(); + afx_msg void OnBlankRestore(); + afx_msg LONG OnTrayNotify ( WPARAM wParam, LPARAM lParam ); + afx_msg void OnDestroy(); + afx_msg void OnFileEditconfiguration(); + afx_msg void OnAboutHelp(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_ICECAST2WINDLG_H__23B4DA8B_C9BC_49C8_A62C_37FC6BC5E54A__INCLUDED_) diff --git a/win32/Makefile.am b/win32/Makefile.am new file mode 100644 index 00000000..cd0c3526 --- /dev/null +++ b/win32/Makefile.am @@ -0,0 +1,15 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = res + +EXTRA_DIST = ConfigTab.cpp ConfigTab.h Icecast2win.clw Icecast2win.cpp \ + Icecast2win.dsp Icecast2win.dsw Icecast2win.h Icecast2win.rc \ + Icecast2winDlg.cpp Icecast2winDlg.h Makefile.am ResizableDialog.cpp \ + ResizableDialog.h StatsTab.cpp StatsTab.h Status.cpp Status.h StdAfx.cpp \ + StdAfx.h TabCtrlSSL.cpp TabCtrlSSL.h TabPageSSL.cpp TabPageSSL.h black.bmp \ + colors.h icecast.dsp icecast.ico icecast2.iss icecast2logo2.bmp\ + resource.h running.bmp stopped.bmp TRAYNOT.h Traynot.cpp \ + icecast2_console.dsw icecast2_console.dsp + diff --git a/win32/ResizableDialog.cpp b/win32/ResizableDialog.cpp new file mode 100644 index 00000000..f56972f4 --- /dev/null +++ b/win32/ResizableDialog.cpp @@ -0,0 +1,442 @@ +// ResizableDialog.cpp : implementation file +// +///////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2000 by Paolo Messina +// (ppescher@yahoo.com) +// +// Free for non-commercial use. +// You may change the code to your needs, +// provided that credits to the original +// author is given in the modified files. +// +///////////////////////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "ResizableDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CResizableDialog + +inline void CResizableDialog::Construct() +{ + m_bInitDone = FALSE; + + m_bUseMinTrack = TRUE; + m_bUseMaxTrack = FALSE; + m_bUseMaxRect = FALSE; + + m_bShowGrip = TRUE; + + m_bEnableSaveRestore = FALSE; + + m_szGripSize.cx = GetSystemMetrics(SM_CXVSCROLL); + m_szGripSize.cy = GetSystemMetrics(SM_CYHSCROLL); +} + +CResizableDialog::CResizableDialog() +{ + Construct(); +} + +CResizableDialog::CResizableDialog(UINT nIDTemplate, CWnd* pParentWnd) + : CDialog(nIDTemplate, pParentWnd) +{ + Construct(); +} + +CResizableDialog::CResizableDialog(LPCTSTR lpszTemplateName, CWnd* pParentWnd) + : CDialog(lpszTemplateName, pParentWnd) +{ + Construct(); +} + +CResizableDialog::~CResizableDialog() +{ + // for safety + m_arrLayout.RemoveAll(); +} + + +BEGIN_MESSAGE_MAP(CResizableDialog, CDialog) + //{{AFX_MSG_MAP(CResizableDialog) + ON_WM_NCHITTEST() + ON_WM_GETMINMAXINFO() + ON_WM_SIZE() + ON_WM_DESTROY() + ON_WM_PAINT() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +///////////////////////////////////////////////////////////////////////////// +// CResizableDialog message handlers + + +BOOL CResizableDialog::OnInitDialog() +{ + CDialog::OnInitDialog(); + + UpdateGripPos(); + + // gets the template size as the min track size + CRect rc; + GetWindowRect(&rc); + m_ptMinTrackSize.x = rc.Width(); + m_ptMinTrackSize.y = rc.Height(); + + m_bInitDone = TRUE; + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CResizableDialog::OnDestroy() +{ + CDialog::OnDestroy(); + + if (m_bEnableSaveRestore) + SaveWindowRect(); + + // remove old windows + m_arrLayout.RemoveAll(); +} + +void CResizableDialog::OnPaint() +{ + CPaintDC dc(this); // device context for painting + + if (m_bShowGrip && !IsZoomed()) + { + // draw size-grip + dc.DrawFrameControl(&m_rcGripRect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); + } +} + +void CResizableDialog::OnSize(UINT nType, int cx, int cy) +{ + CWnd::OnSize(nType, cx, cy); + + if (nType == SIZE_MAXHIDE || nType == SIZE_MAXSHOW) + return; // arrangement not needed + + if (m_bInitDone) + { + ArrangeLayout(); + } +} + +UINT CResizableDialog::OnNcHitTest(CPoint point) +{ + CPoint pt = point; + ScreenToClient(&pt); + + // if in size grip and in client area + if (m_bShowGrip && m_rcGripRect.PtInRect(pt) && + pt.x >= 0 && pt.y >= 0) + return HTBOTTOMRIGHT; + + return CDialog::OnNcHitTest(point); +} + +void CResizableDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) +{ + if (!m_bInitDone) + return; + + if (m_bUseMinTrack) + lpMMI->ptMinTrackSize = m_ptMinTrackSize; + + if (m_bUseMaxTrack) + lpMMI->ptMaxTrackSize = m_ptMaxTrackSize; + + if (m_bUseMaxRect) + { + lpMMI->ptMaxPosition = m_ptMaxPos; + lpMMI->ptMaxSize = m_ptMaxSize; + } +} + +// layout functions + +void CResizableDialog::AddAnchor(HWND wnd, CSize tl_type, CSize br_type) +{ + ASSERT(wnd != NULL && ::IsWindow(wnd)); + ASSERT(::IsChild(*this, wnd)); + ASSERT(tl_type != NOANCHOR); + + // get control's window class + + CString st; + GetClassName(wnd, st.GetBufferSetLength(MAX_PATH), MAX_PATH); + st.ReleaseBuffer(); + st.MakeUpper(); + + // add the style 'clipsiblings' to a GroupBox + // to avoid unnecessary repainting of controls inside + if (st == "BUTTON") + { + DWORD style = GetWindowLong(wnd, GWL_STYLE); + if (style & BS_GROUPBOX) + SetWindowLong(wnd, GWL_STYLE, style | WS_CLIPSIBLINGS); + } + + // wnd classes that don't redraw client area correctly + // when the hor scroll pos changes due to a resizing + BOOL hscroll = FALSE; + if (st == "LISTBOX") + hscroll = TRUE; + + // wnd classes that need refresh when resized + BOOL refresh = FALSE; + if (st == "STATIC") + { + DWORD style = GetWindowLong(wnd, GWL_STYLE); + + switch (style & SS_TYPEMASK) + { + case SS_LEFT: + case SS_CENTER: + case SS_RIGHT: + // word-wrapped text needs refresh + refresh = TRUE; + } + + // centered images or text need refresh + if (style & SS_CENTERIMAGE) + refresh = TRUE; + + // simple text never needs refresh + if (style & SS_TYPEMASK == SS_SIMPLE) + refresh = FALSE; + } + + // get dialog's and control's rect + CRect wndrc, objrc; + + GetClientRect(&wndrc); + ::GetWindowRect(wnd, &objrc); + ScreenToClient(&objrc); + + CSize tl_margin, br_margin; + + if (br_type == NOANCHOR) + br_type = tl_type; + + // calculate margin for the top-left corner + + tl_margin.cx = objrc.left - wndrc.Width() * tl_type.cx / 100; + tl_margin.cy = objrc.top - wndrc.Height() * tl_type.cy / 100; + + // calculate margin for the bottom-right corner + + br_margin.cx = objrc.right - wndrc.Width() * br_type.cx / 100; + br_margin.cy = objrc.bottom - wndrc.Height() * br_type.cy / 100; + + // add to the list + Layout obj(wnd, tl_type, tl_margin, br_type, br_margin, hscroll, refresh); + m_arrLayout.Add(obj); +} + +void CResizableDialog::ArrangeLayout() +{ + // update size-grip + InvalidateRect(&m_rcGripRect); + UpdateGripPos(); + InvalidateRect(&m_rcGripRect); + + // init some vars + CRect wndrc; + GetClientRect(&wndrc); + + int i, count = m_arrLayout.GetSize(); + HDWP hdwp = BeginDeferWindowPos(count); + + for (i=0; iGetWindowRect(&objrc); + ScreenToClient(&objrc); + + // calculate new top-left corner + + newrc.left = obj.tl_margin.cx + wndrc.Width() * obj.tl_type.cx / 100; + newrc.top = obj.tl_margin.cy + wndrc.Height() * obj.tl_type.cy / 100; + + // calculate new bottom-right corner + + newrc.right = obj.br_margin.cx + wndrc.Width() * obj.br_type.cx / 100; + newrc.bottom = obj.br_margin.cy + wndrc.Height() * obj.br_type.cy / 100; + + if (!newrc.EqualRect(&objrc)) + { + if (obj.adj_hscroll) + { + // needs repainting, due to horiz scrolling + int diff = newrc.Width() - objrc.Width(); + int max = wnd->GetScrollLimit(SB_HORZ); + + obj.need_refresh = FALSE; + if (max > 0 && wnd->GetScrollPos(SB_HORZ) > max - diff) + { + obj.need_refresh = TRUE; + } + } + + // set flags + DWORD flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION; + if (newrc.TopLeft() == objrc.TopLeft()) + flags |= SWP_NOMOVE; + if (newrc.Size() == objrc.Size()) + flags |= SWP_NOSIZE; + + DeferWindowPos(hdwp, obj.hwnd, NULL, newrc.left, newrc.top, + newrc.Width(), newrc.Height(), flags); + } + } + // go re-arrange child windows + EndDeferWindowPos(hdwp); + + // refresh those that need + for (i=0; iInvalidate(); + wnd->UpdateWindow(); + } + } +} + +void CResizableDialog::UpdateGripPos() +{ + // size-grip goes bottom right in the client area + + GetClientRect(&m_rcGripRect); + + m_rcGripRect.left = m_rcGripRect.right - m_szGripSize.cx; + m_rcGripRect.top = m_rcGripRect.bottom - m_szGripSize.cy; +} + +// protected members + +void CResizableDialog::ShowSizeGrip(BOOL bShow) +{ + if (m_bShowGrip != bShow) + { + m_bShowGrip = bShow; + InvalidateRect(&m_rcGripRect); + } +} + +void CResizableDialog::SetMaximizedRect(const CRect& rc) +{ + m_bUseMaxRect = TRUE; + + m_ptMaxPos = rc.TopLeft(); + m_ptMaxSize.x = rc.Width(); + m_ptMaxSize.y = rc.Height(); +} + +void CResizableDialog::ResetMaximizedRect() +{ + m_bUseMaxRect = FALSE; +} + +void CResizableDialog::SetMinTrackSize(const CSize& size) +{ + m_bUseMinTrack = TRUE; + + m_ptMinTrackSize.x = size.cx; + m_ptMinTrackSize.y = size.cy; +} + +void CResizableDialog::ResetMinTrackSize() +{ + m_bUseMinTrack = FALSE; +} + +void CResizableDialog::SetMaxTrackSize(const CSize& size) +{ + m_bUseMaxTrack = TRUE; + + m_ptMaxTrackSize.x = size.cx; + m_ptMaxTrackSize.y = size.cy; +} + +void CResizableDialog::ResetMaxTrackSize() +{ + m_bUseMaxTrack = FALSE; +} + +// NOTE: this must be called after all the other settings +// to have the dialog and its controls displayed properly +void CResizableDialog::EnableSaveRestore(LPCTSTR pszSection, LPCTSTR pszEntry) +{ + m_sSection = pszSection; + m_sEntry = pszEntry; + + m_bEnableSaveRestore = TRUE; + + LoadWindowRect(); +} + + +// used to save/restore window's size and position +// either in the registry or a private .INI file +// depending on your application settings + +#define PROFILE_FMT _T("%d,%d,%d,%d,%d,%d") + +void CResizableDialog::SaveWindowRect() +{ + CString data; + WINDOWPLACEMENT wp; + + ZeroMemory(&wp, sizeof(WINDOWPLACEMENT)); + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(&wp); + + RECT& rc = wp.rcNormalPosition; // alias + + data.Format(PROFILE_FMT, rc.left, rc.top, + rc.right, rc.bottom, wp.showCmd, wp.flags); + + AfxGetApp()->WriteProfileString(m_sSection, m_sEntry, data); +} + +void CResizableDialog::LoadWindowRect() +{ + CString data; + WINDOWPLACEMENT wp; + + data = AfxGetApp()->GetProfileString(m_sSection, m_sEntry); + + if (data.IsEmpty()) // never saved before + return; + + ZeroMemory(&wp, sizeof(WINDOWPLACEMENT)); + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(&wp); + + RECT& rc = wp.rcNormalPosition; // alias + + if (_stscanf(data, PROFILE_FMT, &rc.left, &rc.top, + &rc.right, &rc.bottom, &wp.showCmd, &wp.flags) == 6) + { + SetWindowPlacement(&wp); + } +} diff --git a/win32/ResizableDialog.h b/win32/ResizableDialog.h new file mode 100644 index 00000000..89c5b29d --- /dev/null +++ b/win32/ResizableDialog.h @@ -0,0 +1,173 @@ +#if !defined(AFX_RESIZABLEDIALOG_H__INCLUDED_) +#define AFX_RESIZABLEDIALOG_H__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +// ResizableDialog.h : header file +// +///////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2000 by Paolo Messina +// (ppescher@yahoo.com) +// +// Free for non-commercial use. +// You may change the code to your needs, +// provided that credits to the original +// author is given in the modified files. +// +///////////////////////////////////////////////////////////////////////////// + +#include +#include + +// useful compatibility constants (the only one required is NOANCHOR) + +#if !defined(__SIZE_ANCHORS_) +#define __SIZE_ANCHORS_ + +const CSize + NOANCHOR(-1,-1), + TOP_LEFT(0,0), TOP_CENTER(50,0), TOP_RIGHT(100,0), + MIDDLE_LEFT(0,50), MIDDLE_CENTER(50,50), MIDDLE_RIGHT(100,50), + BOTTOM_LEFT(0,100), BOTTOM_CENTER(50,100), BOTTOM_RIGHT(100,100); + +#endif // !defined(__SIZE_ANCHORS_) + +///////////////////////////////////////////////////////////////////////////// +// CResizableDialog window + +class CResizableDialog : public CDialog +{ + +// Construction +public: + CResizableDialog(); + CResizableDialog(UINT nIDTemplate, CWnd* pParentWnd = NULL); + CResizableDialog(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL); + +// Attributes +private: + // flags + BOOL m_bShowGrip; + BOOL m_bUseMaxTrack; + BOOL m_bUseMinTrack; + BOOL m_bUseMaxRect; + BOOL m_bEnableSaveRestore; + + // internal status + CString m_sSection; // section name and + CString m_sEntry; // entry for save/restore + + BOOL m_bInitDone; // if all internal vars initialized + + SIZE m_szGripSize; // set at construction time + + CRect m_rcGripRect; // current pos of grip + + POINT m_ptMinTrackSize; // min tracking size + POINT m_ptMaxTrackSize; // max tracking size + POINT m_ptMaxPos; // maximized position + POINT m_ptMaxSize; // maximized size + + class Layout + { + public: + HWND hwnd; + + BOOL adj_hscroll; + BOOL need_refresh; + + // upper-left corner + CSize tl_type; + CSize tl_margin; + + // bottom-right corner + CSize br_type; + CSize br_margin; + + public: + Layout() + : hwnd(NULL), adj_hscroll(FALSE), need_refresh(FALSE), + tl_type(0,0), tl_margin(0,0), + br_type(0,0), br_margin(0,0) + { + }; + + Layout(HWND hw, SIZE tl_t, SIZE tl_m, + SIZE br_t, SIZE br_m, BOOL hscroll, BOOL refresh) + { + hwnd = hw; + + adj_hscroll = hscroll; + need_refresh = refresh; + + tl_type = tl_t; + tl_margin = tl_m; + + br_type = br_t; + br_margin = br_m; + }; + }; + + CArray m_arrLayout; // list of repositionable controls + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CResizableDialog) + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CResizableDialog(); + +// used internally +private: + void Construct(); + void LoadWindowRect(); + void SaveWindowRect(); + void ArrangeLayout(); + void UpdateGripPos(); + +// callable from derived classes +//protected: +public: + void AddAnchor(HWND wnd, CSize tl_type, + CSize br_type = NOANCHOR); // add anchors to a control + void AddAnchor(UINT ctrl_ID, CSize tl_type, + CSize br_type = NOANCHOR) // add anchors to a control + { + AddAnchor(::GetDlgItem(*this, ctrl_ID), tl_type, br_type); + }; + void ShowSizeGrip(BOOL bShow); // show or hide the size grip + void SetMaximizedRect(const CRect& rc); // set window rect when maximized + void ResetMaximizedRect(); // reset to default maximized rect + void SetMinTrackSize(const CSize& size); // set minimum tracking size + void ResetMinTrackSize(); // reset to default minimum tracking size + void SetMaxTrackSize(const CSize& size); // set maximum tracking size + void ResetMaxTrackSize(); // reset to default maximum tracking size + void EnableSaveRestore(LPCTSTR pszSection, LPCTSTR pszEntry); // section and entry in app's profile + +// Generated message map functions +protected: + //{{AFX_MSG(CResizableDialog) + virtual BOOL OnInitDialog(); + afx_msg UINT OnNcHitTest(CPoint point); + afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnDestroy(); + afx_msg void OnPaint(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_RESIZABLEDIALOG_H__INCLUDED_) \ No newline at end of file diff --git a/win32/StatsTab.cpp b/win32/StatsTab.cpp new file mode 100644 index 00000000..d3facb0f --- /dev/null +++ b/win32/StatsTab.cpp @@ -0,0 +1,149 @@ +// StatsTab.cpp : implementation file +// + +#include "stdafx.h" +#include "Icecast2win.h" +#include "StatsTab.h" + +#include "Icecast2winDlg.h" + +extern CIcecast2winDlg *g_mainDialog; + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +extern void AddToAdditionalGlobalStats(CString source, CString name); + +///////////////////////////////////////////////////////////////////////////// +// CStatsTab dialog + + +CStatsTab::CStatsTab(CWnd* pParent /*=NULL*/) + : CTabPageSSL(CStatsTab::IDD, pParent) +{ + //{{AFX_DATA_INIT(CStatsTab) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CStatsTab::DoDataExchange(CDataExchange* pDX) +{ + CTabPageSSL::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CStatsTab) + DDX_Control(pDX, IDC_STATIC_SLS, m_SLS); + DDX_Control(pDX, IDC_STATSLIST, m_StatsListCtrl); + DDX_Control(pDX, IDC_SOURCELIST, m_SourceListCtrl); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CStatsTab, CTabPageSSL) + //{{AFX_MSG_MAP(CStatsTab) + ON_NOTIFY(NM_DBLCLK, IDC_SOURCELIST, OnDblclkSourcelist) + ON_NOTIFY(NM_RCLICK, IDC_STATSLIST, OnRclickStatslist) + ON_NOTIFY(NM_CLICK, IDC_SOURCELIST, OnClickSourcelist) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CStatsTab message handlers + +BOOL CStatsTab::OnInitDialog() +{ + CTabPageSSL::OnInitDialog(); + + // TODO: Add extra initialization here + m_SourceListCtrl.InsertColumn(0, _T("Source"), LVCFMT_LEFT, m_colSource0Width); + m_StatsListCtrl.InsertColumn(0, _T("Statistic"), LVCFMT_LEFT, m_colStats0Width); + m_StatsListCtrl.InsertColumn(1, _T("Value"), LVCFMT_LEFT, m_colStats1Width); + + AddAnchor(IDC_STATSLIST, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_SOURCELIST, TOP_LEFT, BOTTOM_LEFT); + AddAnchor(IDC_FILLER1, BOTTOM_LEFT, BOTTOM_RIGHT); + + m_SourceListCtrl.SetSelectionMark(0); + m_SLS.SetFont(&(g_mainDialog->labelFont), TRUE); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CStatsTab::OnDblclkSourcelist(NMHDR* pNMHDR, LRESULT* pResult) +{ + // TODO: Add your control notification handler code here + g_mainDialog->statsTab.m_StatsListCtrl.DeleteAllItems(); + g_mainDialog->UpdateStatsLists(); + *pResult = 0; +} + +void CStatsTab::OnRclickStatslist(NMHDR* pNMHDR, LRESULT* pResult) +{ + // TODO: Add your control notification handler code here + CMenu menu; + + + CPoint point; + ::GetCursorPos(&point); //where is the mouse? + + DWORD dwSelectionMade; + menu.LoadMenu(IDR_MENU2); + CMenu *pmenuPopup = menu.GetSubMenu(0); + dwSelectionMade = pmenuPopup->TrackPopupMenu( (TPM_LEFTALIGN|TPM_LEFTBUTTON| + TPM_NONOTIFY|TPM_RETURNCMD), + point.x, point.y, this); + + pmenuPopup->DestroyMenu(); + char msg[255] =""; + char buffer[1024] = ""; + char buffer2[1024] = ""; + + CString name; + CString source; + POSITION pos; + switch (dwSelectionMade) { + case ID_POPUP_ADDTOGLOBALSTATLIST : + pos = m_StatsListCtrl.GetFirstSelectedItemPosition(); + if (pos != NULL) { + int nItem = m_StatsListCtrl.GetNextSelectedItem(pos); + LVITEM lvi; + + lvi.mask = LVIF_TEXT; + lvi.iItem = nItem; + lvi.iSubItem = 0; + lvi.pszText = buffer; + lvi.cchTextMax = sizeof(buffer); + m_StatsListCtrl.GetItem(&lvi); + name = buffer; + } + pos = m_SourceListCtrl.GetFirstSelectedItemPosition(); + if (pos != NULL) { + int nItem = m_SourceListCtrl.GetNextSelectedItem(pos); + LVITEM lvi; + + lvi.mask = LVIF_TEXT; + lvi.iItem = nItem; + lvi.iSubItem = 0; + lvi.pszText = buffer2; + lvi.cchTextMax = sizeof(buffer2); + m_SourceListCtrl.GetItem(&lvi); + source = buffer2; + } + AddToAdditionalGlobalStats(source, name); + break; + default : + break; + } + + *pResult = 0; +} + +void CStatsTab::OnClickSourcelist(NMHDR* pNMHDR, LRESULT* pResult) +{ + // TODO: Add your control notification handler code here + OnDblclkSourcelist(pNMHDR, pResult); + *pResult = 0; +} diff --git a/win32/StatsTab.h b/win32/StatsTab.h new file mode 100644 index 00000000..e5fac45d --- /dev/null +++ b/win32/StatsTab.h @@ -0,0 +1,55 @@ +#if !defined(AFX_STATSTAB_H__64B82CAB_8D6D_45A6_84FD_666F6317E5F2__INCLUDED_) +#define AFX_STATSTAB_H__64B82CAB_8D6D_45A6_84FD_666F6317E5F2__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// StatsTab.h : header file +// +#include "TabPageSSL.h" + +///////////////////////////////////////////////////////////////////////////// +// CStatsTab dialog + +class CStatsTab : public CTabPageSSL +{ +// Construction +public: + int m_colStats1Width; + int m_colStats0Width; + int m_colSource0Width; + CStatsTab(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CStatsTab) + enum { IDD = IDD_STATSDIALOG }; + CStatic m_SLS; + CListCtrl m_StatsListCtrl; + CListCtrl m_SourceListCtrl; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CStatsTab) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CStatsTab) + virtual BOOL OnInitDialog(); + afx_msg void OnDblclkSourcelist(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnRclickStatslist(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnClickSourcelist(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STATSTAB_H__64B82CAB_8D6D_45A6_84FD_666F6317E5F2__INCLUDED_) diff --git a/win32/Status.cpp b/win32/Status.cpp new file mode 100644 index 00000000..7c23b43d --- /dev/null +++ b/win32/Status.cpp @@ -0,0 +1,160 @@ +// Status.cpp : implementation file +// + +#include "stdafx.h" +#include "Icecast2win.h" +#include "Status.h" + +#include "Icecast2winDlg.h" + +extern CIcecast2winDlg *g_mainDialog; + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +extern void RemoveFromAdditionalGlobalStats(CString source, CString name); +extern void AddToTitleAdditionalGlobalStats(CString source, CString name); + + +///////////////////////////////////////////////////////////////////////////// +// CStatus dialog + + +CStatus::CStatus(CWnd* pParent /*=NULL*/) + : CTabPageSSL(CStatus::IDD, pParent) +{ + //{{AFX_DATA_INIT(CStatus) + m_Clients = _T(""); + m_Sources = _T(""); + m_RunningFor = _T(""); + //}}AFX_DATA_INIT +} + + +void CStatus::DoDataExchange(CDataExchange* pDX) +{ + CTabPageSSL::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CStatus) + DDX_Control(pDX, IDC_STATIC_GS, m_GS); + DDX_Control(pDX, IDC_GLOBALSTAT_LIST, m_GlobalStatList); + DDX_Text(pDX, IDC_RUNNINGFOR, m_RunningFor); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CStatus, CTabPageSSL) + //{{AFX_MSG_MAP(CStatus) + ON_NOTIFY(NM_RCLICK, IDC_GLOBALSTAT_LIST, OnRclickGlobalstatList) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CStatus message handlers + +BOOL CStatus::OnInitDialog() +{ + CTabPageSSL::OnInitDialog(); + + + m_GlobalStatList.InsertColumn(0, _T("Stat Type"), LVCFMT_LEFT, m_colStats0Width); + m_GlobalStatList.InsertColumn(1, _T("Name"), LVCFMT_LEFT, m_colStats1Width); + m_GlobalStatList.InsertColumn(2, _T("Value"), LVCFMT_LEFT, m_colStats2Width); + + m_GlobalStatList.SetExtendedStyle(LVS_EX_FULLROWSELECT); + // TODO: Add extra initialization here + AddAnchor(IDC_FILLER2, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_GLOBALSTAT_LIST, TOP_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_STATIC_RUN, BOTTOM_LEFT, BOTTOM_RIGHT); + AddAnchor(IDC_RUNNINGFOR, BOTTOM_LEFT, BOTTOM_RIGHT); + + m_GS.SetFont(&(g_mainDialog->labelFont), TRUE); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CStatus::OnRclickGlobalstatList(NMHDR* pNMHDR, LRESULT* pResult) +{ + // TODO: Add your control notification handler code here + CMenu menu; + + + CPoint point; + ::GetCursorPos(&point); //where is the mouse? + + DWORD dwSelectionMade; + menu.LoadMenu(IDR_MENU3); + CMenu *pmenuPopup = menu.GetSubMenu(0); + dwSelectionMade = pmenuPopup->TrackPopupMenu( (TPM_LEFTALIGN|TPM_LEFTBUTTON| + TPM_NONOTIFY|TPM_RETURNCMD), + point.x, point.y, this); + + pmenuPopup->DestroyMenu(); + char msg[255] =""; + char buffer[1024] = ""; + char buffer2[1024] = ""; + + CString name; + CString source; + POSITION pos; + int nItem; + switch (dwSelectionMade) { + case ID__DELETEFROMGLOBALSTATS : + pos = m_GlobalStatList.GetFirstSelectedItemPosition(); + if (pos != NULL) { + nItem = m_GlobalStatList.GetNextSelectedItem(pos); + LVITEM lvi; + + lvi.mask = LVIF_TEXT; + lvi.iItem = nItem; + lvi.iSubItem = 0; + lvi.pszText = buffer; + lvi.cchTextMax = sizeof(buffer); + m_GlobalStatList.GetItem(&lvi); + source = buffer; + lvi.iSubItem = 1; + lvi.pszText = buffer2; + lvi.cchTextMax = sizeof(buffer2); + m_GlobalStatList.GetItem(&lvi); + name = buffer2; + + if (source == "Global Stat") { + MessageBox("Sorry, but you can't delete this type of stat", NULL, MB_OK); + } + else { + RemoveFromAdditionalGlobalStats(source, name); + m_GlobalStatList.DeleteItem(nItem); + } + } + break; + case ID__MAKETHISSTATTHEWINDOWTITLE : + pos = m_GlobalStatList.GetFirstSelectedItemPosition(); + if (pos != NULL) { + nItem = m_GlobalStatList.GetNextSelectedItem(pos); + LVITEM lvi; + + lvi.mask = LVIF_TEXT; + lvi.iItem = nItem; + lvi.iSubItem = 0; + lvi.pszText = buffer; + lvi.cchTextMax = sizeof(buffer); + m_GlobalStatList.GetItem(&lvi); + source = buffer; + lvi.iSubItem = 1; + lvi.pszText = buffer2; + lvi.cchTextMax = sizeof(buffer2); + m_GlobalStatList.GetItem(&lvi); + name = buffer2; + + AddToTitleAdditionalGlobalStats(source, name); + } + break; + default : + break; + } + + *pResult = 0; +} diff --git a/win32/Status.h b/win32/Status.h new file mode 100644 index 00000000..83605728 --- /dev/null +++ b/win32/Status.h @@ -0,0 +1,59 @@ +#if !defined(AFX_STATUS_H__DE59E22B_FD4F_4131_B347_48BD9FAC9348__INCLUDED_) +#define AFX_STATUS_H__DE59E22B_FD4F_4131_B347_48BD9FAC9348__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// Status.h : header file +// +#include "TabPageSSL.h" + +///////////////////////////////////////////////////////////////////////////// +// CStatus dialog + +class CStatus : public CTabPageSSL +{ +// Construction +public: + int m_colStats2Width; + int m_colStats1Width; + int m_colStats0Width; + CFont labelFont; + CStatus(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + CBitmap runningBitmap; + CBitmap stoppedBitmap; + + //{{AFX_DATA(CStatus) + enum { IDD = IDD_SSTATUS }; + CStatic m_GS; + CListCtrl m_GlobalStatList; + CString m_Clients; + CString m_Sources; + CString m_RunningFor; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CStatus) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CStatus) + virtual BOOL OnInitDialog(); + afx_msg void OnRclickGlobalstatList(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STATUS_H__DE59E22B_FD4F_4131_B347_48BD9FAC9348__INCLUDED_) diff --git a/win32/StdAfx.cpp b/win32/StdAfx.cpp new file mode 100644 index 00000000..915c805f --- /dev/null +++ b/win32/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// Icecast2win.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/win32/StdAfx.h b/win32/StdAfx.h new file mode 100644 index 00000000..1c6c3ca1 --- /dev/null +++ b/win32/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__2C2EF8C3_43CD_47D2_A979_EC36873E602D__INCLUDED_) +#define AFX_STDAFX_H__2C2EF8C3_43CD_47D2_A979_EC36873E602D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#define WM_TRAY_NOTIFY WM_APP+1000 +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__2C2EF8C3_43CD_47D2_A979_EC36873E602D__INCLUDED_) diff --git a/win32/TRAYNOT.h b/win32/TRAYNOT.h new file mode 100644 index 00000000..294550ce --- /dev/null +++ b/win32/TRAYNOT.h @@ -0,0 +1,35 @@ +// CTrayNot window +#ifndef _CTRAYNOT +#define _CTRAYNOT + +class CTrayNot : public CObject +{ +// Construction +public: + CTrayNot ( CWnd* pWnd, UINT uCallbackMessage, + LPCTSTR szTip, HICON* pList ) ; + +// Attributes +public: + BOOL m_bEnabled ; + NOTIFYICONDATA m_tnd ; + HICON* m_pIconList ; + + +public: + void SetState ( int id = 0 ) ; + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CTrayNot) + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CTrayNot(); + void SetTIP(char *pTip); + +}; + +///////////////////////////////////////////////////////////////////////////// +#endif diff --git a/win32/TabCtrlSSL.cpp b/win32/TabCtrlSSL.cpp new file mode 100644 index 00000000..1d1eae29 --- /dev/null +++ b/win32/TabCtrlSSL.cpp @@ -0,0 +1,499 @@ +// TabCtrlSSL.cpp : implementation file +// + +#include "stdafx.h" +#include "TabCtrlSSL.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Construction + +CTabCtrlSSL::CTabCtrlSSL () { +#ifndef _AFX_NO_OCC_SUPPORT + AfxEnableControlContainer (); +#endif // !_AFX_NO_OCC_SUPPORT +} + +///////////////////////////////////////////////////////////////////////////// +// Destruction + +CTabCtrlSSL::~CTabCtrlSSL (void) { +} + +BEGIN_MESSAGE_MAP(CTabCtrlSSL, CTabCtrl) + //{{AFX_MSG_MAP(CTabCtrlSSL) + ON_WM_DESTROY () + ON_WM_SETFOCUS () + ON_WM_KILLFOCUS () + ON_NOTIFY_REFLECT (TCN_SELCHANGING, OnSelChanging) + ON_NOTIFY_REFLECT (TCN_SELCHANGE, OnSelChange) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// Page Functions + +int CTabCtrlSSL::AddSSLPage (LPCTSTR pszTitle, int nPageID, CTabPageSSL* pTabPage) { + // Add a page to the tab control. + TabDelete tabDelete; + tabDelete.pTabPage = pTabPage; + tabDelete.bDelete = FALSE; + + return AddPage (pszTitle, nPageID, tabDelete); +} + +int CTabCtrlSSL::AddSSLPage (LPCTSTR pszTitle, int nPageID, LPCTSTR pszTemplateName) { + // Verify that the dialog template is compatible with CTabCtrlSSL + // (debug builds only). If your app asserts here, make sure the dialog + // resource you're adding to the view is a borderless child window and + // is not marked visible. +#ifdef _DEBUG + if (pszTemplateName != NULL) { + BOOL bResult = CheckDialogTemplate (pszTemplateName); + ASSERT (bResult); + } +#endif // _DEBUG + + // Add a page to the tab control. + // Create a modeless dialog box. + CTabPageSSL* pDialog = new CTabPageSSL; + + if (pDialog == NULL) { + return -1; + } + + if (!pDialog->Create (pszTemplateName, this)) { + pDialog->DestroyWindow (); + delete pDialog; + return -1; + } + + TabDelete tabDelete; + tabDelete.pTabPage = pDialog; + tabDelete.bDelete = TRUE; + + return AddPage (pszTitle, nPageID, tabDelete); +} + +int CTabCtrlSSL::AddSSLPage (LPCTSTR pszTitle, int nPageID, int nTemplateID) { + return AddSSLPage (pszTitle, nPageID, MAKEINTRESOURCE (nTemplateID)); +} + +BOOL CTabCtrlSSL::RemoveSSLPage (int nIndex) { + if (nIndex >= GetItemCount ()) + return FALSE; + + // Notify derived classes that the page is being destroyed. + OnDestroyPage (nIndex, m_nPageIDs[nIndex]); + + // Switch pages if the page being deleted is the current page and it's + // not the only remaining page. + int nCount = GetItemCount (); + if (nCount > 1 && nIndex == GetCurSel ()) { + int nPage = nIndex + 1; + if (nPage >= nCount) + nPage = nCount - 2; + ActivateSSLPage (nPage); + } + + // Remove the page from the tab control. + DeleteItem (nIndex); + + // Destroy the dialog (if any) that represents the page. + TabDelete tabDelete = m_tabs[nIndex]; + CTabPageSSL* pDialog = tabDelete.pTabPage; + if (pDialog != NULL) { + pDialog->DestroyWindow (); + delete pDialog; + } + + // Clean up, repaint, and return. + m_tabs.RemoveAt (nIndex); + m_hFocusWnd.RemoveAt (nIndex); + m_nPageIDs.RemoveAt (nIndex); + Invalidate (); + return TRUE; +} + +int CTabCtrlSSL::GetSSLPageCount (void) { + return GetItemCount (); +} + +BOOL CTabCtrlSSL::GetSSLPageTitle (int nIndex, CString &strTitle) { + if (nIndex >= GetItemCount ()) + return FALSE; + + TCHAR szTitle[1024]; + + TC_ITEM item; + item.mask = TCIF_TEXT; + item.pszText = szTitle; + item.cchTextMax = sizeof szTitle / sizeof (TCHAR); + + if (!GetItem (nIndex, &item)) + return FALSE; + + strTitle = item.pszText; + return TRUE; +} + +BOOL CTabCtrlSSL::SetSSLPageTitle (int nIndex, LPCTSTR pszTitle) { + if (nIndex >= GetItemCount ()) + return FALSE; + + TC_ITEM item; + item.mask = TCIF_TEXT; + item.pszText = (LPTSTR) pszTitle; + + BOOL bResult = SetItem (nIndex, &item); + if (bResult) + Invalidate (); + return bResult; +} + +int CTabCtrlSSL::GetSSLPageID (int nIndex) { + if (nIndex >= GetItemCount ()) + return -1; + + return m_nPageIDs[nIndex]; +} + +int CTabCtrlSSL::SetSSLPageID (int nIndex, int nPageID) { + if (nIndex >= GetItemCount ()) + return -1; + + int nOldPageID = m_nPageIDs[nIndex]; + m_nPageIDs[nIndex] = nPageID; + return nOldPageID; +} + +BOOL CTabCtrlSSL::ActivateSSLPage (int nIndex) { + if (nIndex >= GetItemCount ()) + return FALSE; + + // Do nothing if the specified page is already active. + if (nIndex == GetCurSel ()) + return TRUE; + + // Deactivate the current page. + int nOldIndex = GetCurSel (); + + if (nIndex != -1) { + TabDelete tabDelete = m_tabs[nOldIndex]; + CTabPageSSL* pDialog = tabDelete.pTabPage; + if (pDialog != NULL) { + m_hFocusWnd[nOldIndex] = ::GetFocus (); + pDialog->ShowWindow (SW_HIDE); + } + } + + // Activate the new one. + SetCurSel (nIndex); + TabDelete tabDelete = m_tabs[nIndex]; + CTabPageSSL* pDialog = tabDelete.pTabPage; + + if (pDialog != NULL) { + ::SetFocus (m_hFocusWnd[nIndex]); + CRect rect; + GetClientRect (&rect); + ResizeDialog (nIndex, rect.Width (), rect.Height ()); + pDialog->ShowWindow (SW_SHOW); + } + return TRUE; +} + +int CTabCtrlSSL::GetSSLActivePage (void) { + return GetCurSel (); +} + +CWnd* CTabCtrlSSL::GetSSLPage (int nIndex) { + if (nIndex >= GetItemCount ()) + return NULL; + + TabDelete tabDelete = m_tabs[nIndex]; + return (CWnd*) tabDelete.pTabPage; +} + +int CTabCtrlSSL::GetSSLPageIndex (int nPageID) { + int nCount = GetItemCount (); + if (nCount == 0) + return -1; + + for (int i=0; isignature == 0xFFFF) + dwStyle = pTemplate->style; + else + dwStyle = ((DLGTEMPLATE*) pTemplate)->style; + + UnlockResource (hTemplate); + FreeResource (hTemplate); + + // Verify that the dialog is an invisible child window. + if (dwStyle & WS_VISIBLE) + return FALSE; // WS_VISIBLE flag is set + + if (!(dwStyle & WS_CHILD)) + return FALSE; // WS_CHILD flag isn't set + + // Verify that the dialog has no border and no title bar. + if (dwStyle & (WS_BORDER | WS_THICKFRAME | DS_MODALFRAME)) + return FALSE; // One or more border flags are set + + if (dwStyle & WS_CAPTION) + return FALSE; // WS_CAPTION flag is set + + return TRUE; +} +#endif // _DEBUG + +void CTabCtrlSSL::ResizeDialog (int nIndex, int cx, int cy) { + if (nIndex != -1) { + TabDelete tabDelete = m_tabs[nIndex]; + CTabPageSSL* pDialog = tabDelete.pTabPage; + + if (pDialog != NULL) { + CRect rect; + GetItemRect (nIndex, &rect); + + int x, y, nWidth, nHeight; + DWORD dwStyle = GetStyle (); + + if (dwStyle & TCS_VERTICAL) { // Vertical tabs + int nTabWidth = + rect.Width () * GetRowCount (); + x = (dwStyle & TCS_RIGHT) ? 4 : nTabWidth + 4; + y = 4; + nWidth = cx - nTabWidth - 8; + nHeight = cy - 8; + } + else { // Horizontal tabs + int nTabHeight = + rect.Height () * GetRowCount (); + x = 4; + y = (dwStyle & TCS_BOTTOM) ? 4 : nTabHeight + 4; + nWidth = cx - 8; + nHeight = cy - nTabHeight - 8; + + + + } + pDialog->SetWindowPos (NULL, x, y, nWidth, nHeight, SWP_NOZORDER); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Overridables + +BOOL CTabCtrlSSL::OnInitPage (int nIndex, int nPageID) { + // TODO: Override in derived class to initialise pages. + return TRUE; +} + +void CTabCtrlSSL::OnActivatePage (int nIndex, int nPageID) { + // TODO: Override in derived class to respond to page activations. +} + +void CTabCtrlSSL::OnDeactivatePage (int nIndex, int nPageID) { + // TODO: Override in derived class to respond to page deactivations. +} + +void CTabCtrlSSL::OnDestroyPage (int nIndex, int nPageID) { + // TODO: Override in derived class to free resources. +} + +///////////////////////////////////////////////////////////////////////////// +// Message handlers + +void CTabCtrlSSL::OnSelChanging (NMHDR* pNMHDR, LRESULT* pResult) { + // Notify derived classes that the selection is changing. + int nIndex = GetCurSel (); + if (nIndex == -1) + return; + + OnDeactivatePage (nIndex, m_nPageIDs[nIndex]); + + // Save the input focus and hide the old page. + TabDelete tabDelete = m_tabs[nIndex]; + CTabPageSSL* pDialog = tabDelete.pTabPage; + + if (pDialog != NULL) { + m_hFocusWnd[nIndex] = ::GetFocus (); + pDialog->ShowWindow (SW_HIDE); + } + *pResult = 0; +} + +void CTabCtrlSSL::OnSelChange (NMHDR* pNMHDR, LRESULT* pResult) { + int nIndex = GetCurSel (); + if (nIndex == -1) + return; + + // Show the new page. + TabDelete tabDelete = m_tabs[nIndex]; + CTabPageSSL* pDialog = tabDelete.pTabPage; + + if (pDialog != NULL) { + ::SetFocus (m_hFocusWnd[nIndex]); + CRect rect; + GetClientRect (&rect); + ResizeDialog (nIndex, rect.Width (), rect.Height ()); + pDialog->ShowWindow (SW_SHOW); + } + + // Notify derived classes that the selection has changed. + OnActivatePage (nIndex, m_nPageIDs[nIndex]); + *pResult = 0; +} + +void CTabCtrlSSL::OnSetFocus (CWnd* pOldWnd) { + CTabCtrl::OnSetFocus (pOldWnd); + + // Set the focus to a control on the current page. + int nIndex = GetCurSel (); + if (nIndex != -1) + ::SetFocus (m_hFocusWnd[nIndex]); +} + +void CTabCtrlSSL::OnKillFocus (CWnd* pNewWnd) { + CTabCtrl::OnKillFocus (pNewWnd); + + // Save the HWND of the control that holds the input focus. + int nIndex = GetCurSel (); + if (nIndex != -1) + m_hFocusWnd[nIndex] = ::GetFocus (); +} + +// My thanks to Tomasz Sowinski for all his help coming up with a workable +// solution to the stack versus heap object destruction +void CTabCtrlSSL::OnDestroy (void) { + int nCount = m_tabs.GetSize (); + + // Destroy dialogs and delete CTabCtrlSSL objects. + if (nCount > 0) { + for (int i=nCount - 1; i>=0; i--) { + OnDestroyPage (i, m_nPageIDs[i]); + TabDelete tabDelete = m_tabs[i]; + CTabPageSSL* pDialog = tabDelete.pTabPage; + if (pDialog != NULL) { + pDialog->DestroyWindow (); + if (TRUE == tabDelete.bDelete) { + delete pDialog; + } + } + } + } + + // Clean up the internal arrays. + m_tabs.RemoveAll (); + m_hFocusWnd.RemoveAll (); + m_nPageIDs.RemoveAll (); + + CTabCtrl::OnDestroy (); +} + +BOOL CTabCtrlSSL::OnCommand (WPARAM wParam, LPARAM lParam) { + // Forward WM_COMMAND messages to the dialog's parent. + return GetParent ()->SendMessage (WM_COMMAND, wParam, lParam); +} + +BOOL CTabCtrlSSL::OnNotify (WPARAM wParam, LPARAM lParam, LRESULT* pResult) { + // Forward WM_NOTIFY messages to the dialog's parent. + return GetParent ()->SendMessage (WM_NOTIFY, wParam, lParam); +} + +BOOL CTabCtrlSSL::OnCmdMsg (UINT nID, int nCode, void* pExtra, + AFX_CMDHANDLERINFO* pHandlerInfo) { + // Forward ActiveX control events to the dialog's parent. +#ifndef _AFX_NO_OCC_SUPPORT + if (nCode == CN_EVENT) + return GetParent ()->OnCmdMsg (nID, nCode, pExtra, pHandlerInfo); +#endif // !_AFX_NO_OCC_SUPPORT + + return CTabCtrl::OnCmdMsg (nID, nCode, pExtra, pHandlerInfo); +} + +int CTabCtrlSSL::AddPage (LPCTSTR pszTitle, int nPageID, TabDelete tabDelete) { + // Add a page to the tab control. + TC_ITEM item; + item.mask = TCIF_TEXT; + item.pszText = (LPTSTR) pszTitle; + int nIndex = GetItemCount (); + + if (InsertItem (nIndex, &item) == -1) + return -1; + + if (NULL == tabDelete.pTabPage) { + // Fail - no point calling the function with a NULL pointer! + DeleteItem (nIndex); + return -1; + } + else { + // Record the address of the dialog object and the page ID. + int nArrayIndex = m_tabs.Add (tabDelete); + ASSERT (nIndex == nArrayIndex); + + nArrayIndex = m_nPageIDs.Add (nPageID); + ASSERT (nIndex == nArrayIndex); + + // Size and position the dialog box within the view. + tabDelete.pTabPage->SetParent (this); // Just to be sure + + CRect rect; + GetClientRect (&rect); + + if (rect.Width () > 0 && rect.Height () > 0) + ResizeDialog (nIndex, rect.Width (), rect.Height ()); + + // Initialize the page. + if (OnInitPage (nIndex, nPageID)) { + // Make sure the first control in the dialog is the one that + // receives the input focus when the page is displayed. + HWND hwndFocus = tabDelete.pTabPage->GetTopWindow ()->m_hWnd; + nArrayIndex = m_hFocusWnd.Add (hwndFocus); + ASSERT (nIndex == nArrayIndex); + } + else { + // Make the control that currently has the input focus is the one + // that receives the input focus when the page is displayed. + m_hFocusWnd.Add (::GetFocus ()); + } + + // If this is the first page added to the view, make it visible. + if (nIndex == 0) + tabDelete.pTabPage->ShowWindow (SW_SHOW); + } + return nIndex; +} diff --git a/win32/TabCtrlSSL.h b/win32/TabCtrlSSL.h new file mode 100644 index 00000000..0c6d4bf0 --- /dev/null +++ b/win32/TabCtrlSSL.h @@ -0,0 +1,98 @@ +#if !defined(AFX_TABCTRLSSL_H__75BE48A7_864C_11D5_9F04_000102FB9990__INCLUDED_) +#define AFX_TABCTRLSSL_H__75BE48A7_864C_11D5_9F04_000102FB9990__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// TabCtrlEx.h : header file +// + +#include +#include "TabPageSSL.h" + +#ifdef _DEBUG +#pragma pack (push, 1) + +typedef struct { + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; +} DLGTEMPLATEEX; + +#pragma pack (pop) +#endif // _DEBUG + +///////////////////////////////////////////////////////////////////////////// +// CTabCtrlSSL window + +class CTabCtrlSSL : public CTabCtrl { +public: +// Construction + CTabCtrlSSL (); +// Destruction + virtual ~CTabCtrlSSL (void); +// Page Functions + int AddSSLPage (LPCTSTR pszTitle, int nPageID, CTabPageSSL* pTabPage); + int AddSSLPage (LPCTSTR pszTitle, int nPageID, LPCTSTR pszTemplateName); + int AddSSLPage (LPCTSTR pszTitle, int nPageID, int nTemplateID); + BOOL RemoveSSLPage (int nIndex); + int GetSSLPageCount (void); + BOOL GetSSLPageTitle (int nIndex, CString& strTitle); + BOOL SetSSLPageTitle (int nIndex, LPCTSTR pszTitle); + int GetSSLPageID (int nIndex); + int SetSSLPageID (int nIndex, int nPageID); + BOOL ActivateSSLPage (int nIndex); + int GetSSLActivePage (void); + CWnd* GetSSLPage (int nIndex); + int GetSSLPageIndex (int nPageID); + void ResizeDialog (int nIndex, int cx, int cy); + +protected: + struct TabDelete { + CTabPageSSL* pTabPage; + BOOL bDelete; + }; + CArray m_tabs; + CArray m_hFocusWnd; + CArray m_nPageIDs; + + int AddPage (LPCTSTR pszTitle, int nPageID, TabDelete tabDelete); + + virtual BOOL OnInitPage (int nIndex, int nPageID); + virtual void OnActivatePage (int nIndex, int nPageID); + virtual void OnDeactivatePage (int nIndex, int nPageID); + virtual void OnDestroyPage (int nIndex, int nPageID); + virtual BOOL OnCommand (WPARAM wParam, LPARAM lParam); + virtual BOOL OnNotify (WPARAM wParam, LPARAM lParam, LRESULT* pResult); + virtual BOOL OnCmdMsg (UINT nID, int nCode, void* pExtra, + AFX_CMDHANDLERINFO* pHandlerInfo); + +#ifdef _DEBUG + BOOL CheckDialogTemplate (LPCTSTR pszTemplateName); +#endif // _DEBUG + // Generated message map functions +protected: + //{{AFX_MSG(CTabCtrlSSL) + afx_msg void OnDestroy (void); + afx_msg void OnSetFocus (CWnd* pOldWnd); + afx_msg void OnKillFocus (CWnd* pNewWnd); + afx_msg void OnSelChanging (NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnSelChange (NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_TABCTRLSSL_H__75BE48A7_864C_11D5_9F04_000102FB9990__INCLUDED_) diff --git a/win32/TabPageSSL.cpp b/win32/TabPageSSL.cpp new file mode 100644 index 00000000..b70fbcb8 --- /dev/null +++ b/win32/TabPageSSL.cpp @@ -0,0 +1,76 @@ +#include "stdafx.h" +#include "TabPageSSL.h" +#include "ResizableDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Construction + +CTabPageSSL::CTabPageSSL () { +#ifndef _AFX_NO_OCC_SUPPORT + AfxEnableControlContainer (); +#endif // !_AFX_NO_OCC_SUPPORT +} + +CTabPageSSL::CTabPageSSL (UINT nIDTemplate, CWnd* pParent /*=NULL*/) + : CResizableDialog(nIDTemplate, pParent) { +#ifndef _AFX_NO_OCC_SUPPORT + AfxEnableControlContainer (); +#endif // !_AFX_NO_OCC_SUPPORT +} + +///////////////////////////////////////////////////////////////////////////// +// Destruction + +CTabPageSSL::~CTabPageSSL () { +} + +///////////////////////////////////////////////////////////////////////////// +// Message Handlers + +void CTabPageSSL::OnOK (void) { + // + // Prevent CDialog::OnOK from calling EndDialog. + // +} + +void CTabPageSSL::OnCancel (void) { + // + // Prevent CDialog::OnCancel from calling EndDialog. + // +} + +BOOL CTabPageSSL::OnCommand (WPARAM wParam, LPARAM lParam) { + // Call base class OnCommand to allow message map processing + CResizableDialog::OnCommand (wParam, lParam); + // + // Forward WM_COMMAND messages to the dialog's parent. + // + return GetParent ()->SendMessage (WM_COMMAND, wParam, lParam); +} + +BOOL CTabPageSSL::OnNotify (WPARAM wParam, LPARAM lParam, LRESULT* pResult) { + // + // Forward WM_NOTIFY messages to the dialog's parent. + // + CResizableDialog::OnNotify (wParam, lParam, pResult); + return GetParent ()->SendMessage (WM_NOTIFY, wParam, lParam); +} + +BOOL CTabPageSSL::OnCmdMsg (UINT nID, int nCode, void* pExtra, + AFX_CMDHANDLERINFO* pHandlerInfo) { + // + // Forward ActiveX control events to the dialog's parent. + // +#ifndef _AFX_NO_OCC_SUPPORT + if (nCode == CN_EVENT) + return GetParent ()->OnCmdMsg (nID, nCode, pExtra, pHandlerInfo); +#endif // !_AFX_NO_OCC_SUPPORT + + return CResizableDialog::OnCmdMsg (nID, nCode, pExtra, pHandlerInfo); +} diff --git a/win32/TabPageSSL.h b/win32/TabPageSSL.h new file mode 100644 index 00000000..99a55d7f --- /dev/null +++ b/win32/TabPageSSL.h @@ -0,0 +1,27 @@ +#if !defined(AFX_TABPAGESSL_H__619331B3_7DE7_4DB1_A039_2103E87E8E71__INCLUDED_) +#define AFX_TABPAGESSL_H__619331B3_7DE7_4DB1_A039_2103E87E8E71__INCLUDED_ + +///////////////////////////////////////////////////////////////////////////// +// CTabPageSSL declaration +#include "ResizableDialog.h" + +class CTabPageSSL : public CResizableDialog +{ +public: +// Construction + CTabPageSSL (); // Default Constructor + CTabPageSSL (UINT nIDTemplate, CWnd* pParent = NULL); // Standard Constructor +// Destruction + ~CTabPageSSL (); + +protected: +// Message Handlers + virtual BOOL OnCommand (WPARAM wParam, LPARAM lParam); + virtual BOOL OnNotify (WPARAM wParam, LPARAM lParam, LRESULT* pResult); + virtual void OnOK (void); + virtual void OnCancel (void); + virtual BOOL OnCmdMsg (UINT nID, int nCode, void* pExtra, + AFX_CMDHANDLERINFO* pHandlerInfo); +}; + +#endif // !defined(AFX_TABPAGE_H__619331B3_7DE7_4DB1_A039_2103E87E8E71__INCLUDED_) diff --git a/win32/Traynot.cpp b/win32/Traynot.cpp new file mode 100644 index 00000000..c8afac7e --- /dev/null +++ b/win32/Traynot.cpp @@ -0,0 +1,65 @@ +#include "stdafx.h" +#include "TrayNot.h" +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CTrayNot + +CTrayNot::CTrayNot ( CWnd* pWnd, UINT uCallbackMessage, + LPCTSTR szTip, HICON* pList ) +{ + // this is only for Windows 95 (or higher) + m_bEnabled = ( GetVersion() & 0xff ) >= 4 ; + if (!m_bEnabled) + return ; + + // load up the NOTIFYICONDATA structure + m_tnd.cbSize = sizeof(NOTIFYICONDATA) ; + m_tnd.hWnd = pWnd->GetSafeHwnd() ; + m_tnd.uID = 0 ; + m_tnd.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP ; + m_tnd.uCallbackMessage = uCallbackMessage; + strcpy ( m_tnd.szTip, "Icecast2"); + //or you could use: + //strcpy ( m_tnd.szTip, AfxGetApp()->m_pszAppName); + //this will display the app name instead of the string you specify + + + + // save the pointer to the icon list and set the initial + // default icon. + m_pIconList = pList ; + m_tnd.hIcon = m_pIconList[0] ; + Shell_NotifyIcon (NIM_ADD,&m_tnd); + + + +} + +CTrayNot::~CTrayNot() +{ + if (m_bEnabled) + Shell_NotifyIcon (NIM_DELETE, &m_tnd); +} + +void CTrayNot::SetState(int id) +{ + if (!m_bEnabled) + return; + m_tnd.hIcon = m_pIconList[id]; + m_tnd.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP ; + + //Ive found in windows XP that this command makes the icon not visable in the system tray....we dont want that now + //do we? + Shell_NotifyIcon(NIM_MODIFY, &m_tnd); +} +void CTrayNot::SetTIP(char *pTip) { + memset(m_tnd.szTip, '\000', sizeof(m_tnd.szTip)); + strncpy(m_tnd.szTip, pTip, sizeof(m_tnd.szTip)-1); + + Shell_NotifyIcon(NIM_MODIFY, &m_tnd); +} \ No newline at end of file diff --git a/win32/black.bmp b/win32/black.bmp new file mode 100644 index 0000000000000000000000000000000000000000..59ba65253945a369f7a127a9ac12fda4e850c6b8 GIT binary patch literal 28856 zcmeIup%H*U5Cp+}jYA=kh}Q&wCl|saJPs&wK=f4Yo$qZwk36k)ms)ShT)$v0?7q8z;iYL literal 0 HcmV?d00001 diff --git a/win32/colors.h b/win32/colors.h new file mode 100644 index 00000000..66053d0b --- /dev/null +++ b/win32/colors.h @@ -0,0 +1,5 @@ +#define BGCOLOR RGB(0,0,0) +#define TEXTCOLOR RGB(175,175,175) +#define TEXTBGCOLOR RGB(0,0,0) +#define LOGOCOLOR RGB(175,175,175) + diff --git a/win32/icecast.dsp b/win32/icecast.dsp new file mode 100644 index 00000000..7a2bf851 --- /dev/null +++ b/win32/icecast.dsp @@ -0,0 +1,344 @@ +# Microsoft Developer Studio Project File - Name="icecast" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=icecast - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "icecast.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "icecast.mak" CFG="icecast - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "icecast - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "icecast - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "icecast - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Releaseicecast" +# PROP Intermediate_Dir "Releaseicecast" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../../curl/include" /I "..\src" /I "..\src/httpp" /I "..\src/thread" /I "..\src/log" /I "..\src/avl" /I "..\src/net" /I "..\src/timings" /I "../" /I "../../libxslt/include" /I "../../iconv/include" /I "../../libxml2/include" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "HAVE_CURL" /D "USE_YP" /D "HAVE_SYS_STAT_H" /D PACKAGE_VERSION=\"2.0.0\" /D "HAVE_LOCALTIME_R" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "icecast - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debugicecast" +# PROP Intermediate_Dir "Debugicecast" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../curl/include" /I "..\src" /I "..\src/httpp" /I "..\src/thread" /I "..\src/log" /I "..\src/avl" /I "..\src/net" /I "..\src/timings" /I "../" /I "../../libxslt/include" /I "../../iconv/include" /I "../../libxml2/include" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /D "_WIN32" /D "HAVE_CURL" /D "USE_YP" /D "HAVE_SYS_STAT_H" /D PACKAGE_VERSION=\"2.0.0\" /FD /D /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "icecast - Win32 Release" +# Name "icecast - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\admin.c +# End Source File +# Begin Source File + +SOURCE=..\src\admin.h +# End Source File +# Begin Source File + +SOURCE=..\src\auth.c +# End Source File +# Begin Source File + +SOURCE=..\src\auth.h +# End Source File +# Begin Source File + +SOURCE=..\src\auth_htpasswd.c +# End Source File +# Begin Source File + +SOURCE=..\src\auth_url.c +# End Source File +# Begin Source File + +SOURCE=..\src\avl\avl.c +# End Source File +# Begin Source File + +SOURCE=..\src\avl\avl.h +# End Source File +# Begin Source File + +SOURCE=..\src\cfgfile.c +# End Source File +# Begin Source File + +SOURCE=..\src\cfgfile.h +# End Source File +# Begin Source File + +SOURCE=..\src\client.c +# End Source File +# Begin Source File + +SOURCE=..\src\client.h +# End Source File +# Begin Source File + +SOURCE=..\src\compat.h +# End Source File +# Begin Source File + +SOURCE=..\src\connection.c +# End Source File +# Begin Source File + +SOURCE=..\src\connection.h +# End Source File +# Begin Source File + +SOURCE=..\src\event.c +# End Source File +# Begin Source File + +SOURCE=..\src\event.h +# End Source File +# Begin Source File + +SOURCE=..\src\format.c +# End Source File +# Begin Source File + +SOURCE=..\src\format.h +# End Source File +# Begin Source File + +SOURCE=..\src\format_mp3.c +# End Source File +# Begin Source File + +SOURCE=..\src\format_mp3.h +# End Source File +# Begin Source File + +SOURCE=..\src\format_ogg.c +# End Source File +# Begin Source File + +SOURCE=..\src\format_ogg.h +# End Source File +# Begin Source File + +SOURCE=..\src\fserve.c +# End Source File +# Begin Source File + +SOURCE=..\src\fserve.h +# End Source File +# Begin Source File + +SOURCE=..\src\global.c +# End Source File +# Begin Source File + +SOURCE=..\src\global.h +# End Source File +# Begin Source File + +SOURCE=..\src\httpp\httpp.c +# End Source File +# Begin Source File + +SOURCE=..\src\httpp\httpp.h +# End Source File +# Begin Source File + +SOURCE=..\src\log\log.c +# End Source File +# Begin Source File + +SOURCE=..\src\log\log.h +# End Source File +# Begin Source File + +SOURCE=..\src\logging.c +# End Source File +# Begin Source File + +SOURCE=..\src\logging.h +# End Source File +# Begin Source File + +SOURCE=..\src\main.c +# End Source File +# Begin Source File + +SOURCE=..\src\md5.c +# End Source File +# Begin Source File + +SOURCE=..\src\md5.h +# End Source File +# Begin Source File + +SOURCE=..\src\os.h +# End Source File +# Begin Source File + +SOURCE=..\src\refbuf.c +# End Source File +# Begin Source File + +SOURCE=..\src\refbuf.h +# End Source File +# Begin Source File + +SOURCE=..\src\net\resolver.c +# End Source File +# Begin Source File + +SOURCE=..\src\net\resolver.h +# End Source File +# Begin Source File + +SOURCE=..\src\sighandler.c +# End Source File +# Begin Source File + +SOURCE=..\src\sighandler.h +# End Source File +# Begin Source File + +SOURCE=..\src\slave.c +# End Source File +# Begin Source File + +SOURCE=..\src\net\sock.c +# End Source File +# Begin Source File + +SOURCE=..\src\net\sock.h +# End Source File +# Begin Source File + +SOURCE=..\src\source.c +# End Source File +# Begin Source File + +SOURCE=..\src\source.h +# End Source File +# Begin Source File + +SOURCE=..\src\stats.c +# End Source File +# Begin Source File + +SOURCE=..\src\stats.h +# End Source File +# Begin Source File + +SOURCE=..\src\thread\thread.c +# End Source File +# Begin Source File + +SOURCE=..\src\thread\thread.h +# End Source File +# Begin Source File + +SOURCE=..\src\timing\timing.c +# End Source File +# Begin Source File + +SOURCE=..\src\util.c +# End Source File +# Begin Source File + +SOURCE=..\src\util.h +# End Source File +# Begin Source File + +SOURCE=..\src\xslt.c +# End Source File +# Begin Source File + +SOURCE=..\src\yp.c +# End Source File +# Begin Source File + +SOURCE=..\src\yp.h +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\src\auth_cmd.h +# End Source File +# Begin Source File + +SOURCE=..\src\auth_htpasswd.h +# End Source File +# Begin Source File + +SOURCE=..\src\auth_url.h +# End Source File +# Begin Source File + +SOURCE=..\src\timing\timing.h +# End Source File +# Begin Source File + +SOURCE=..\src\xslt.h +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/icecast.ico b/win32/icecast.ico new file mode 100644 index 0000000000000000000000000000000000000000..a40d61ae5b89f6763cf9c4c3089d8b66a296c5f1 GIT binary patch literal 2238 zcmaLZJxD8A6u|K#yO`|+ku8mdg^0ysCy3cbu@)`GT1a{e@!14h(Nco1Se8Xw6T~V+ zY!;Hj&alXuB4)ml%uHYcE1vz|NlYS7y?W;3p5MJQbLJ*0GRDu$jKuA)UqpW8XpWd6 zekaC+PTJwm@$Q4oAxXLq4|_Yd97?fRl;S@{Y5)Bv$rWOo*eA}28{&az%j~Sm`npOw zt#W*szKoy8r|_HjUHk$56n}v);7j-lzKXBoSMb~Tef$~z z2LFI>;}uV^o|W~itmk=r3crcp#UJ2L@fY|4zJ#yftN1#83O|jX!!P2O@oV@kd67byYStHe_dKhtF18_V)H9o6X9}$%&kwpUdUt zrCeWM%iZ0b+~42Jk+~o5ZIEi+ z3<(=%+i&DsgGbOP?tOksvNzA`xy;{eHf_g_Gj+4=cphD@$;Vf)qoC;;7(Ls8`YoEx zv&Jnjw=lTq;352=pQj>TWWDvfZvSYQuLTM2yA$ymo~@Secc`rIJ8$#XYT=-aQU5wN z?`EV$rmCiy8k=U1B;&)D<9Z$SpW_LF!M%^N_`QXW=XtHs?!j@QVN=T=W`4^j+>Xt} z!6$&U1+_i_2{$z9&%hx4G4P$BH`>fVZ9KCdww4!;-j(Y&qhXRI@;+^5Xf}u7hxB_8 zI`1a$jk6POzlfpLUq-_T&5`d#@g+!)Tx!I$Vh~#1$7yrdEzft~Kgr1W7$n@@5`Nb| zIPpChd*QErm-vm~Ux0r6%ictr=!*Pg;)Qta#ROj;DJ>FRzDBwes%=*A)2-_5lj>1h hb)x(2HTjZyNxde!-*keWBQm#$rz12Yx=GcS{{f|BR?+|f literal 0 HcmV?d00001 diff --git a/win32/icecast2.iss b/win32/icecast2.iss new file mode 100644 index 00000000..181d9beb --- /dev/null +++ b/win32/icecast2.iss @@ -0,0 +1,64 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +[Setup] +AppName=Icecast2 KH47 Win32 +AppVerName=Icecast v2.0.0 KH47 +AppPublisherURL=http://www.icecast.org +AppSupportURL=http://www.icecast.org +AppUpdatesURL=http://www.icecast.org +DefaultDirName={pf}\Icecast2 Win32 +DefaultGroupName=Icecast2 Win32 +AllowNoIcons=yes +LicenseFile=..\COPYING +InfoAfterFile=..\README +OutputDir=. +OutputBaseFilename=icecast2_win32_2.0.0_KH47_setup +WizardImageFile=icecast2logo2.bmp +; uncomment the following line if you want your installation to run on NT 3.51 too. +; MinVersion=4,3.51 + +[Tasks] +Name: "desktopicon"; Description: "Create a &desktop icon"; GroupDescription: "Additional icons:"; MinVersion: 4,4 + +[Dirs] +Name: "{app}\web" +Name: "{app}\admin" +Name: "{app}\doc" +Name: "{app}\logs" + + +[Files] +Source: "Release\Icecast2.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "Release\icecast2console.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\doc\icecast2.chm"; DestDir: "{app}\doc"; Flags: ignoreversion +Source: "..\web\corner_bottomleft.jpg"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\corner_bottomright.jpg"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\corner_topleft.jpg"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\corner_topright.jpg"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\icecast.png"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\key.gif"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\status2.xsl"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\status.xsl"; DestDir: "{app}\web"; Flags: ignoreversion +Source: "..\web\style.css"; DestDir: "{app}\web"; Flags: ignoreversion + +Source: "..\admin\listclients.xsl"; DestDir: "{app}\admin"; Flags: ignoreversion +Source: "..\admin\listmounts.xsl"; DestDir: "{app}\admin"; Flags: ignoreversion +Source: "..\admin\moveclients.xsl"; DestDir: "{app}\admin"; Flags: ignoreversion +Source: "..\admin\response.xsl"; DestDir: "{app}\admin"; Flags: ignoreversion +Source: "..\admin\stats.xsl"; DestDir: "{app}\admin"; Flags: ignoreversion +Source: "..\admin\manageauth.xsl"; DestDir: "{app}\admin"; Flags: ignoreversion +Source: "..\..\pthreads\pthreadVSE.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\conf\icecast.xml"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\..\iconv\lib\iconv.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\..\libxslt\lib\libxslt.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\..\libxml2\lib\libxml2.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\..\curl\lib\Release\libcurl.dll"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] + +Name: "{group}\Icecast2 Win32"; Filename: "{app}\Icecast2.exe";WorkingDir: "{app}"; +Name: "{userdesktop}\Icecast2 Win32"; Filename: "{app}\Icecast2.exe"; MinVersion: 4,4; Tasks: desktopicon;WorkingDir: "{app}"; + +[Run] + diff --git a/win32/icecast2_console.dsp b/win32/icecast2_console.dsp new file mode 100644 index 00000000..ea0c4212 --- /dev/null +++ b/win32/icecast2_console.dsp @@ -0,0 +1,102 @@ +# Microsoft Developer Studio Project File - Name="icecast2 console" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=icecast2 console - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "icecast2_console.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "icecast2_console.mak" CFG="icecast2 console - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "icecast2 console - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "icecast2 console - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "icecast2 console - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "icecast2_console___Win32_Release" +# PROP BASE Intermediate_Dir "icecast2_console___Win32_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../" /I "../../libxslt/include" /I "../../curl/include" /I "../../iconv/include" /I "../../libxml2/include" /I "..\src" /I "..\src/httpp" /I "..\src/thread" /I "..\src/log" /I "..\src/avl" /I "..\src/net" /I "..\src/timings" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "HAVE_CURL" /D "USE_YP" /D "HAVE_SYS_STAT_H" /D PACKAGE_VERSION=\"2.0.0\" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib Releaseicecast\icecast.lib ..\..\curl\lib\Release\libcurl.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\ogg_static.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\vorbis_static.lib ..\..\libxml2\lib\libxml2.lib ..\..\libxslt\lib\libxslt.lib ..\..\iconv\lib\iconv.lib ..\..\pthreads\pthreadVSE.lib ws2_32.lib /nologo /subsystem:console /machine:I386 /out:"Release/icecast2console.exe" + +!ELSEIF "$(CFG)" == "icecast2 console - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "icecast2_console___Win32_Debug" +# PROP BASE Intermediate_Dir "icecast2_console___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "icecast2_console___Win32_Debug" +# PROP Intermediate_Dir "icecast2_console___Win32_Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../" /I "../../libxslt/include" /I "../../curl/include" /I "../../iconv/include" /I "../../libxml2/include" /I "..\src" /I "..\src/httpp" /I "..\src/thread" /I "..\src/log" /I "..\src/avl" /I "..\src/net" /I "..\src/timings" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "HAVE_CURL" /D "USE_YP" /D "HAVE_SYS_STAT_H" /D PACKAGE_VERSION=\"2.0.0\" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib Debugicecast\icecast.lib ..\..\curl\lib\Release\libcurl.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\ogg_static.lib ..\..\oggvorbis-win32sdk-1.0.1\lib\vorbis_static.lib ..\..\libxml2\lib\libxml2.lib ..\..\libxslt\lib\libxslt.lib ..\..\iconv\lib\iconv.lib ..\..\pthreads\pthreadVSE.lib ws2_32.lib /nologo /subsystem:console /debug /machine:I386 /out:"Debug/icecast2console.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "icecast2 console - Win32 Release" +# Name "icecast2 console - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\main.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/win32/icecast2_console.dsw b/win32/icecast2_console.dsw new file mode 100644 index 00000000..bbf52ccc --- /dev/null +++ b/win32/icecast2_console.dsw @@ -0,0 +1,44 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "icecast"=.\icecast.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "icecast2 console"=.\icecast2_console.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name icecast + End Project Dependency +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/win32/icecast2logo2.bmp b/win32/icecast2logo2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2985b00e474e02a9ed10561868941d23e591c903 GIT binary patch literal 16952 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 literal 0 HcmV?d00001 diff --git a/win32/res/Icecast2win.rc2 b/win32/res/Icecast2win.rc2 new file mode 100644 index 00000000..c3231ad8 --- /dev/null +++ b/win32/res/Icecast2win.rc2 @@ -0,0 +1,13 @@ +// +// ICECAST2WIN.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/win32/res/Makefile.am b/win32/res/Makefile.am new file mode 100644 index 00000000..c65cf606 --- /dev/null +++ b/win32/res/Makefile.am @@ -0,0 +1,6 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = foreign + +EXTRA_DIST = Icecast2win.rc2 + diff --git a/win32/resource.h b/win32/resource.h new file mode 100644 index 00000000..cf2e8dcc --- /dev/null +++ b/win32/resource.h @@ -0,0 +1,76 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by Icecast2win.rc +// +#define IDM_ABOUTBOX 0x0010 +#define IDD_ABOUTBOX 100 +#define IDS_ABOUTBOX 101 +#define IDD_ICECAST2WIN_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDR_MENU1 132 +#define IDB_BITMAP1 134 +#define IDB_BITMAP2 135 +#define IDI_R 141 +#define IDI_G 142 +#define IDI_ICON1 142 +#define IDD_SERVERSTATUS 144 +#define IDD_SSTATUS 145 +#define IDD_CONFIGDIALOG 146 +#define IDD_STATSDIALOG 147 +#define IDB_BITMAP3 149 +#define IDB_BITMAP4 150 +#define IDB_BITMAP5 151 +#define IDB_BITMAP6 152 +#define IDR_MENU2 153 +#define IDR_MENU3 154 +#define IDC_CURSOR1 155 +#define IDC_CURSOR2 156 +#define IDB_BITMAP7 159 +#define IDR_TRAY 160 +#define IDR_MENU4 161 +#define IDC_MAINTAB 1000 +#define IDC_ERROR_EDIT 1003 +#define IDC_ACCESS_EDIT 1004 +#define IDC_CONFIG_EDIT 1006 +#define IDC_SERVERSTATUS 1008 +#define IDC_SOURCES_CONNECTED 1009 +#define IDC_NUMBER_CLIENTS 1010 +#define IDC_GROUP1 1011 +#define IDC_STATS_EDIT 1012 +#define IDC_CONFIG 1020 +#define IDC_STATSLIST 1021 +#define IDC_SOURCELIST 1022 +#define IDC_START 1023 +#define IDC_AUTOSTART 1024 +#define IDC_FILLER1 1025 +#define IDC_FILLER2 1026 +#define IDC_STATIC_SS 1029 +#define IDC_GLOBALSTAT_LIST 1030 +#define IDC_STATIC_GS 1031 +#define IDC_STATIC_SLS 1032 +#define IDC_RUNNINGFOR 1033 +#define IDC_STATIC_RUN 1034 +#define IDC_STATICBLACK 1035 +#define IDC_HIDESYSTRAY 1036 +#define ID_FILE_STARTSERVER 32771 +#define ID_FILE_EXIT 32772 +#define ID_FILE_STOPSERVER 32774 +#define ID_FILE 32775 +#define ID_POPUP_ADDTOGLOBALSTATLIST 32776 +#define ID__DELETEFROMGLOBALSTATS 32777 +#define ID__MAKETHISSTATTHEWINDOWTITLE 32779 +#define ID_BLANK_RESTORE 32780 +#define ID_ABOUT_HELP 32781 +#define ID_FILE_EDITCONFIGURATION 32782 +#define ID_ABOUT_CREDITS 32784 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 162 +#define _APS_NEXT_COMMAND_VALUE 32785 +#define _APS_NEXT_CONTROL_VALUE 1037 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/win32/running.bmp b/win32/running.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b5059fc672877c23aff9b233800ff3c17769eb56 GIT binary patch literal 5336 zcmeH|y-fo_5QP_m`Lh9o2_QovfQSf4$Se^!Kq3MXGDJcFv_K1#Knt`$0|b;n3rKwC ziKh{|#A3a&tUcYGX2)-5=DnF)4=&!f)6aFv`;g~`XXKhoJG^ymby6BVzgPQfv))yK zs=&-Bu=}=t^0dl*^xWxw_GX6HxI_l`%iQR2dA@jk+@4M;`X3v6uWQ9>yWL-4D#leZ z7oJAY4oAbj>(4W;DDco~sZG4O!N_Sa06Z+!Q28UqfS4tMAx=7AOa=20D{+=GMy&AQ zrR&OvS4-5U8n7p!1M}u|sK-1^gAU99t?^_=ecZv=Ya}uY18_!+0gchv#SFOJ5o2dD zgX}7RcAPz~IB%@VG$TilrfC4qh%ulsTHM9Ph;bRk46>^LL_i8Y4W*7%x!}kVm}we- zGhz&AjHbiEOe4n5Vn)nPVMw9W(QlgK$Z>&zlkZ6XCfHEsjo*g}aiR|T=m5lXfbT-l zl$r>|i2q3x`HH3h6vk3E21{&CHcI6r5m^t904#~dPXM$LFk7${jU@n%U;h_lFo$yT zt21hQ$+v2t09eo+mKn2Khm0{N6iV%YJ~w5%Q(4jx)3cE!U=30gs0vgCssjH}Af<2e C$_=If literal 0 HcmV?d00001 diff --git a/win32/stopped.bmp b/win32/stopped.bmp new file mode 100644 index 0000000000000000000000000000000000000000..db201ac4d260cf9d400cc0a04a95488c82caefe3 GIT binary patch literal 5336 zcmeIyL24C26a~=!V`m=+&NFeYxB$u2VZaq+3t2+4gk%Z21YJVrq9lkQh+qOj2uL8} zi5DM5{T>BUQR$|B^ZM1T_wKz_^`F0Z|8V;KJoA3+_tNhswVEDz=UG3PrkmfNXSI5_ zjKzV)fxpLr-Q6$S+n+i}GwJ`w>$H$jwQ)HZL87r7PHk
    + +

    Server Status Tab

    +
    +

    Overview

    +

    +The server status tab contains information regarding statistics that are global to the server. There are two types of statistics in icecast2, source level and global statistics. Global statistics are those that are accumilations of stats from all sources offered by the server. Source level statistics are stats which apply only to a single source attached to the server. +

    +

    +Examples of global statistics are : +

    +
    +	The number of current sources connected
    +	The number of sources that have attempted connections
    +	Total number of attempted connections to the server
    +
    +

    +

    +The Server Status tab contains at a minimal the global stats for the server. Additionally, you may add source specific stats to this tab. The intent is to provide a single "dashboard view" of what's going on in the server. To add source statistics to the Server Status tab, see the section on the Stats tab. +

    +

    Adding stats to the window title

    +

    +Any stat that is contained on the Server Status tab can be displayed as the icecast2 window title. This provides yet another mechanism by which you can view activities on the server. To enable this feature, right click on any stat in the Server Status tab as seen below : +

    + +

    +

    Removing source level stats from the Server Status Tab

    +

    +To remove a source level stat that you have inserted onto the Server Status Tab, simple right click that statistic and select "Delete from Global Stats". The stat will be deleted from the Server Status tab, but will still remain on the source level Stats tab. +

    + +

    +