[xiph-commits] r8203 - in icecast/branches/kh/icecast: admin conf doc src web

karl at motherfish-iii.xiph.org karl at motherfish-iii.xiph.org
Mon Nov 15 09:20:20 PST 2004


Author: karl
Date: 2004-11-15 09:20:19 -0800 (Mon, 15 Nov 2004)
New Revision: 8203

Added:
   icecast/branches/kh/icecast/conf/icecast_minimal.xml.in
   icecast/branches/kh/icecast/conf/icecast_shoutcast_compat.xml.in
Modified:
   icecast/branches/kh/icecast/admin/listclients.xsl
   icecast/branches/kh/icecast/admin/listmounts.xsl
   icecast/branches/kh/icecast/admin/manageauth.xsl
   icecast/branches/kh/icecast/admin/moveclients.xsl
   icecast/branches/kh/icecast/admin/stats.xsl
   icecast/branches/kh/icecast/conf/Makefile.am
   icecast/branches/kh/icecast/conf/icecast.xml.in
   icecast/branches/kh/icecast/doc/icecast2_config_file.html
   icecast/branches/kh/icecast/src/admin.c
   icecast/branches/kh/icecast/src/cfgfile.c
   icecast/branches/kh/icecast/src/cfgfile.h
   icecast/branches/kh/icecast/src/compat.h
   icecast/branches/kh/icecast/src/connection.c
   icecast/branches/kh/icecast/src/format.c
   icecast/branches/kh/icecast/src/format.h
   icecast/branches/kh/icecast/src/fserve.c
   icecast/branches/kh/icecast/src/fserve.h
   icecast/branches/kh/icecast/src/slave.c
   icecast/branches/kh/icecast/src/source.c
   icecast/branches/kh/icecast/src/source.h
   icecast/branches/kh/icecast/src/util.c
   icecast/branches/kh/icecast/src/util.h
   icecast/branches/kh/icecast/src/yp.c
   icecast/branches/kh/icecast/web/auth.xsl
   icecast/branches/kh/icecast/web/status.xsl
Log:
another resync with recent trunk updates. 


Modified: icecast/branches/kh/icecast/admin/listclients.xsl
===================================================================
--- icecast/branches/kh/icecast/admin/listclients.xsl	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/admin/listclients.xsl	2004-11-15 17:20:19 UTC (rev 8203)
@@ -57,6 +57,7 @@
 <br />
 <br />
 </xsl:for-each>
+<xsl:text disable-output-escaping="yes">&amp;</xsl:text>nbsp;
 </div>
 <div class="roundbottom">
 <img src="/corner_bottomleft.jpg" class="corner" style="display: none" />

Modified: icecast/branches/kh/icecast/admin/listmounts.xsl
===================================================================
--- icecast/branches/kh/icecast/admin/listmounts.xsl	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/admin/listmounts.xsl	2004-11-15 17:20:19 UTC (rev 8203)
@@ -43,6 +43,7 @@
 <p><xsl:value-of select="listeners" /> Listener(s)</p>
 <br></br>
 </xsl:for-each>
+<xsl:text disable-output-escaping="yes">&amp;</xsl:text>nbsp;
 </div>
 <div class="roundbottom">
 <img src="/corner_bottomleft.jpg" class="corner" style="display: none" />

Modified: icecast/branches/kh/icecast/admin/manageauth.xsl
===================================================================
--- icecast/branches/kh/icecast/admin/manageauth.xsl	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/admin/manageauth.xsl	2004-11-15 17:20:19 UTC (rev 8203)
@@ -69,6 +69,7 @@
 <br />
 <br />
 </xsl:for-each>
+<xsl:text disable-output-escaping="yes">&amp;</xsl:text>nbsp;
 </div>
 <div class="roundbottom">
 <img src="/corner_bottomleft.jpg" class="corner" style="display: none" />

Modified: icecast/branches/kh/icecast/admin/moveclients.xsl
===================================================================
--- icecast/branches/kh/icecast/admin/moveclients.xsl	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/admin/moveclients.xsl	2004-11-15 17:20:19 UTC (rev 8203)
@@ -37,6 +37,7 @@
 <br />
 <br />
 </xsl:for-each>
+<xsl:text disable-output-escaping="yes">&amp;</xsl:text>nbsp;
 </div>
 <div class="roundbottom">
 <img src="/corner_bottomleft.jpg" class="corner" style="display: none" />

Modified: icecast/branches/kh/icecast/admin/stats.xsl
===================================================================
--- icecast/branches/kh/icecast/admin/stats.xsl	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/admin/stats.xsl	2004-11-15 17:20:19 UTC (rev 8203)
@@ -79,6 +79,7 @@
 <br />
 </xsl:if>
 </xsl:for-each>
+<xsl:text disable-output-escaping="yes">&amp;</xsl:text>nbsp;
 </div>
 <div class="roundbottom">
 <img src="/corner_bottomleft.jpg" class="corner" style="display: none" />

Modified: icecast/branches/kh/icecast/conf/Makefile.am
===================================================================
--- icecast/branches/kh/icecast/conf/Makefile.am	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/conf/Makefile.am	2004-11-15 17:20:19 UTC (rev 8203)
@@ -2,11 +2,11 @@
 
 AUTOMAKE_OPTIONS = foreign
 
