Logo Search packages:      
Sourcecode: icecast-server version File versions  Download package

client.c

/* client.c
 * - Client functions
 * Copyright (c) 1999 Jack Moffitt, Barath Raghavan, and Alexander Havšng
 *
 * 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.
 *
 */

/* Last overhauled 990420 <eel@musiknet.se> */

#ifdef HAVE_CONFIG_H
#ifdef _WIN32
#include <win32config.h>
#else
#include <config.h>
#endif
#endif

#include "definitions.h"
#include <stdio.h>
#include "definitions.h"

#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif

#include <stdlib.h>
#include <stdarg.h>
# ifndef __USE_BSD
#  define __USE_BSD
# endif
#include <string.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sys/types.h>
#include <ctype.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

#if defined (_WIN32)
#include <windows.h>
#define strncasecmp strnicmp
#else
#include <sys/socket.h> 
#include <sys/wait.h>
#include <netinet/in.h>
#endif

#include "avl.h"
#include "avl_functions.h"
#include "threads.h"
#include "icetypes.h"
#include "icecast.h"
#include "utility.h"
#include "ice_string.h"
#include "client.h"
#include "threads.h"
#include "connection.h"
#include "log.h"
#include "source.h"
#include "sock.h"
#include "restrict.h"
#include "static.h"
#include "memory.h"
#include "admin.h"
#include "http.h"
#include "vars.h"
#include "commands.h"
#include "authenticate/basic.h"
#include "match.h"
#include "pool.h"

#include <signal.h>

extern server_info_t info;

static void client_send_fake_file (connection_t *con);

/* Brand new client. Check what he wants, and either add him to
   the correct tree of clients (inside a source), or kill him off */