-EXTRA_DIST = icecast.xml.in
-DISTCLEANFILES = icecast.xml.dist
+EXTRA_DIST = icecast.xml.in icecast_minimal.xml.in icecast_shoutcast_compat.xml.in
+DISTCLEANFILES = icecast.xml.dist icecast_minimal.xml.dist icecast_shoutcast_compat.xml.dist
 
 docdir = $(datadir)/$(PACKAGE)/doc
-doc_DATA = icecast.xml.dist
+doc_DATA = icecast.xml.dist icecast_minimal.xml.dist icecast_shoutcast_compat.xml.dist
 
 install-data-hook:
 	$(mkinstalldirs) $(DESTDIR)$(sysconfdir)
@@ -20,6 +20,12 @@
 icecast.xml.dist: $(srcdir)/icecast.xml.in
 	$(edit) $(srcdir)/icecast.xml.in > icecast.xml.dist
 
+icecast_minimal.xml.dist: $(srcdir)/icecast_minimal.xml.in
+	$(edit) $(srcdir)/icecast_minimal.xml.in > icecast_minimal.xml.dist
+
+icecast_shoutcast_compat.xml.dist: $(srcdir)/icecast_shoutcast_compat.xml.in
+	$(edit) $(srcdir)/icecast_shoutcast_compat.xml.in > icecast_shoutcast_compat.xml.dist
+
 debug:
 	$(MAKE) all CFLAGS="@DEBUG@"
 

Modified: icecast/branches/kh/icecast/conf/icecast.xml.in
===================================================================
--- icecast/branches/kh/icecast/conf/icecast.xml.in	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/conf/icecast.xml.in	2004-11-15 17:20:19 UTC (rev 8203)
@@ -166,6 +166,7 @@
     <logging>
         <accesslog>access.log</accesslog>
         <errorlog>error.log</errorlog>
+        <!-- <playlistlog>playlist.log</playlistlog> -->
       	<loglevel>4</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
     </logging>
 

Added: icecast/branches/kh/icecast/conf/icecast_minimal.xml.in
===================================================================
--- icecast/branches/kh/icecast/conf/icecast_minimal.xml.in	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/conf/icecast_minimal.xml.in	2004-11-15 17:20:19 UTC (rev 8203)
@@ -0,0 +1,37 @@
+<!-- This config file contains a minimal set of configurable parameters,
+     and mostly just contains the things you need to change.  We created
+     this for those who got scared away from the rather large and heavily
+     commented icecast.xml.dist file. -->
+<icecast>
+    <limits>
+        <sources>2</sources>
+    </limits>
+    <authentication>
+        <source-password>hackme</source-password>
+        <relay-password>hackme</relay-password>
+        <admin-user>admin</admin-user>
+        <admin-password>hackme</admin-password>
+    </authentication>
+    <directory>
+        <yp-url-timeout>15</yp-url-timeout>
+        <yp-url>http://dir.xiph.org/cgi-bin/yp-cgi</yp-url>
+    </directory>
+    <hostname>localhost</hostname>
+    <listen-socket>
+        <port>8000</port>
+    </listen-socket>
+    <fileserve>1</fileserve>
+    <paths>
+        <logdir>@localstatedir@/log/@PACKAGE@</logdir>
+        <webroot>@pkgdatadir@/web</webroot>
+        <adminroot>@pkgdatadir@/admin</adminroot>
+    </paths>
+    <logging>
+        <accesslog>access.log</accesslog>
+        <errorlog>error.log</errorlog>
+      	<loglevel>3</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
+    </logging>
+    <security>
+        <chroot>0</chroot>
+    </security>
+</icecast>

Added: icecast/branches/kh/icecast/conf/icecast_shoutcast_compat.xml.in
===================================================================
--- icecast/branches/kh/icecast/conf/icecast_shoutcast_compat.xml.in	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/conf/icecast_shoutcast_compat.xml.in	2004-11-15 17:20:19 UTC (rev 8203)
@@ -0,0 +1,53 @@
+<!-- This config file can be used to configure icecast
+     in shoutcast compatibility mode which will allow
+     you to connect the Shoutcast DSP (or other Nullsoft
+     encoders such as the NSV encoder). Note this is just
+     a minimal config, check the main icecast.xml.dist file
+     for a complete list of possible configuration options -->
+<icecast>
+    <limits>
+        <sources>2</sources>
+    </limits>
+    <authentication>
+        <!-- Configure the shoutcast DSP to use this password -->
+        <source-password>hackme</source-password>
+        <!-- This is used for icecast's web interface -->
+        <admin-user>admin</admin-user>
+        <admin-password>hackme</admin-password>
+    </authentication>
+    <directory>
+        <yp-url-timeout>15</yp-url-timeout>
+        <yp-url>http://dir.xiph.org/cgi-bin/yp-cgi</yp-url>
+    </directory>
+    <!-- This is the hostname other people will use to connect to your server.
+    It affects mainly the urls generated by Icecast for playlists and yp
+    listings. -->
+    <hostname>localhost</hostname>
+    <!-- You MUST define 2 ports, port and port +1 -->
+    <listen-socket>
+        <!-- Configure the shoutcast DSP with *this* port 
+             the shoutcast DSP actually will connect the
+             encoder to this port + 1 -->
+        <port>8000</port>
+    </listen-socket>
+    <listen-socket>
+        <!-- This port *must* be one larger than the one defined
+             above and defined as 'shoutcast-compat' -->
+        <port>8001</port>
+        <shoutcast-compat>1</shoutcast-compat>
+    </listen-socket>
+    <fileserve>1</fileserve>
+    <paths>
+        <logdir>@localstatedir@/log/@PACKAGE@</logdir>
+        <webroot>@pkgdatadir@/web</webroot>
+        <adminroot>@pkgdatadir@/admin</adminroot>
+    </paths>
+    <logging>
+        <accesslog>access.log</accesslog>
+        <errorlog>error.log</errorlog>
+      	<loglevel>3</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
+    </logging>
+    <security>
+        <chroot>0</chroot>
+    </security>
+</icecast>

Modified: icecast/branches/kh/icecast/doc/icecast2_config_file.html
===================================================================
--- icecast/branches/kh/icecast/doc/icecast2_config_file.html	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/doc/icecast2_config_file.html	2004-11-15 17:20:19 UTC (rev 8203)
@@ -157,6 +157,11 @@
         &lt;port&gt;8000&lt;/port&gt;
         &lt;bind-address&gt;127.0.0.1&lt;/bind-address&gt;
     &lt;/listen-socket&gt;
+    &lt;listen-socket&gt;
+        &lt;port&gt;8001&lt;/port&gt;
+        &lt;bind-address&gt;127.0.0.1&lt;/bind-address&gt;
+        &lt;shoutcast-compat&gt;1&lt;/shoutcast-compat&gt;
+    &lt;/listen-socket&gt;
 
     &lt;fileserve&gt;1&lt;/fileserve&gt;
 </pre>
@@ -170,6 +175,10 @@
 <div class="indentedbox">
 And option IP address that can be used to bind to a specific network card.  If not supplied, then &lt;hostname&gt; will be used.
 </div>
+<h4>shoutcast-compat</h4>
+<div class="indentedbox">
+This optional flag will indicate that this port will operate in 'shoutcast-compatibility' mode.  Due to major differences in the source client connection protocol, if you wish to use any of the shoutcast DJ tools, you will need to configure at least one socket as shoutcast-compatible.  Note that when in this mode, only source clients (and specifically shoutcast source clients) will be able to attach to this port.  All listeners may connect to any of the ports defined without this flag.  Also, for proper Shoutcast DSP compatibility, you must define a listen socket with a port one less than the one defined as 'shoutcast-compat'.  This means if you define 8001 as shoutcast-compat, then you will need to define a listen port of 8000 and it must not also be defined as shoutcast-compat.  See the example config file is the distribution for more info.
+</div>
 <h4>fileserve</h4>
 <div class="indentedbox">
 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 &lt;paths&gt;&lt;webroot&gt; configuration setting.

Modified: icecast/branches/kh/icecast/src/admin.c
===================================================================
--- icecast/branches/kh/icecast/src/admin.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/admin.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -51,6 +51,7 @@
 #define COMMAND_RAW_SHOW_LISTENERS  3
 #define COMMAND_RAW_MOVE_CLIENTS    4
 #define COMMAND_RAW_MANAGEAUTH      5
+#define COMMAND_SHOUTCAST_METADATA_UPDATE   6
 
 #define COMMAND_TRANSFORMED_FALLBACK        50
 #define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
@@ -79,6 +80,7 @@
 
 #define FALLBACK_RAW_REQUEST "fallbacks"
 #define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
+#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
 #define METADATA_REQUEST "metadata"
 #define LISTCLIENTS_RAW_REQUEST "listclients"
 #define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl"
@@ -116,6 +118,8 @@
         return COMMAND_TRANSFORMED_FALLBACK;
     else if(!strcmp(command, METADATA_REQUEST))
         return COMMAND_METADATA_UPDATE;
+    else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST))
+        return COMMAND_SHOUTCAST_METADATA_UPDATE;
     else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST))
         return COMMAND_RAW_SHOW_LISTENERS;
     else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST))
@@ -166,6 +170,7 @@
 
 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_shoutcast_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,