void client_login(connection_t *con, char *expr)
{
      char line[BUFSIZE];
      int go_on = 1;
      connection_t *source;
      request_t req;

      xa_debug(3, "Client login...\n");

      if (!con || !expr) {
            write_log(LOG_DEFAULT, "WARNING: client_login called with NULL pointer");
            return;
      }

      if (info.throttle_on) {
            sock_write(con->sock, "HTTP/1.0 406 Not Acceptable (using too much bandwidth)");
            kick_not_connected(con, "Bandwidth usage too high (throttling)");
            return;
      }

#ifdef HAVE_LIBWRAP
      if (!sock_check_libwrap (con->sock, client_e)) {
            write_http_code_page (con, 403, "Forbidden");
            kick_not_connected (con, "Access Denied (libwrap (client connection))");
            return;
      }
#endif
      if (!allowed(con, client_e)) {
            write_http_code_page (con, 403, "Forbidden");
            kick_not_connected (con, "Access Denied (internal acl list (client connection))");
            return;
      }

      zero_request(&req);
      
      con->headervars = create_header_vars ();

      do {
            if (splitc(line, expr, '\n') == NULL) {
                  strncpy(line, expr, BUFSIZE);
                  go_on = 0;
            }
      
            if (ice_strncmp(line, "GET", 3) == 0) {
                  build_request(line, &req);
            } else {
                        if (ice_strncmp(line, "Host:", 5) == 0 || (ice_strncmp(line, "HOST:", 5) == 0))
                                build_request(line, &req);
                  else
                        extract_header_vars (line, con->headervars);
            }
      } while (go_on);

      if (need_authentication (&req))
      {
            if (!authenticate_user_request (con, &req))
            {
                  write_401 (con, req.path);
                  kick_not_connected (con, "Not authorized");
                  return;
            }
      }
      
      if (ice_strcmp(req.path, "/list.cgi") == 0) {
            show_mountlist(con, &req);
            kick_not_connected(con, "Displayed mountlist");
            return;
      }
      
      if (ice_strncmp(req.path, "/playlist.pls", 13) == 0) {
            gen_playlist(con, &req);
            kick_not_connected (con, "Generated playlist");
            return;
      }

      if (ice_strncmp(req.path, "/admin", 6) == 0)
      {
            int err;
            
            char *secfile = get_icecast_file (info.mountfile, conf_file_e, R_OK);

            if (!secfile && strstr (req.path, "updinfo") == NULL) {
                  write_http_code_page (con, 403, "Forbidden");
                  write_log (LOG_DEFAULT, "No mountfile found, refusing access to WWW admin for %s", con_host (con));
                  kick_not_connected (con, "No mountfile found");
                  return;
            }
                  
            err = http_admin_command (con, &req);

            if (err)
            {
                  xa_debug (2, "DEBUG: kicking %s, executed admin command", con_host (con));
                  kick_not_connected (con, NULL);
            } else
                  kick_not_connected (con, "Failed to execute admin command");
            return;
      }

      if (ice_strncmp(req.path, "/file/", 6) == 0) {
            thread_rename("Static File Thread");
            if (req.path[ice_strlen (req.path) - 1] == '/')
            {
                  list_directory (con, req.path);
                  kick_not_connected (con, "Displayed directory list");
            } else {
                  send_file(con, &req);
                  kick_not_connected(con, "Sent static file");
            }
            return;
      }
      
      xa_debug (1, "Looking for mount [%s:%d%s]", req.host, req.port, req.path);
      
      /* Try to find a mount point with this name */
      thread_mutex_lock (&info.double_mutex);
      thread_mutex_lock (&info.mount_mutex);
      thread_mutex_lock (&info.source_mutex);

      source = find_mount_with_req (&req);
      
      thread_mutex_unlock (&info.source_mutex);
      thread_mutex_unlock (&info.mount_mutex);
      thread_mutex_unlock (&info.double_mutex);

      /* Ok, none found, give him the default mount */
      if (source == NULL && info.mount_fallback)
            source = get_default_mount();
        
      /* No LOG_DEFAULT, then no encoder, kick him out */
      if (source == NULL) {
            write_http_code_page (con, 404, "Stream not found");
            kick_not_connected (con, "No encoder");
            return;
      }

      if (need_authentication_on_mount (source->food.source->audiocast.mount))
      {
            if (!authenticate_user_request (con, &req))
            {
              write_401 (con, req.path);
              kick_not_connected (con, "Not authorized");
              return;
            }
      }

      if ((info.num_clients >= info.max_clients) 
          || (source->food.source->num_clients >= info.max_clients_per_source))
      {
            write_http_code_page (con, 504, "Server Full");
            if (info.num_clients >= info.max_clients)
                  xa_debug (2, "DEBUG: inc > imc: %lu %lu", info.num_clients, info.max_clients);
            else if (source->food.source->num_clients >= info.max_clients_per_source)
                  xa_debug (2, "DEBUG: snc > smc: %lu %lu", source->food.source->num_clients, info.max_clients_per_source);
            else 
                  xa_debug (1, "ERROR: Erroneous number of clients, what the hell is going on?");

            kick_not_connected (con, "Server Full (too many listeners)");
            return;
      }

      put_client(con);
      con->food.client->type = listener_e;

      {
            const char *umd = get_con_variable (con, "Icy-MetaData");
            if (umd)
                  con->food.client->use_icy_metadata = atoi (umd);
      }

      {
            const char *ref = get_con_variable (con, "Referer");
            if (ref && ice_strcmp (ref, "RELAY") == 0)
                  con->food.client->type = pulling_client_e;
      }
                  
      /* Change the sockaddr_in for the client to point to the port the client specified */
      if (con->sin)
      {
            const char *sport = get_con_variable (con, "x-audiocast-udpport");
            if (sport)
            {
                  con->sin->sin_port = htons (atoi (sport));
                  con->food.client->use_udp = 1;
                  xa_debug (1, "DEBUG: client_login(): Client listening on udp port %d", atoi (sport));
            }
      }

      if (con->food.client->use_icy_metadata && con->food.client->use_udp)
            con->food.client->use_icy_metadata = 0;
      
      util_increase_total_clients ();
      
      con->food.client->source = source->food.source;

      /* FIXME: Dangerous */
      source->food.source->stats.client_connections++;

      pool_add (con);

      write_log(LOG_DEFAULT, "Accepted client %d from [%s] on mountpoint [%s]. %d clients connected", con->id, 
              con_host (con), source->food.source->audiocast.mount, info.num_clients);
      greet_client(con, source->food.source);
}

client_t *
create_client()
{
      client_t *client = (client_t *)nmalloc(sizeof(client_t));
      client->type = unknown_client_e;
      return client;
}

void put_client(connection_t *con)
{
      client_t *cli = create_client();
      con->food.client = cli;
      cli->errors = 0;
      cli->type = unknown_client_e;
      cli->write_bytes = 0;
      cli->virgin = -1;
      cli->source = NULL;
      cli->cid = -1;
      cli->offset = 0;
      cli->alive = CLIENT_ALIVE;
      cli->use_icy_metadata = 0;
      cli->metadataoffset = 0;
      cli->metadatalen = 0;
      cli->metadatawritten = 0;
      cli->use_icy = 0;
      cli->use_udp = 0;
      con->type = client_e;
      cli->udpseqnr = 0;
}

void 
gen_playlist (connection_t *con, request_t *req)
{
      const char *mount_point;
      vartree_t *req_vars = avl_create(compare_vars, &info);
      
      extract_vars(req_vars, req->path);
      mount_point = get_variable(req_vars, "mount");

      xa_debug (1, "DEBUG: Generating playlist");

      write_http_header(con->sock, 200, "OK");

      sock_write_line (con->sock, "Connection: close");
      sock_write_line (con->sock, "Content-Type: audio/x-scpls\r\n");


      sock_write_line (con->sock, "[playlist]\r\n");
      sock_write_line (con->sock, "NumberOfEntries=1");
      sock_write_line (con->sock, "File1=http://%s:%i%s", info.server_name, info.port[0], mount_point ? mount_point : "/");

      free_variables(req_vars);
}

void 
show_mountlist(connection_t *con, request_t *req)
{
  char *filename = get_template ("mountlist.html");
  avl_traverser trav = {0};
  connection_t *sourcecon;
  int i = 0;
  
  xa_debug (1, "DEBUG: Displaying mountlist [%s]", filename ? filename : "internal");

  if (filename)
    {
      write_template_parsed_html_page (con, NULL, filename, -1, NULL);
      nfree (filename);
      return;
    }
  
  thread_mutex_lock(&info.source_mutex);
  
  write_http_header(con->sock, 200, "OK");
  
  sock_write_line (con->sock, "Connection: close");
  sock_write_line (con->sock, "Content-Type: text/html\r\n");
  
  sock_write_line (con->sock, "<html><head><title>icecast server, version %s, running on %s</title></head>", VERSION, info.server_name);
  sock_write_line (con->sock, "<body bgcolor=black text=white link=lightblue alink=lightblue vlink=lightblue><h3><tt>icecast server, version %s, running on %s</tt></h3><br>", VERSION, info.server_name);

      for (i = 0; i < MAXLISTEN; i++) 
            if (info.port[i] > 0) 
                  sock_write_line (con->sock, "<p>Listening on port %d<br>", info.port[i]);
  
  if (info.location)
    sock_write_line (con->sock, "Server location: %s<br>", info.location);
  if (info.rp_email)
    sock_write_line (con->sock, "Server admin: <a href=\"mailto:%s\">%s</a><br>", info.rp_email, info.rp_email);
  if (info.server_url)
    sock_write_line (con->sock, "Server URL: <a href=\"%s\">%s</a><br></p>", info.server_url, info.server_url);
  
  sock_write_line (con->sock, "<table border=0 cellspacing=0 cellpadding=3><tr><td><font face=\"sans-serif\" size=+2>Stream Link</font></td><td><font face=\"sans-serif\" size=+2>Name</font></td><td><font face=\"sans-serif\" size=+2>URL</font></td><td><font face=\"sans-serif\" size=+2>Genre</font></td><td><font face=\"sans-serif\" size=+2>Description</font></td></tr>");
  
  while ((sourcecon = avl_traverse(info.sources, &trav))) {
    
    i++;
    i % 2 ? sock_write_line (con->sock, "<tr bgcolor=#000000>") : sock_write_line (con->sock, "<tr bgcolor=#333333>");
    
    sock_write_line (con->sock, "<td><tt><a href=\"/playlist.pls?mount=%s&file=dummy.pls\">%s</a></tt></td><td><tt>%s</tt></td><td><tt><a href=\"%s\">%s</a></tt></td><td><tt>%s</tt></td><td><tt>%s</tt></td></tr>",
                 sourcecon->food.source->audiocast.mount, 
                 sourcecon->food.source->audiocast.mount, 
                 sourcecon->food.source->audiocast.name, 
                 sourcecon->food.source->audiocast.url,
                 sourcecon->food.source->audiocast.url,
                 sourcecon->food.source->audiocast.genre, 
                 sourcecon->food.source->audiocast.description);
  }
  
  sock_write_line (con->sock, "</table></body></html>");
  thread_mutex_unlock(&info.source_mutex);
}

void util_increase_total_clients ()
{
      internal_lock_mutex (&info.misc_mutex);
      info.num_clients++;
      info.hourly_stats.client_connections++;
      internal_unlock_mutex (&info.misc_mutex);
}