@@ -289,14 +294,22 @@
 {
     char *mount, *command_string;
     int command;
+    int noauth = 0;
 
-    if(strncmp("/admin/", uri, 7)) {
+    DEBUG1("Admin request (%s)", uri);
+    if (!((strcmp(uri, "/admin.cgi") == 0) ||
+                (strncmp("/admin/", uri, 7) == 0))) {
         ERROR0("Internal error: admin request isn't");
         client_send_401(client);
         return;
     }
 
-    command_string = uri + 7;
+    if (strcmp(uri, "/admin.cgi") == 0) {
+        command_string = uri + 1;
+    }
+    else {
+        command_string = uri + 7;
+    }
 
     DEBUG1("Got command (%s)", command_string);
     command = admin_get_command(command_string);
@@ -310,6 +323,31 @@
 
     mount = httpp_get_query_param(client->parser, "mount");
 
+    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {
+        source_t *source;
+
+        mount = "/";
+        noauth = 1;
+        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, "Mount / does not exist");
+            return;
+        }
+        else {
+            if (source->shoutcast_compat == 0) {
+                ERROR0("Illegal call to change metadata, source not shoutcast compatible");
+                avl_tree_unlock (global.source_tree);
+                client_send_400 (client, "Illegal metadata call");
+                return;
+            }
+        }
+        avl_tree_unlock(global.source_tree);
+    }
+
     if(mount != NULL) {
         source_t *source;
 
@@ -334,13 +372,16 @@
         }
         else
         {
-            if (source->running == 0 && source->on_demand == 0)
+            if (!source->shoutcast_compat)
             {
-                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;
+                if (source->running == 0 && source->on_demand == 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);
@@ -434,6 +475,9 @@
         case COMMAND_METADATA_UPDATE:
             command_metadata(client, source);
             break;
+        case COMMAND_SHOUTCAST_METADATA_UPDATE:
+            command_shoutcast_metadata(client, source);
+            break;
         case COMMAND_RAW_SHOW_LISTENERS:
             command_show_listeners(client, source, RAW);
             break;
@@ -907,10 +951,77 @@
     else
     {
         thread_mutex_unlock (&source->lock);
-        client_send_400 (client, "source will not accept URL updates");
+        client_send_400 (client, "mountpoint will not accept URL updates");
     }
 }
 
+static void command_shoutcast_metadata(client_t *client, source_t *source)
+{
+    char *action;
+    char *value;
+    char *source_pass;
+    char *config_source_pass;
+    ice_config_t *config;
+
+    DEBUG0("Got shoutcast metadata update request");
+
+    COMMAND_REQUIRE(client, "mode", action);
+    COMMAND_REQUIRE(client, "song", value);
+    COMMAND_REQUIRE(client, "pass", source_pass);
+
+    config = config_get_config();
+    config_source_pass = strdup(config->source_password);
+    config_release_config();
+
+    if ((source->format->type != FORMAT_TYPE_MP3) &&
+            (source->format->type != FORMAT_TYPE_NSV))
+    {
+        thread_mutex_unlock (&source->lock);
+        client_send_400 (client, "Not mp3 or NSV, cannot update metadata");
+        return;
+    }
+
+    if (strcmp (action, "updinfo") != 0)
+    {
+        thread_mutex_unlock (&source->lock);
+        client_send_400 (client, "No such action");
+        return;
+    }
+
+    if (strcmp(source_pass, config_source_pass) != 0)
+    {
+        thread_mutex_unlock (&source->lock);
+        ERROR0("Invalid source password specified, metadata not updated");
+        client_send_400 (client, "Invalid source password");
+        return;
+    }
+
+    if (config_source_pass) {
+        free(config_source_pass);
+    }
+
+    if (source->format && source->format->set_tag)
+    {
+        source->format->set_tag (source->format, "title", value);
+
+        DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
+                source->mount, value);
+        stats_event(source->mount, "title", value);
+        /* If we get an update on the mountpoint, force a
+         * yp touch */
+        yp_touch (source->mount);
+
+        thread_mutex_unlock (&source->lock);
+        html_success(client, "Metadata update successful");
+    }
+    else
+    {
+        thread_mutex_unlock (&source->lock);
+        client_send_400 (client, "mountpoint will not accept URL updates");
+    }
+}
+
+
 static void command_stats(client_t *client, int response) {
     xmlDocPtr doc;
 

Modified: icecast/branches/kh/icecast/src/cfgfile.c
===================================================================
--- icecast/branches/kh/icecast/src/cfgfile.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/cfgfile.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -333,6 +333,7 @@
     configuration->port = 0;
     configuration->listeners[0].port = 0;
     configuration->listeners[0].bind_address = NULL;
+    configuration->listeners[0].shoutcast_compat = 0;
     configuration->master_server = NULL;
     configuration->master_server_port = 0;
     configuration->master_update_interval = CONFIG_MASTER_UPDATE_INTERVAL;
@@ -746,6 +747,11 @@
             listener->port = atoi(tmp);
             if(tmp) xmlFree(tmp);
         }