void
util_decrease_total_clients ()
{
      internal_lock_mutex (&info.misc_mutex);
      info.num_clients--;
      internal_unlock_mutex (&info.misc_mutex);
}

void 
del_client(connection_t *client, source_t *source)
{
      if (!client || !source) {
            write_log(LOG_DEFAULT, "WARNING: del_client() called with NULL pointer");
            return;
      }

      if (source && client->food.client && (client->food.client->virgin != 1) && (client->food.client->virgin != -1)) {
            if (source->num_clients == 0)
                  write_log (LOG_DEFAULT, "WARNING: Bloody going below limits on client count!");
            else
                  source->num_clients--;
      }
      util_decrease_total_clients ();
}

int
client_wants_udp_info (connection_t *con)
{
      const char *val;

      if (!con)
            return 0;
      
      val = get_con_variable (con, "x-audiocast-udpport");
      if (!val)
            return 0;
      
      return 1;
}

int 
client_wants_content_length(connection_t *con)
{
      const char *val;

      if (!con)
            return 0;

      val = get_user_agent (con);
      if (!val)
            return 0;

      if (strstr(val, "MSIE"))
            return 1;
      if (strstr(val, "RMA/1.0"))
            return 1; 
      if (strstr(val, "NSPlayer"))
            return 1;
      return 0;
}

int 
client_wants_icy_headers (connection_t *con)
{
      const char *val;

      if (!con)
            return 1;

      val = get_user_agent (con);
      if (!val || !val[0] || strcmp (val, "(null)") == 0)
            return 1;

      if (con->food.client->use_icy)
            return 1;
      if (strncasecmp (val, "winamp", 6) == 0)
            return 1;
      if (strncasecmp (val, "Shoutcast", 9) == 0)
            return 1;

      return 0;
}

int
client_wants_metadata (connection_t *con)
{
      if (!con)
            return 0;
      if (con->food.client->use_icy_metadata && info.use_meta_data)
            return 1;
      return 0;
}

void
print_relay_ids (connection_t *con, source_t *source)
{
      avl_traverser trav = {0};
      relay_id_t *rid;

      thread_mutex_lock (&source->mutex);

      while ((rid = avl_traverse (source->relay_tree, &trav)))
      {
            if (!rid || !rid->host)
            {
                  write_log (LOG_DEFAULT, "ERROR: Erroneous relay id:s...");
                  continue;
            }
            xa_debug (1, "DEBUG: Displaying dir-id: %s:%d to %d", rid->host, rid->id, con->id);
            sock_write_line (con->sock, "x-audiocast-directory: %s:%d", rid->host, rid->id);
      }

      thread_mutex_unlock (&source->mutex);
}


int
client_wants_relay_ids (connection_t *con)
{
      return 0; /* Currently disabled awaiting new interface */
}

void 
greet_client(connection_t *con, source_t *source)
{
#ifdef _WIN32
      int bufsize = 16384; /* Is that a buffer in your pocket, or are you just happy to see me? :) */
#endif

      if (!con) {
            write_log(LOG_DEFAULT, "WARNING: greet_client called with NULL pointer");
            return;
      }

      con->food.client->udpseqnr = source->info.udpseqnr > 0 ? source->info.udpseqnr - 1 : source->info.udpseqnr;

      if (client_wants_icy_headers (con)) {
              xa_debug (2, "DEBUG: client [%s] wants icy headers", con_host (con));
              sock_write_line (con->sock, "ICY 200 OK");
              sock_write_line (con->sock, 
              "icy-notice1:<BR>This stream requires <a href=\"http://www.winamp.com/\">Winamp</a><BR>");
              sock_write_line (con->sock, "icy-notice2:SHOUTcast Distributed Network Audio Server/posix v1.6.0rc2<BR>");
              
              if (client_wants_metadata (con))
              sock_write_line (con->sock, "icy-metaint:%u", info.metainterval);
              
              if (client_wants_udp_info (con))
              sock_write_line (con->sock, "x-audiocast-udpport: %d", info.port[0]);
              
              sock_write (con->sock, "icy-name:%s\r\nicy-genre:%s\r\nicy-url:%s\r\nicy-pub:%d\nicy-br:%d\r\n\r\n",
              source->audiocast.name, source->audiocast.genre, source->audiocast.url, source->audiocast.public,
              source->audiocast.bitrate); 
      } else {
        xa_debug (2, "DEBUG: client [%s] wants xaudiocast headers", con_host (con));
        
        write_http_header (con->sock, 200, "OK");
        if (source->audiocast.streammimetype)
              sock_write_line (con->sock, "Content-Type: %s", source->audiocast.streammimetype);
        else
              sock_write_line (con->sock, "Content-Type: audio/mpeg");

        if (client_wants_content_length (con))
          sock_write (con->sock, "Cache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\nContent-Length: 54000000\r\n");
        
        if (info.location)
          sock_write_line (con->sock, "x-audiocast-location: %s", info.location);
            
        if (info.rp_email)
          sock_write_line (con->sock, "x-audiocast-admin: %s", info.rp_email);
            
        if (info.server_url)
          sock_write_line (con->sock, "x-audiocast-server-url: %s", info.server_url);
        
        if (client_wants_relay_ids (con))
          print_relay_ids (con, source);
        
        if (client_wants_metadata (con))
              sock_write_line (con->sock, "icy-metaint:%u", info.metainterval);
        
        if (client_wants_udp_info (con))
              sock_write_line (con->sock, "x-audiocast-udpport: %d", info.port[0]);
        
        sock_write (con->sock, "x-audiocast-mount:%s\r\nx-audiocast-name:%s\r\nx-audiocast-description:%s\r\nx-audiocast-url:%s\r\nx-audiocast-genre:%s\r\nx-audiocast-bitrate:%d\r\nx-audiocast-public:%d\r\n\r\n", nullcheck_string (source->audiocast.mount), nullcheck_string (source->audiocast.name), nullcheck_string (source->audiocast.description), nullcheck_string (source->audiocast.url), nullcheck_string (source->audiocast.genre), source->audiocast.bitrate, source->audiocast.public);
      }
      
      sock_set_blocking(con->sock, SOCK_NONBLOCK);
      
#ifdef _WIN32
      setsockopt(con->sock, SOL_SOCKET, SO_SNDBUF, (char *)&bufsize, sizeof(int));
#endif
      
      con->food.client->virgin = 1;

      if (con->food.client->type == pulling_client_e) {
        char* title = NULL;
        char* msg = NULL;
        char length[11];
        char* url = NULL;

        url_encode (source->info.streamtitle, &title);
        url_encode (source->info.streammsg, &msg);
        snprintf (length, sizeof (length), "%ld", source->info.streamlength);
        url_encode (source->info.streamurl, &url);
        update_metadata_on_relay (con, source->audiocast.mount, title, msg, length, url);
        nfree (title);
        nfree (msg);
        nfree (url);
      }
}

void
describe_client (const com_request_t *req, const connection_t *clicon)
{
      const client_t *client;

      if (!req || !clicon)
      {
            xa_debug (1, "WARNING: describe_client(): called with NULL pointers");
            return;
      }

      if (clicon->type != client_e)
      {
            xa_debug (1, "WARNING: describe_client(): called with invalid type");
            return;
      }

      describe_connection (req, clicon);
      
      client = clicon->food.client;

      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_START, "Misc client info:");
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "UDPinfo: %s", client->use_udp ? "yes" : "no");
      if (client->use_udp)
            admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "UDP client port: %d", htons (clicon->sin->sin_port));
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "UDP sequence number: %d", client->udpseqnr);
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "ICY metadata: %s", client->use_icy_metadata ? "yes" : "no");
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "Transfer error balance: %d", client_errors (client));
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "Transfer chunk id and offset: %d : %d", client->cid, client->offset);
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "Bytes transfered: %lu", client->write_bytes);
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "Virgin: %s", client->virgin ? "yes" : "no");
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "Client type: %s", client_type (clicon));
      if (client->source && client->source->audiocast.mount)
            admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_MISC, "Mountpoint: %s", client->source->audiocast.mount);
      admin_write_line (req, ADMIN_SHOW_DESCRIBE_CLIENT_END, "End of client info");
}

const char client_types[4][16] = { "listener", "pusher", "puller", "unknown listener" };

const char *
client_type (const connection_t *clicon)
{
      switch (clicon->food.client->type)
      {
            case listener_e:
                  return client_types[0];
                  break;
            case pusher_e:
                  return client_types[1];
                  break;
            case pulling_client_e:
                  return client_types[2];
                  break;
            default:
                  return client_types[3];
                  break;
      }
}

int
client_errors (const client_t *client)
{
      if (!client || !client->source)
            return 0;
      
      return (CHUNKLEN - (client->cid - client->source->cid)) % CHUNKLEN;
}

Generated by  Doxygen 1.6.0   Back to index