+        else if (strcmp(node->name, "shoutcast-compat") == 0) {
+            tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+            listener->shoutcast_compat = atoi(tmp);
+            if(tmp) xmlFree(tmp);
+        }
         else if (strcmp(node->name, "bind-address") == 0) {
             listener->bind_address = (char *)xmlNodeListGetString(doc, 
                     node->xmlChildrenNode, 1);

Modified: icecast/branches/kh/icecast/src/cfgfile.h
===================================================================
--- icecast/branches/kh/icecast/src/cfgfile.h	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/cfgfile.h	2004-11-15 17:20:19 UTC (rev 8203)
@@ -85,6 +85,7 @@
 typedef struct {
     int port;
     char *bind_address;
+    int shoutcast_compat;
 } listener_t;
 
 typedef struct ice_config_tag

Modified: icecast/branches/kh/icecast/src/compat.h
===================================================================
--- icecast/branches/kh/icecast/src/compat.h	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/compat.h	2004-11-15 17:20:19 UTC (rev 8203)
@@ -10,6 +10,9 @@
  *                      and others (see AUTHORS for details).
  */
 
+#ifndef __COMPAT_H__
+#define __COMPAT_H__
+
 /* compat.h
  * 
  * This file contains most of the ugliness for header portability
@@ -29,3 +32,12 @@
 #    include <inttypes.h>
 #  endif
 #endif
+
+#ifdef _WIN32
+#define FORMAT_INT64      "%I64d"
+#else
+#define FORMAT_INT64      "%lld"
+#endif
+
+#endif /* __COMPAT_H__ */
+

Modified: icecast/branches/kh/icecast/src/connection.c
===================================================================
--- icecast/branches/kh/icecast/src/connection.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/connection.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -64,6 +64,23 @@
 
 #define CATMODULE "connection"
 
+/* Two different major types of source authentication.
+   Shoutcast style is used only by the Shoutcast DSP
+   and is a crazy version of HTTP.  It looks like :
+     Source Client -> Connects to port + 1
+     Source Client -> sends encoder password (plaintext)\r\n
+     Icecast -> reads encoder password, if ok, sends OK2\r\n, else disconnects
+     Source Client -> reads OK2\r\n, then sends http-type request headers
+                      that contain the stream details (icy-name, etc..)
+     Icecast -> reads headers, stores them
+     Source Client -> starts sending MP3 data
+     Source Client -> periodically updates metadata via admin.cgi call
+
+   Icecast auth style uses HTTP and Basic Authorization.
+*/
+#define SHOUTCAST_SOURCE_AUTH 1
+#define ICECAST_SOURCE_AUTH 0
+
 typedef struct con_queue_tag {
     connection_t *con;
     struct con_queue_tag *next;
@@ -642,7 +659,7 @@
 
 
 static void _handle_source_request(connection_t *con, 
-        http_parser_t *parser, char *uri)
+        http_parser_t *parser, char *uri, int auth_style)
 {
     client_t *client;
     source_t *source;
@@ -657,19 +674,24 @@
         client_send_401 (client);
         return;
     }
-    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;
+    if (auth_style == ICECAST_SOURCE_AUTH) {
+        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)
     {
+        if (auth_style == SHOUTCAST_SOURCE_AUTH) {
+            source->shoutcast_compat = 1;
+        }
         source->client = client;
         source->parser = parser;
         source->con = con;
@@ -774,7 +796,8 @@
     stats_event_inc(NULL, "client_connections");
 
     /* Dispatch all admin requests */
-    if (strncmp(uri, "/admin/", 7) == 0) {
+    if ((strcmp(uri, "/admin.cgi") == 0) ||
+            (strncmp(uri, "/admin/", 7) == 0)) {
         admin_handle_request(client, uri);
         if (uri != passed_uri) free (uri);
         return;
@@ -857,6 +880,74 @@
     if (uri != passed_uri) free (uri);
 }
 
+void _handle_shoutcast_compatible(connection_t *con, char *source_password) {
+    char shoutcast_password[256];
+    char shoutcast_source[256];
+    char *http_compliant;
+    int http_compliant_len = 0;
+    char header[4096];
+    http_parser_t *parser;
+
+    memset(shoutcast_password, 0, sizeof (shoutcast_password));
+    /* Step one of shoutcast auth protocol, read encoder password (1 line) */
+    if (util_read_header(con->sock, shoutcast_password, 
+            sizeof (shoutcast_password), 
+            READ_LINE) == 0) {
+        /* either we didn't get a complete line, or we timed out */
+        connection_close(con);
+        return;
+    }
+    /* Get rid of trailing \n */
+    shoutcast_password[strlen(shoutcast_password)-1] = '\000';
+    if (strcmp(shoutcast_password, source_password)) {
+        ERROR0("Invalid source password");
+        connection_close(con);
+        return;
+    }
+    /* Step two of shoutcast auth protocol, send OK2.  For those
+       interested, OK2 means it supports metadata updates via admin.cgi,
+       and the string "OK" can also be sent, but will indicate to the
+       shoutcast source client to not send metadata updates.
+       I believe icecast 1.x used to send OK. */
+    sock_write(con->sock, "%s\r\n", "OK2");
+
+    memset(header, 0, sizeof (header));
+    /* Step three of shoutcast auth protocol, read HTTP-style
+       request headers and process them.*/
+    if (util_read_header(con->sock, header, sizeof (header), 
+                         READ_ENTIRE_HEADER) == 0) {
+        /* either we didn't get a complete header, or we timed out */
+        connection_close(con);
+        return;
+    }
+    /* Here we create a valid HTTP request based of the information
+       that was passed in via the non-HTTP style protocol above. This
+       means we can use some of our existing code to handle this case */
+    memset(shoutcast_source, 0, sizeof (shoutcast_source));
+    strcpy(shoutcast_source, "SOURCE / HTTP/1.0\r\n");
+    http_compliant_len = strlen(shoutcast_source) + 
+                         strlen(header) + 1;
+    http_compliant = (char *)calloc(1, http_compliant_len);
+    sprintf(http_compliant, "%s%s", shoutcast_source, 
+        header);
+    parser = httpp_create_parser();
+    httpp_initialize(parser, NULL);
+    if (httpp_parse(parser, http_compliant, 
+        strlen(http_compliant))) {
+        _handle_source_request(con, parser, "/", SHOUTCAST_SOURCE_AUTH);
+        free(http_compliant);
+        return;
+    }
+    else {
+        ERROR0("Invalid source request");
+        connection_close(con);
+        free(http_compliant);
+        httpp_destroy(parser);
+        return;
+    }
+    return;
+}
+
 static void *_handle_connection(void *arg)
 {
     char header[4096];
@@ -864,6 +955,10 @@
     http_parser_t *parser;
     char *rawuri, *uri;
     client_t *client;
+    int i = 0;
+    int continue_flag = 0;
+    ice_config_t *config;
+    char *source_password;
 
     while (global.running == ICE_RUNNING) {
 
@@ -888,9 +983,30 @@
 
             sock_set_blocking(con->sock, SOCK_BLOCK);
 
+            continue_flag = 0;
+            /* Check for special shoutcast compatability processing */
+            for(i = 0; i < MAX_LISTEN_SOCKETS; i++) {
+                if(global.serversock[i] == con->serversock) {
+                    config = config_get_config();
+                    if (config->listeners[i].shoutcast_compat) {
+                        source_password = strdup(config->source_password);
+                        config_release_config();
+                        _handle_shoutcast_compatible(con, source_password);
+                        free(source_password);
+                        continue_flag = 1;
+                        break;
+                    }
+                    config_release_config();
+                }
+            }
+            if(continue_flag) {
+                continue;
+            }
+
             /* fill header with the http header */
             memset(header, 0, sizeof (header));
-            if (util_read_header(con->sock, header, sizeof (header)) == 0) {
+            if (util_read_header(con->sock, header, sizeof (header), 
+                        READ_ENTIRE_HEADER) == 0) {
                 /* either we didn't get a complete header, or we timed out */
                 connection_close(con);
                 continue;
@@ -919,7 +1035,7 @@
                 }
 
                 if (parser->req_type == httpp_req_source) {
-                    _handle_source_request(con, parser, uri);
+                    _handle_source_request(con, parser, uri, ICECAST_SOURCE_AUTH);
                 }
                 else if (parser->req_type == httpp_req_stats) {
                     _handle_stats_request(con, parser, uri);
@@ -936,22 +1052,6 @@
                 free(uri);
                 continue;
             } 
-            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);
-                continue;
-            }
             else {
                 ERROR0("HTTP request parsing failed");
                 connection_close(con);

Modified: icecast/branches/kh/icecast/src/format.c
===================================================================
--- icecast/branches/kh/icecast/src/format.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/format.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -40,6 +40,7 @@
 #include "format_mp3.h"
 
 #include "logging.h"
+#include "stats.h"
 #define CATMODULE "format"
 
 #ifdef WIN32
@@ -61,11 +62,13 @@
         return FORMAT_TYPE_MP3; 
     else if(strcmp(contenttype, "audio/x-mpeg") == 0)
         return FORMAT_TYPE_MP3; 
+    else if(strcmp(contenttype, "video/nsv") == 0)
+        return FORMAT_TYPE_NSV;
     else
         return FORMAT_ERROR;
 }
 
-char *format_get_mimetype(format_type_t type)
+const char *format_get_mimetype(format_type_t type)
 {
     switch(type) {
         case FORMAT_TYPE_OGG:
@@ -74,6 +77,9 @@
         case FORMAT_TYPE_MP3:
             return "audio/mpeg";
             break;
+        case FORMAT_TYPE_NSV:
+            return "video/nsv";
+            break;
         default:
             return NULL;
     }
@@ -91,9 +97,16 @@
     case FORMAT_TYPE_MP3:
         ret = format_mp3_get_plugin (source);
         break;
+     case FORMAT_TYPE_NSV:
+        ret = format_mp3_get_plugin (source);
+        source->format->format_description = "NSV Video";
+        source->format->type = FORMAT_TYPE_NSV;
+        break;
     default:
         break;
     }
+    stats_event (source->mount, "content-type", 
+            format_get_mimetype(source->format->type));
 
     return ret;
 }

Modified: icecast/branches/kh/icecast/src/format.h
===================================================================
--- icecast/branches/kh/icecast/src/format.h	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/format.h	2004-11-15 17:20:19 UTC (rev 8203)
@@ -30,7 +30,8 @@
     FORMAT_ERROR, /* No format, source not processable */
     FORMAT_TYPE_OGG,
     FORMAT_TYPE_VORBIS,
-    FORMAT_TYPE_MP3
+    FORMAT_TYPE_MP3,
+    FORMAT_TYPE_NSV
 } format_type_t;
 
 typedef struct _format_plugin_tag
@@ -56,7 +57,7 @@
 } format_plugin_t;
 
 format_type_t format_get_type(char *contenttype);
-char *format_get_mimetype(format_type_t type);
+const char *format_get_mimetype(format_type_t type);
 int format_get_plugin(format_type_t type, struct source_tag *source);
 
 int format_generic_write_to_client (struct source_tag *source, client_t *client);

Modified: icecast/branches/kh/icecast/src/fserve.c
===================================================================
--- icecast/branches/kh/icecast/src/fserve.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/fserve.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -355,6 +355,11 @@
     int bytes;
     int client_limit;
     ice_config_t *config = config_get_config();
+    struct stat file_buf;
+    char *range = NULL;
+    int64_t new_content_len = 0;
+    int64_t rangenumber = 0;
+    int ret = 0;
 
     client_limit = config->client_limit;
     config_release_config();
@@ -371,38 +376,97 @@
     client_set_queue (httpclient, NULL);
     httpclient->refbuf = refbuf_new (BUFSIZE);
     httpclient->pos = BUFSIZE;
+    if (stat(path, &file_buf) == 0) {
+        client->content_length = (int64_t)file_buf.st_size;
+    }
 
     global_lock();
     if(global.clients >= client_limit) {
-        httpclient->respcode = 504;
+        global_unlock();
+        httpclient->respcode = 503;
         bytes = sock_write(httpclient->con->sock,
-                "HTTP/1.0 504 Server Full\r\n"
+                "HTTP/1.0 503 Service Unavailable\r\n"
                 "Content-Type: text/html\r\n\r\n"
                 "<b>Server is full, try again later.</b>\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;
+    range = httpp_getvar (client->client->parser, "range");
 
-    sock_set_blocking(client->client->con->sock, SOCK_NONBLOCK);
-    sock_set_nodelay(client->client->con->sock);
+    do
+    {
+        if (range)
+        {
+            ret = sscanf(range, "bytes=" FORMAT_INT64 "-", &rangenumber);
+            if (ret == 1 && rangenumber>=0 && rangenumber < client->content_length)
+            {
+                /* Date: is required on all HTTP1.1 responses */
+                char currenttime[50];
+                time_t now;
+                int strflen;
+                struct tm result;
+                int64_t endpos;
 
-    thread_mutex_lock (&pending_lock);
-    client->next = (fserve_t *)pending_list;
-    pending_list = client;
-    thread_mutex_unlock (&pending_lock);
+                ret = fseek(client->file, rangenumber, SEEK_SET);
+                if (ret == -1)
+                    break;
 
-    return 0;
+                new_content_len = client->content_length - rangenumber;
+                endpos = rangenumber + new_content_len - 1;
+                if (endpos < 0)
+                    endpos = 0;
+                
+                time(&now);
+                strflen = strftime(currenttime, 50, "%a, %d-%b-%Y %X GMT",
+                                   gmtime_r (&now, &result));
+                httpclient->respcode = 206;
+                bytes = sock_write(httpclient->con->sock,
+                    "HTTP/1.1 206 Partial Content\r\n"
+                    "Date: %s\r\n"
+                    "Content-Length: %ld\r\n"
+                    "Content-Range: bytes " FORMAT_INT64 \
+                    "-" FORMAT_INT64 "/" FORMAT_INT64 "\r\n"
+                    "Content-Type: %s\r\n\r\n",
+                    currenttime,
+                    new_content_len,
+                    rangenumber,
+                    rangenumber+new_content_len,
+                    client->content_length,
+                    fserve_content_type(path));
+            }
+            else
+                break;
+        }
+        else {
+            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);
+
+        thread_mutex_lock (&pending_lock);
+        client->next = (fserve_t *)pending_list;
+        pending_list = client;
+        thread_mutex_unlock (&pending_lock);
+
+        return 0;
+    } while (0);
+    /* If we run into any issues with the ranges
+       we fallback to a normal/non-range request */
+    httpclient->respcode = 416;
+    bytes = sock_write(httpclient->con->sock,
+            "HTTP/1.0 416 Request Range Not Satisfiable\r\n\r\n");
+    if(bytes > 0) httpclient->con->sent_bytes = bytes;
+    return -1;
 }
 
 static int _free_client(void *key)

Modified: icecast/branches/kh/icecast/src/fserve.h
===================================================================
--- icecast/branches/kh/icecast/src/fserve.h	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/fserve.h	2004-11-15 17:20:19 UTC (rev 8203)
@@ -21,6 +21,7 @@
 
     FILE *file;
     int ready;
+    int64_t content_length;
     struct _fserve_t *next;
 } fserve_t;
 

Modified: icecast/branches/kh/icecast/src/slave.c
===================================================================
--- icecast/branches/kh/icecast/src/slave.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/slave.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -274,7 +274,7 @@
         free (auth_header);
         free (redirect_header);
         memset (header, 0, sizeof(header));
-        if (util_read_header (con->sock, header, 4096) == 0)
+        if (util_read_header (con->sock, header, 4096, READ_ENTIRE_HEADER) == 0)
         {
             WARN0("Header read failed");
             break;

Modified: icecast/branches/kh/icecast/src/source.c
===================================================================
--- icecast/branches/kh/icecast/src/source.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/source.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -257,6 +257,7 @@
     source->queue_size_limit = 0;
     source->listeners = 0;
     source->no_mount = 0;
+    source->shoutcast_compat = 0;
     source->max_listeners = -1;
     source->yp_public = 0;
     util_dict_free (source->audio_info);
@@ -1207,10 +1208,20 @@
         fclose (source->intro_file);
     if (mountinfo->intro_filename)
     {
-        source->intro_file = fopen (mountinfo->intro_filename, "rb");
-        if (source->intro_file == NULL)
-            WARN2 ("Cannot open intro file \"%s\": %s",
-                    mountinfo->intro_filename, strerror(errno));
+        ice_config_t *config = config_get_config_unlocked ();
+        unsigned int len  = strlen (config->webroot_dir) +
+            strlen (mountinfo->intro_filename) + 1;
+        char *path = malloc (len);
+        if (path)
+        {
+            snprintf (path, len, "%s%s", config->webroot_dir,
+                    mountinfo->intro_filename);
+
+            source->intro_file = fopen (path, "rb");
+            if (source->intro_file == NULL)
+                WARN2 ("Cannot open intro file \"%s\": %s", path, strerror(errno));
+            free (path);
+        }
     }
 
     if (mountinfo->queue_size_limit)

Modified: icecast/branches/kh/icecast/src/source.h
===================================================================
--- icecast/branches/kh/icecast/src/source.h	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/source.h	2004-11-15 17:20:19 UTC (rev 8203)
@@ -61,6 +61,7 @@
     int fallback_override;
     int fallback_when_full;
     int no_mount;
+    int shoutcast_compat;
 
     /* per source burst handling for connecting clients */
     unsigned int burst_size;    /* trigger level for burst on connect */

Modified: icecast/branches/kh/icecast/src/util.c
===================================================================
--- icecast/branches/kh/icecast/src/util.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/util.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -84,7 +84,7 @@
 #endif
 }
 
-int util_read_header(int sock, char *buff, unsigned long len)
+int util_read_header(int sock, char *buff, unsigned long len, int entire)
 {
     int read_bytes, ret;
     unsigned long pos;
@@ -107,10 +107,19 @@
 
             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;
+                if (entire) {
+                    if ((pos > 1) && (buff[pos - 1] == '\n' && 
+                                      buff[pos - 2] == '\n')) {
+                        ret = 1;
+                        break;
+                    }
                 }
+                else {
+                    if ((pos > 1) && (buff[pos - 1] == '\n')) {
+                        ret = 1;
+                        break;
+                    }
+                }
             }
         } else {
             break;

Modified: icecast/branches/kh/icecast/src/util.h
===================================================================
--- icecast/branches/kh/icecast/src/util.h	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/util.h	2004-11-15 17:20:19 UTC (rev 8203)
@@ -16,8 +16,11 @@
 #define XSLT_CONTENT 1
 #define HTML_CONTENT 2
 
+#define READ_ENTIRE_HEADER 1
+#define READ_LINE 0
+
 int util_timed_wait_for_fd(int fd, int timeout);
-int util_read_header(int sock, char *buff, unsigned long len);
+int util_read_header(int sock, char *buff, unsigned long len, int entire);
 int util_check_valid_extension(char *uri);
 char *util_get_extension(char *path);
 char *util_get_path_from_uri(char *uri);

Modified: icecast/branches/kh/icecast/src/yp.c
===================================================================
--- icecast/branches/kh/icecast/src/yp.c	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/src/yp.c	2004-11-15 17:20:19 UTC (rev 8203)
@@ -489,12 +489,22 @@
         if (url == NULL)
             break;
         config = config_get_config();
-        ret = snprintf (url, len, "http://%s:%d%s", config->hostname, config->port, source->mount);
+        if (source->format->type == FORMAT_TYPE_NSV) {
+            ret = snprintf (url, len, "http://%s:%d%s?stream.nsv", config->hostname, config->port, source->mount);
+        }
+        else {
+            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);
+            if (source->format->type == FORMAT_TYPE_NSV) {
+                snprintf (url, ret, "http://%s:%d%s?file=stream.nsv", config->hostname, config->port, source->mount);
+            }
+            else {
+                snprintf (url, ret, "http://%s:%d%s", config->hostname, config->port, source->mount);
+            }
         }
         config_release_config();
         yp->listen_url = util_url_escape (url);

Modified: icecast/branches/kh/icecast/web/auth.xsl
===================================================================
--- icecast/branches/kh/icecast/web/auth.xsl	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/web/auth.xsl	2004-11-15 17:20:19 UTC (rev 8203)
@@ -40,6 +40,7 @@
 <br></br>
 <br></br>
 </xsl:for-each>
+<xsl:text disable-output-escaping="yes">&amp;</xsl:text>nbsp;
 </div>
 <div class="roundbottom">
 <img src="corner_bottomleft.jpg" class="corner" style="display: none" />

Modified: icecast/branches/kh/icecast/web/status.xsl
===================================================================
--- icecast/branches/kh/icecast/web/status.xsl	2004-11-15 15:55:43 UTC (rev 8202)
+++ icecast/branches/kh/icecast/web/status.xsl	2004-11-15 17:20:19 UTC (rev 8203)
@@ -54,7 +54,14 @@
 <a href="auth.xsl">Click to Listen</a>
 </xsl:when>
 <xsl:otherwise>
-<a href="{@mount}.m3u">Click to Listen</a>
+   <xsl:choose>
+   <xsl:when test="content-type='video/nsv'">
+   <a href="{@mount}%3Ffile%3Dstream.nsv.m3u">Click to Listen</a>
+   </xsl:when>
+   <xsl:otherwise>
+   <a href="{@mount}.m3u">Click to Listen</a>
+   </xsl:otherwise>
+   </xsl:choose>
 </xsl:otherwise>
 </xsl:choose>
 </td></tr>
@@ -67,6 +74,7 @@
 <br></br>
 <br></br>
 </xsl:for-each>
+<xsl:text disable-output-escaping="yes">&amp;</xsl:text>nbsp;
 </div>
 <div class="roundbottom">
 <img src="corner_bottomleft.jpg" class="corner" style="display: none" />



More information about the commits mailing list