diff options
| author | Yang Tse <yangsita@gmail.com> | 2010-02-01 12:05:08 +0000 | 
|---|---|---|
| committer | Yang Tse <yangsita@gmail.com> | 2010-02-01 12:05:08 +0000 | 
| commit | 35fbeda0032860929292896f73ab37a274cb5fa1 (patch) | |
| tree | 16495b58a5e3768697b9d43e79cb5dc63537cb45 | |
| parent | 715e3a806f333320f3d8a1172d7f000db59edcf6 (diff) | |
Test suite support for RTSP
| -rw-r--r-- | tests/FILEFORMAT | 8 | ||||
| -rw-r--r-- | tests/Makefile.am | 2 | ||||
| -rw-r--r-- | tests/data/DISABLED | 5 | ||||
| -rwxr-xr-x | tests/rtspserver.pl | 109 | ||||
| -rwxr-xr-x | tests/runtests.pl | 187 | ||||
| -rw-r--r-- | tests/server/Makefile.inc | 5 | ||||
| -rw-r--r-- | tests/server/rtspd.c | 1410 | ||||
| -rw-r--r-- | tests/serverhelp.pm | 4 | 
8 files changed, 1720 insertions, 10 deletions
| diff --git a/tests/FILEFORMAT b/tests/FILEFORMAT index a8893abe4..1ff8a27b3 100644 --- a/tests/FILEFORMAT +++ b/tests/FILEFORMAT @@ -128,6 +128,10 @@ pipe: [num]     tell the server to expect this many HTTP requests before                  sending back anything, to allow pipelining tests  skip: [num]     instructs the server to ignore reading this many bytes from a PUT                  or POST request + +rtp: part [num] channel [num] size [num] +               stream a fake RTP packet for the given part on a chosen channel +               with the given payload size  </servercmd>  </reply> @@ -148,6 +152,8 @@ scp  sftp  socks4  socks5 +rtsp +rtsp-ipv6  Give only one per line.  This subsection is mandatory.  </server> @@ -262,6 +268,8 @@ Available substitute variables include:  %TFTP6PORT - IPv6 port number of the TFTP server  %SSHPORT   - Port number of the SCP/SFTP server  %SOCKSPORT - Port number of the SOCKS4/5 server +%RTSPPORT  - Port number of the RTSP server +%RTSP6PORT - IPv6 port number of the RTSP server  %SRCDIR    - Full path to the source dir  %PWD       - Current directory  %CURL      - Path to the curl executable diff --git a/tests/Makefile.am b/tests/Makefile.am index 1c38aa544..16212ec14 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -28,7 +28,7 @@ EXTRA_DIST = ftpserver.pl httpserver.pl secureserver.pl runtests.pl getpart.pm \   FILEFORMAT README stunnel.pem memanalyze.pl testcurl.pl valgrind.pm ftp.pm   \   sshserver.pl sshhelp.pm testcurl.1 runtests.1 $(HTMLPAGES) $(PDFPAGES) \   CMakeLists.txt certs/scripts/*.sh certs/Server* certs/EdelCurlRoot* \ - serverhelp.pm tftpserver.pl + serverhelp.pm tftpserver.pl rtspserver.pl  SUBDIRS = data server libtest diff --git a/tests/data/DISABLED b/tests/data/DISABLED index 2635590ce..c65cdd90b 100644 --- a/tests/data/DISABLED +++ b/tests/data/DISABLED @@ -7,8 +7,3 @@  564  802  803 -567 -568 -569 -570 -571 diff --git a/tests/rtspserver.pl b/tests/rtspserver.pl new file mode 100755 index 000000000..2bdc54a76 --- /dev/null +++ b/tests/rtspserver.pl @@ -0,0 +1,109 @@ +#!/usr/bin/env perl +#*************************************************************************** +#                                  _   _ ____  _ +#  Project                     ___| | | |  _ \| | +#                             / __| | | | |_) | | +#                            | (__| |_| |  _ <| |___ +#                             \___|\___/|_| \_\_____| +# +# Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://curl.haxx.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# $Id$ +#*************************************************************************** + +BEGIN { +    @INC=(@INC, $ENV{'srcdir'}, '.'); +} + +use strict; +use warnings; + +use serverhelp qw( +    server_pidfilename +    server_logfilename +    ); + +my $verbose = 0;     # set to 1 for debugging +my $port = 8990;     # just a default +my $ipvnum = 4;      # default IP version of rtsp server +my $idnum = 1;       # dafault rtsp server instance number +my $proto = 'rtsp';  # protocol the rtsp server speaks +my $pidfile;         # rtsp server pid file +my $logfile;         # rtsp server log file +my $srcdir; + +my $flags  = ""; +my $path   = '.'; +my $logdir = $path .'/log'; + +while(@ARGV) { +    if($ARGV[0] eq '--pidfile') { +        if($ARGV[1]) { +            $pidfile = $ARGV[1]; +            shift @ARGV; +        } +    } +    elsif($ARGV[0] eq '--logfile') { +        if($ARGV[1]) { +            $logfile = $ARGV[1]; +            shift @ARGV; +        } +    } +    elsif($ARGV[0] eq '--srcdir') { +        if($ARGV[1]) { +            $srcdir = $ARGV[1]; +            shift @ARGV; +        } +    } +    elsif($ARGV[0] eq '--ipv4') { +        $ipvnum = 4; +    } +    elsif($ARGV[0] eq '--ipv6') { +        $ipvnum = 6; +    } +    elsif($ARGV[0] eq '--port') { +        if($ARGV[1] =~ /^(\d+)$/) { +            $port = $1; +            shift @ARGV; +        } +    } +    elsif($ARGV[0] eq '--id') { +        if($ARGV[1] =~ /^(\d+)$/) { +            $idnum = $1 if($1 > 0); +            shift @ARGV; +        } +    } +    elsif($ARGV[0] eq '--verbose') { +        $verbose = 1; +    } +    else { +        print STDERR "\nWarning: rtspserver.pl unknown parameter: $ARGV[0]\n"; +    } +    shift @ARGV; +} + +if(!$srcdir) { +    $srcdir = $ENV{'srcdir'} || '.'; +} +if(!$pidfile) { +    $pidfile = "$path/". server_pidfilename($proto, $ipvnum, $idnum); +} +if(!$logfile) { +    $logfile = server_logfilename($logdir, $proto, $ipvnum, $idnum); +} + +$flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" "; +$flags .= "--ipv$ipvnum --port $port --srcdir \"$srcdir\""; + +exec("server/rtspd $flags"); diff --git a/tests/runtests.pl b/tests/runtests.pl index 428e5131f..227e66d8a 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -132,6 +132,8 @@ my $IMAPPORT; # IMAP  my $IMAP6PORT; # IMAP IPv6 server port  my $SMTPPORT; # SMTP  my $SMTP6PORT; # SMTP IPv6 server port +my $RTSPPORT; # RTSP +my $RTSP6PORT; # RTSP IPv6 server port  my $srcdir = $ENV{'srcdir'} || '.';  my $CURL="../src/curl".exe_ext(); # what curl executable to run on the tests @@ -323,7 +325,7 @@ sub init_serverpidfile_hash {        }      }    } -  for my $proto (('tftp', 'sftp', 'socks', 'ssh')) { +  for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp')) {      for my $ipvnum ((4, 6)) {        for my $idnum ((1, 2)) {          my $serv = servername_id($proto, $ipvnum, $idnum); @@ -803,6 +805,79 @@ sub verifyftp {  }  ####################################################################### +# Verify that the server that runs on $ip, $port is our server.  This also +# implies that we can speak with it, as there might be occasions when the +# server runs fine but we cannot talk to it ("Failed to connect to ::1: Can't +# assign requested address" # + +sub verifyrtsp { +    my ($proto, $ipvnum, $idnum, $ip, $port) = @_; +    my $server = servername_id($proto, $ipvnum, $idnum); +    my $pid = 0; + +    my $verifyout = "$LOGDIR/". +        servername_canon($proto, $ipvnum, $idnum) .'_verify.out'; +    unlink($verifyout) if(-f $verifyout); + +    my $verifylog = "$LOGDIR/". +        servername_canon($proto, $ipvnum, $idnum) .'_verify.log'; +    unlink($verifylog) if(-f $verifylog); + +    my $flags = "--max-time $server_response_maxtime "; +    $flags .= "--output $verifyout "; +    $flags .= "--silent "; +    $flags .= "--verbose "; +    $flags .= "--globoff "; +    # currently verification is done using http +    $flags .= "\"http://$ip:$port/verifiedserver\""; + +    my $cmd = "$VCURL $flags 2>$verifylog"; + +    # verify if our/any server is running on this port +    logmsg "RUN: $cmd\n" if($verbose); +    my $res = runclient($cmd); + +    $res >>= 8; # rotate the result +    if($res & 128) { +        logmsg "RUN: curl command died with a coredump\n"; +        return -1; +    } + +    if($res && $verbose) { +        logmsg "RUN: curl command returned $res\n"; +        if(open(FILE, "<$verifylog")) { +            while(my $string = <FILE>) { +                logmsg "RUN: $string" if($string !~ /^([ \t]*)$/); +            } +            close(FILE); +        } +    } + +    my $data; +    if(open(FILE, "<$verifyout")) { +        while(my $string = <FILE>) { +            $data = $string; +            last; # only want first line +        } +        close(FILE); +    } + +    if($data && ($data =~ /RTSP_SERVER WE ROOLZ: (\d+)/)) { +        $pid = 0+$1; +    } +    elsif($res == 6) { +        # curl: (6) Couldn't resolve host '::1' +        logmsg "RUN: failed to resolve host ($proto://$ip:$port/verifiedserver)\n"; +        return -1; +    } +    elsif($data || ($res != 7)) { +        logmsg "RUN: Unknown server on our $server port: $port\n"; +        return -1; +    } +    return $pid; +} + +#######################################################################  # Verify that the ssh server has written out its pidfile, recovering  # the pid from the file and returning it if a process with that pid is  # actually alive. @@ -901,6 +976,7 @@ sub verifysocks {  my %protofunc = ('http' => \&verifyhttp,                   'https' => \&verifyhttp, +                 'rtsp' => \&verifyrtsp,                   'ftp' => \&verifyftp,                   'pop3' => \&verifyftp,                   'imap' => \&verifyftp, @@ -1359,6 +1435,87 @@ sub runtftpserver {  ####################################################################### +# start the rtsp server +# +sub runrtspserver { +    my ($verbose, $ipv6) = @_; +    my $port = $RTSPPORT; +    my $ip = $HOSTIP; +    my $proto = 'rtsp'; +    my $ipvnum = 4; +    my $idnum = 1; +    my $server; +    my $srvrname; +    my $pidfile; +    my $logfile; +    my $flags = ""; + +    if($ipv6) { +        # if IPv6, use a different setup +        $ipvnum = 6; +        $port = $RTSP6PORT; +        $ip = $HOST6IP; +    } + +    $server = servername_id($proto, $ipvnum, $idnum); + +    $pidfile = $serverpidfile{$server}; + +    # don't retry if the server doesn't work +    if ($doesntrun{$pidfile}) { +        return (0,0); +    } + +    my $pid = processexists($pidfile); +    if($pid > 0) { +        stopserver($server, "$pid"); +    } +    unlink($pidfile) if(-f $pidfile); + +    $srvrname = servername_str($proto, $ipvnum, $idnum); + +    $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum); + +    $flags .= "--verbose " if($debugprotocol); +    $flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" "; +    $flags .= "--id $idnum " if($idnum > 1); +    $flags .= "--ipv$ipvnum --port $port --srcdir \"$srcdir\""; + +    my $cmd = "$perl $srcdir/rtspserver.pl $flags"; +    my ($rtsppid, $pid2) = startnew($cmd, $pidfile, 15, 0); + +    if($rtsppid <= 0 || !kill(0, $rtsppid)) { +        # it is NOT alive +        logmsg "RUN: failed to start the $srvrname server\n"; +        stopserver($server, "$pid2"); +        displaylogs($testnumcheck); +        $doesntrun{$pidfile} = 1; +        return (0,0); +    } + +    # Server is up. Verify that we can speak to it. +    my $pid3 = verifyserver($proto, $ipvnum, $idnum, $ip, $port); +    if(!$pid3) { +        logmsg "RUN: $srvrname server failed verification\n"; +        # failed to talk to it properly. Kill the server and return failure +        stopserver($server, "$rtsppid $pid2"); +        displaylogs($testnumcheck); +        $doesntrun{$pidfile} = 1; +        return (0,0); +    } +    $pid2 = $pid3; + +    if($verbose) { +        logmsg "RUN: $srvrname server is now running PID $rtsppid\n"; +    } + +    sleep(1); + +    return ($rtsppid, $pid2); +} + + +#######################################################################  # Start the ssh (scp/sftp) server  #  sub runsshserver { @@ -1912,6 +2069,7 @@ sub checksystem {      logmsg sprintf("*   HTTP/%d ", $HTTPPORT);      logmsg sprintf("FTP/%d ", $FTPPORT);      logmsg sprintf("FTP2/%d ", $FTP2PORT); +    logmsg sprintf("RTSP/%d ", $RTSPPORT);      if($stunnel) {          logmsg sprintf("FTPS/%d ", $FTPSPORT);          logmsg sprintf("HTTPS/%d ", $HTTPSPORT); @@ -1919,6 +2077,7 @@ sub checksystem {      logmsg sprintf("\n*   TFTP/%d ", $TFTPPORT);      if($http_ipv6) {          logmsg sprintf("HTTP-IPv6/%d ", $HTTP6PORT); +        logmsg sprintf("RTSP-IPv6/%d ", $RTSP6PORT);      }      if($ftp_ipv6) {          logmsg sprintf("FTP-IPv6/%d ", $FTP6PORT); @@ -1973,6 +2132,8 @@ sub subVariables {    $$thing =~ s/%USER/$USER/g;    $$thing =~ s/%CLIENTIP/$CLIENTIP/g;    $$thing =~ s/%CLIENT6IP/$CLIENT6IP/g; +  $$thing =~ s/%RTSPPORT/$RTSPPORT/g; +  $$thing =~ s/%RTSP6PORT/$RTSP6PORT/g;    # The purpose of FTPTIME2 and FTPTIME3 is to provide times that can be    # used for time-out tests and that whould work on most hosts as these @@ -3053,6 +3214,28 @@ sub startservers {                  $run{'http-ipv6'}="$pid $pid2";              }          } +        elsif($what eq "rtsp") { +            if(!$run{'rtsp'}) { +                ($pid, $pid2) = runrtspserver($verbose); +                if($pid <= 0) { +                    return "failed starting RTSP server"; +                } +                printf ("* pid rtsp => %d %d\n", $pid, $pid2) if($verbose); +                $run{'rtsp'}="$pid $pid2"; +            } +        } +        elsif($what eq "rtsp-ipv6") { +            if(!$run{'rtsp-ipv6'}) { +                ($pid, $pid2) = runrtspserver($verbose, "IPv6"); +                if($pid <= 0) { +                    return "failed starting RTSP-IPv6 server"; +                } +                logmsg sprintf("* pid rtsp-ipv6 => %d %d\n", $pid, $pid2) +                    if($verbose); +                $run{'rtsp-ipv6'}="$pid $pid2"; +            } +        } +          elsif($what eq "ftps") {              if(!$stunnel) {                  # we can't run ftps tests without stunnel @@ -3587,6 +3770,8 @@ $IMAPPORT =  $base++;  $IMAP6PORT = $base++;  $SMTPPORT =  $base++;  $SMTP6PORT = $base++; +$RTSPPORT =  $base++; +$RTSP6PORT = $base++;  #######################################################################  # clear and create logging directory: diff --git a/tests/server/Makefile.inc b/tests/server/Makefile.inc index b4394f9e8..4e8fc3658 100644 --- a/tests/server/Makefile.inc +++ b/tests/server/Makefile.inc @@ -1,4 +1,4 @@ -noinst_PROGRAMS = sws getpart sockfilt resolve tftpd +noinst_PROGRAMS = sws getpart sockfilt resolve tftpd rtspd  useful = getpart.c getpart.h $(top_srcdir)/lib/strequal.c	\   $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c		\ @@ -20,3 +20,6 @@ getpart_LDADD = @TEST_SERVER_LIBS@  tftpd_SOURCES = tftpd.c util.c util.h $(useful) tftp.h  tftpd_LDADD = @TEST_SERVER_LIBS@ + +rtspd_SOURCES = rtspd.c util.c util.h $(useful) +rtspd_LDADD = @TEST_SERVER_LIBS@ diff --git a/tests/server/rtspd.c b/tests/server/rtspd.c new file mode 100644 index 000000000..273fddb61 --- /dev/null +++ b/tests/server/rtspd.c @@ -0,0 +1,1410 @@ +/*************************************************************************** + *                                  _   _ ____  _ + *  Project                     ___| | | |  _ \| | + *                             / __| | | | |_) | | + *                            | (__| |_| |  _ <| |___ + *                             \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +/* + * curl's test suite Real Time Streaming Protocol (RTSP) server. + * + * This source file was started based on curl's HTTP test suite server. + */ + +#include "setup.h" /* portability help from the lib directory */ + +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_NETINET_TCP_H +#include <netinet/tcp.h> /* for TCP_NODELAY */ +#endif + +#define ENABLE_CURLX_PRINTF +/* make the curlx header define all printf() functions to use the curlx_* +   versions instead */ +#include "curlx.h" /* from the private lib dir */ +#include "getpart.h" +#include "util.h" + +/* include memdebug.h last */ +#include "memdebug.h" + +#ifdef ENABLE_IPV6 +static bool use_ipv6 = FALSE; +#endif +static const char *ipv_inuse = "IPv4"; +static int serverlogslocked = 0; + +#define REQBUFSIZ 150000 +#define REQBUFSIZ_TXT "149999" + +static long prevtestno=-1;    /* previous test number we served */ +static long prevpartno=-1;    /* previous part number we served */ +static bool prevbounce=FALSE; /* instructs the server to increase the part +                                 number for a test in case the identical +                                 testno+partno request shows up again */ + +#define RCMD_NORMALREQ 0 /* default request, use the tests file normally */ +#define RCMD_IDLE      1 /* told to sit idle */ +#define RCMD_STREAM    2 /* told to stream */ + +typedef enum { +  RPROT_NONE = 0, +  RPROT_RTSP = 1, +  RPROT_HTTP = 2 +} reqprot_t; + +struct httprequest { +  char reqbuf[REQBUFSIZ]; /* buffer area for the incoming request */ +  size_t checkindex; /* where to start checking of the request */ +  size_t offset;     /* size of the incoming request */ +  long testno;       /* test number found in the request */ +  long partno;       /* part number found in the request */ +  bool open;      /* keep connection open info, as found in the request */ +  bool auth_req;  /* authentication required, don't wait for body unless +                     there's an Authorization header */ +  bool auth;      /* Authorization header present in the incoming request */ +  size_t cl;      /* Content-Length of the incoming request */ +  bool digest;    /* Authorization digest header found */ +  bool ntlm;      /* Authorization ntlm header found */ +  int pipe;       /* if non-zero, expect this many requests to do a "piped" +                     request/response */ +  int skip;       /* if non-zero, the server is instructed to not read this +                     many bytes from a PUT/POST request. Ie the client sends N +                     bytes said in Content-Length, but the server only reads N +                     - skip bytes. */ +  int rcmd;       /* doing a special command, see defines above */ +  reqprot_t protocol; /* request protocol, HTTP or RTSP */ +  int prot_version;   /* HTTP or RTSP version (major*10 + minor) */ +  bool pipelining;    /* true if request is pipelined */ +  char *rtp_buffer; +  size_t rtp_buffersize; +}; + +static int ProcessRequest(struct httprequest *req); +static void storerequest(char *reqbuf, size_t totalsize); + +#define DEFAULT_PORT 8999 + +#ifndef DEFAULT_LOGFILE +#define DEFAULT_LOGFILE "log/rtspd.log" +#endif + +const char *serverlogfile = DEFAULT_LOGFILE; + +#define RTSPDVERSION "cURL test suite RTSP server/0.1" + +#define REQUEST_DUMP  "log/server.input" +#define RESPONSE_DUMP "log/server.response" + +/* very-big-path support */ +#define MAXDOCNAMELEN 140000 +#define MAXDOCNAMELEN_TXT "139999" + +#define REQUEST_KEYWORD_SIZE 256 +#define REQUEST_KEYWORD_SIZE_TXT "255" + +#define CMD_AUTH_REQUIRED "auth_required" + +/* 'idle' means that it will accept the request fine but never respond +   any data. Just keep the connection alive. */ +#define CMD_IDLE "idle" + +/* 'stream' means to send a never-ending stream of data */ +#define CMD_STREAM "stream" + +#define END_OF_HEADERS "\r\n\r\n" + +enum { +  DOCNUMBER_NOTHING = -7, +  DOCNUMBER_QUIT    = -6, +  DOCNUMBER_BADCONNECT = -5, +  DOCNUMBER_INTERNAL= -4, +  DOCNUMBER_CONNECT = -3, +  DOCNUMBER_WERULEZ = -2, +  DOCNUMBER_404     = -1 +}; + + +/* sent as reply to a QUIT */ +static const char *docquit = +"HTTP/1.1 200 Goodbye" END_OF_HEADERS; + +/* sent as reply to a CONNECT */ +static const char *docconnect = +"HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS; + +/* sent as reply to a "bad" CONNECT */ +static const char *docbadconnect = +"HTTP/1.1 501 Forbidden you fool" END_OF_HEADERS; + +/* send back this on HTTP 404 file not found */ +static const char *doc404_HTTP = "HTTP/1.1 404 Not Found\r\n" +    "Server: " RTSPDVERSION "\r\n" +    "Connection: close\r\n" +    "Content-Type: text/html" +    END_OF_HEADERS +    "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" +    "<HTML><HEAD>\n" +    "<TITLE>404 Not Found</TITLE>\n" +    "</HEAD><BODY>\n" +    "<H1>Not Found</H1>\n" +    "The requested URL was not found on this server.\n" +    "<P><HR><ADDRESS>" RTSPDVERSION "</ADDRESS>\n" "</BODY></HTML>\n"; + +/* send back this on RTSP 404 file not found */ +static const char *doc404_RTSP = "RTSP/1.0 404 Not Found\r\n" +    "Server: " RTSPDVERSION "\r\n" +    END_OF_HEADERS; + +/* Default size to send away fake RTP data */ +#define RTP_DATA_SIZE 12 +static const char *RTP_DATA = "$_1234\n\0asdf"; + +/* do-nothing macro replacement for systems which lack siginterrupt() */ + +#ifndef HAVE_SIGINTERRUPT +#define siginterrupt(x,y) do {} while(0) +#endif + +/* vars used to keep around previous signal handlers */ + +typedef RETSIGTYPE (*SIGHANDLER_T)(int); + +#ifdef SIGHUP +static SIGHANDLER_T old_sighup_handler  = SIG_ERR; +#endif + +#ifdef SIGPIPE +static SIGHANDLER_T old_sigpipe_handler = SIG_ERR; +#endif + +#ifdef SIGALRM +static SIGHANDLER_T old_sigalrm_handler = SIG_ERR; +#endif + +#ifdef SIGINT +static SIGHANDLER_T old_sigint_handler  = SIG_ERR; +#endif + +#ifdef SIGTERM +static SIGHANDLER_T old_sigterm_handler = SIG_ERR; +#endif + +/* var which if set indicates that the program should finish execution */ + +SIG_ATOMIC_T got_exit_signal = 0; + +/* if next is set indicates the first signal handled in exit_signal_handler */ + +static volatile int exit_signal = 0; + +/* signal handler that will be triggered to indicate that the program +  should finish its execution in a controlled manner as soon as possible. +  The first time this is called it will set got_exit_signal to one and +  store in exit_signal the signal that triggered its execution. */ + +static RETSIGTYPE exit_signal_handler(int signum) +{ +  int old_errno = ERRNO; +  if(got_exit_signal == 0) { +    got_exit_signal = 1; +    exit_signal = signum; +  } +  (void)signal(signum, exit_signal_handler); +  SET_ERRNO(old_errno); +} + +static void install_signal_handlers(void) +{ +#ifdef SIGHUP +  /* ignore SIGHUP signal */ +  if((old_sighup_handler = signal(SIGHUP, SIG_IGN)) == SIG_ERR) +    logmsg("cannot install SIGHUP handler: %s", strerror(ERRNO)); +#endif +#ifdef SIGPIPE +  /* ignore SIGPIPE signal */ +  if((old_sigpipe_handler = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) +    logmsg("cannot install SIGPIPE handler: %s", strerror(ERRNO)); +#endif +#ifdef SIGALRM +  /* ignore SIGALRM signal */ +  if((old_sigalrm_handler = signal(SIGALRM, SIG_IGN)) == SIG_ERR) +    logmsg("cannot install SIGALRM handler: %s", strerror(ERRNO)); +#endif +#ifdef SIGINT +  /* handle SIGINT signal with our exit_signal_handler */ +  if((old_sigint_handler = signal(SIGINT, exit_signal_handler)) == SIG_ERR) +    logmsg("cannot install SIGINT handler: %s", strerror(ERRNO)); +  else +    siginterrupt(SIGINT, 1); +#endif +#ifdef SIGTERM +  /* handle SIGTERM signal with our exit_signal_handler */ +  if((old_sigterm_handler = signal(SIGTERM, exit_signal_handler)) == SIG_ERR) +    logmsg("cannot install SIGTERM handler: %s", strerror(ERRNO)); +  else +    siginterrupt(SIGTERM, 1); +#endif +} + +static void restore_signal_handlers(void) +{ +#ifdef SIGHUP +  if(SIG_ERR != old_sighup_handler) +    (void)signal(SIGHUP, old_sighup_handler); +#endif +#ifdef SIGPIPE +  if(SIG_ERR != old_sigpipe_handler) +    (void)signal(SIGPIPE, old_sigpipe_handler); +#endif +#ifdef SIGALRM +  if(SIG_ERR != old_sigalrm_handler) +    (void)signal(SIGALRM, old_sigalrm_handler); +#endif +#ifdef SIGINT +  if(SIG_ERR != old_sigint_handler) +    (void)signal(SIGINT, old_sigint_handler); +#endif +#ifdef SIGTERM +  if(SIG_ERR != old_sigterm_handler) +    (void)signal(SIGTERM, old_sigterm_handler); +#endif +} + +static int ProcessRequest(struct httprequest *req) +{ +  char *line=&req->reqbuf[req->checkindex]; +  bool chunked = FALSE; +  static char request[REQUEST_KEYWORD_SIZE]; +  static char doc[MAXDOCNAMELEN]; +  static char prot_str[5]; +  char logbuf[256]; +  int prot_major, prot_minor; +  char *end; +  int error; +  end = strstr(line, END_OF_HEADERS); + +  logmsg("ProcessRequest() called with testno %ld and line [%s]", +         req->testno, line); + +  /* try to figure out the request characteristics as soon as possible, but +     only once! */ +  if((req->testno == DOCNUMBER_NOTHING) && +     sscanf(line, +            "%" REQUEST_KEYWORD_SIZE_TXT"s %" MAXDOCNAMELEN_TXT "s %4s/%d.%d", +            request, +            doc, +            prot_str, +            &prot_major, +            &prot_minor) == 5) { +    char *ptr; + +    if(!strcmp(prot_str, "HTTP")) { +        req->protocol = RPROT_HTTP; +    } +    else if(!strcmp(prot_str, "RTSP")) { +        req->protocol = RPROT_RTSP; +    } +    else { +        req->protocol = RPROT_NONE; +        logmsg("got unknown protocol %s", prot_str); +        return 1; +    } + +    req->prot_version = prot_major*10 + prot_minor; + +    /* find the last slash */ +    ptr = strrchr(doc, '/'); + +    /* get the number after it */ +    if(ptr) { +      FILE *stream; +      char *filename; + +      if((strlen(doc) + strlen(request)) < 200) +        sprintf(logbuf, "Got request: %s %s %s/%d.%d", +                request, doc, prot_str, prot_major, prot_minor); +      else +        sprintf(logbuf, "Got a *HUGE* request %s/%d.%d", +                prot_str, prot_major, prot_minor); +      logmsg("%s", logbuf); + +      if(!strncmp("/verifiedserver", ptr, 15)) { +        logmsg("Are-we-friendly question received"); +        req->testno = DOCNUMBER_WERULEZ; +        return 1; /* done */ +      } + +      if(!strncmp("/quit", ptr, 5)) { +        logmsg("Request-to-quit received"); +        req->testno = DOCNUMBER_QUIT; +        return 1; /* done */ +      } + +      ptr++; /* skip the slash */ + +      /* skip all non-numericals following the slash */ +      while(*ptr && !ISDIGIT(*ptr)) +        ptr++; + +      req->testno = strtol(ptr, &ptr, 10); + +      if(req->testno > 10000) { +        req->partno = req->testno % 10000; +        req->testno /= 10000; +      } +      else +        req->partno = 0; + +      sprintf(logbuf, "Requested test number %ld part %ld", +              req->testno, req->partno); +      logmsg("%s", logbuf); + +      filename = test2file(req->testno); + +      stream=fopen(filename, "rb"); +      if(!stream) { +        error = ERRNO; +        logmsg("fopen() failed with error: %d %s", error, strerror(error)); +        logmsg("Error opening file: %s", filename); +        logmsg("Couldn't open test file %ld", req->testno); +        req->open = FALSE; /* closes connection */ +        return 1; /* done */ +      } +      else { +        char *cmd = NULL; +        size_t cmdsize = 0; +        int num=0; + +        int rtp_channel = 0; +        int rtp_size = 0; +        int rtp_partno = -1; +        int i = 0; +        char *rtp_scratch = NULL; + +        /* get the custom server control "commands" */ +        cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize); +        ptr = cmd; +        fclose(stream); + +        if(cmdsize) { +          logmsg("Found a reply-servercmd section!"); +          do { +            if(!strncmp(CMD_AUTH_REQUIRED, ptr, strlen(CMD_AUTH_REQUIRED))) { +              logmsg("instructed to require authorization header"); +              req->auth_req = TRUE; +            } +            else if(!strncmp(CMD_IDLE, ptr, strlen(CMD_IDLE))) { +              logmsg("instructed to idle"); +              req->rcmd = RCMD_IDLE; +              req->open = TRUE; +            } +            else if(!strncmp(CMD_STREAM, ptr, strlen(CMD_STREAM))) { +              logmsg("instructed to stream"); +              req->rcmd = RCMD_STREAM; +            } +            else if(1 == sscanf(ptr, "pipe: %d", &num)) { +              logmsg("instructed to allow a pipe size of %d", num); +              if(num < 0) +                logmsg("negative pipe size ignored"); +              else if(num > 0) +                req->pipe = num-1; /* decrease by one since we don't count the +                                      first request in this number */ +            } +            else if(1 == sscanf(ptr, "skip: %d", &num)) { +              logmsg("instructed to skip this number of bytes %d", num); +              req->skip = num; +            } +            else if(3 == sscanf(ptr, "rtp: part %d channel %d size %d", +                                &rtp_partno, &rtp_channel, &rtp_size)) { + +              if(rtp_partno == req->partno) { +                logmsg("RTP: part %d channel %d size %d", +                       rtp_partno, rtp_channel, rtp_size); + +                /* Make our scratch buffer enough to fit all the +                 * desired data and one for padding */ +                rtp_scratch = malloc(rtp_size + 4 + RTP_DATA_SIZE); + +                /* RTP is signalled with a $ */ +                rtp_scratch[0] = '$'; + +                /* The channel follows and is one byte */ +                rtp_scratch[1] = (char)(rtp_channel & 0xFF); + +                /* Length follows and is a two byte short in network order */ +                *((unsigned short *)(&rtp_scratch[2])) = +                  htons((unsigned short)rtp_size); + +                /* Fill it with junk data */ +                for(i = 0; i < rtp_size; i+= RTP_DATA_SIZE) { +                  memcpy(rtp_scratch + 4 + i, RTP_DATA, RTP_DATA_SIZE); +                } + +                if(req->rtp_buffer == NULL) { +                  req->rtp_buffer = rtp_scratch; +                  req->rtp_buffersize = rtp_size + 4; +                } else { +                  req->rtp_buffer = realloc(req->rtp_buffer, req->rtp_buffersize + rtp_size + 4); +                  memcpy(req->rtp_buffer + req->rtp_buffersize, rtp_scratch, rtp_size + 4); +                  req->rtp_buffersize += rtp_size + 4; +                  free(rtp_scratch); +                } +                logmsg("rtp_buffersize is %zu, rtp_size is %d.", req->rtp_buffersize, rtp_size); + +              } +            } +            else { +              logmsg("funny instruction found: %s", ptr); +            } + +            ptr = strchr(ptr, '\n'); +            if(ptr) +              ptr++; +            else +              ptr = NULL; +          } while(ptr && *ptr); +          logmsg("Done parsing server commands"); +        } +      } +    } +    else { +      if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d", +                doc, &prot_major, &prot_minor) == 3) { +        sprintf(logbuf, "Received a CONNECT %s HTTP/%d.%d request", +                doc, prot_major, prot_minor); +        logmsg("%s", logbuf); + +        if(req->prot_version == 10) +          req->open = FALSE; /* HTTP 1.0 closes connection by default */ + +        if(!strncmp(doc, "bad", 3)) +          /* if the host name starts with bad, we fake an error here */ +          req->testno = DOCNUMBER_BADCONNECT; +        else if(!strncmp(doc, "test", 4)) { +          /* if the host name starts with test, the port number used in the +             CONNECT line will be used as test number! */ +          char *portp = strchr(doc, ':'); +          if(portp) +            req->testno = atoi(portp+1); +          else +            req->testno = DOCNUMBER_CONNECT; +        } +        else +          req->testno = DOCNUMBER_CONNECT; +      } +      else { +        logmsg("Did not find test number in PATH"); +        req->testno = DOCNUMBER_404; +      } +    } +  } + +  if(!end) { +    /* we don't have a complete request yet! */ +    logmsg("ProcessRequest returned without a complete request"); +    return 0; /* not complete yet */ +  } +  logmsg("ProcessRequest found a complete request"); + +  if(req->pipe) +    /* we do have a full set, advance the checkindex to after the end of the +       headers, for the pipelining case mostly */ +    req->checkindex += (end - line) + strlen(END_OF_HEADERS); + +  /* **** Persistence **** +   * +   * If the request is a HTTP/1.0 one, we close the connection unconditionally +   * when we're done. +   * +   * If the request is a HTTP/1.1 one, we MUST check for a "Connection:" +   * header that might say "close". If it does, we close a connection when +   * this request is processed. Otherwise, we keep the connection alive for X +   * seconds. +   */ + +  do { +    if(got_exit_signal) +      return 1; /* done */ + +    if((req->cl==0) && curlx_strnequal("Content-Length:", line, 15)) { +      /* If we don't ignore content-length, we read it and we read the whole +         request including the body before we return. If we've been told to +         ignore the content-length, we will return as soon as all headers +         have been received */ +      size_t cl = strtol(line+15, &line, 10); +      req->cl = cl - req->skip; + +      logmsg("Found Content-Length: %zu in the request", cl); +      if(req->skip) +        logmsg("... but will abort after %zu bytes", req->cl); +      break; +    } +    else if(curlx_strnequal("Transfer-Encoding: chunked", line, +                            strlen("Transfer-Encoding: chunked"))) { +      /* chunked data coming in */ +      chunked = TRUE; +    } + +    if(chunked) { +      if(strstr(req->reqbuf, "\r\n0\r\n\r\n")) +        /* end of chunks reached */ +        return 1; /* done */ +      else +        return 0; /* not done */ +    } + +    line = strchr(line, '\n'); +    if(line) +      line++; + +  } while(line); + +  if(!req->auth && strstr(req->reqbuf, "Authorization:")) { +    req->auth = TRUE; /* Authorization: header present! */ +    if(req->auth_req) +      logmsg("Authorization header found, as required"); +  } + +  if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) { +    /* If the client is passing this Digest-header, we set the part number +       to 1000. Not only to spice up the complexity of this, but to make +       Digest stuff to work in the test suite. */ +    req->partno += 1000; +    req->digest = TRUE; /* header found */ +    logmsg("Received Digest request, sending back data %ld", req->partno); +  } +  else if(!req->ntlm && +          strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAD")) { +    /* If the client is passing this type-3 NTLM header */ +    req->partno += 1002; +    req->ntlm = TRUE; /* NTLM found */ +    logmsg("Received NTLM type-3, sending back data %ld", req->partno); +    if(req->cl) { +      logmsg("  Expecting %zu POSTed bytes", req->cl); +    } +  } +  else if(!req->ntlm && +          strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAB")) { +    /* If the client is passing this type-1 NTLM header */ +    req->partno += 1001; +    req->ntlm = TRUE; /* NTLM found */ +    logmsg("Received NTLM type-1, sending back data %ld", req->partno); +  } +  else if((req->partno >= 1000) && strstr(req->reqbuf, "Authorization: Basic")) { +    /* If the client is passing this Basic-header and the part number is already +       >=1000, we add 1 to the part number.  This allows simple Basic authentication +       negotiation to work in the test suite. */ +    req->partno += 1; +    logmsg("Received Basic request, sending back data %ld", req->partno); +  } +  if(strstr(req->reqbuf, "Connection: close")) +    req->open = FALSE; /* close connection after this request */ + +  if(!req->pipe && +     req->open && +     req->prot_version >= 11 && +     end && +     req->reqbuf + req->offset > end + strlen(END_OF_HEADERS) && +     (!strncmp(req->reqbuf, "GET", strlen("GET")) || +      !strncmp(req->reqbuf, "HEAD", strlen("HEAD")))) { +    /* If we have a persistent connection, HTTP version >= 1.1 +       and GET/HEAD request, enable pipelining. */ +    req->checkindex = (end - req->reqbuf) + strlen(END_OF_HEADERS); +    req->pipelining = TRUE; +  } + +  while(req->pipe) { +    if(got_exit_signal) +      return 1; /* done */ +    /* scan for more header ends within this chunk */ +    line = &req->reqbuf[req->checkindex]; +    end = strstr(line, END_OF_HEADERS); +    if(!end) +      break; +    req->checkindex += (end - line) + strlen(END_OF_HEADERS); +    req->pipe--; +  } + +  /* If authentication is required and no auth was provided, end now. This +     makes the server NOT wait for PUT/POST data and you can then make the +     test case send a rejection before any such data has been sent. Test case +     154 uses this.*/ +  if(req->auth_req && !req->auth) +    return 1; /* done */ + +  if(req->cl > 0) { +    if(req->cl <= req->offset - (end - req->reqbuf) - strlen(END_OF_HEADERS)) +      return 1; /* done */ +    else +      return 0; /* not complete yet */ +  } + +  return 1; /* done */ +} + +/* store the entire request in a file */ +static void storerequest(char *reqbuf, size_t totalsize) +{ +  int res; +  int error = 0; +  size_t written; +  size_t writeleft; +  FILE *dump; + +  if (reqbuf == NULL) +    return; +  if (totalsize == 0) +    return; + +  do { +    dump = fopen(REQUEST_DUMP, "ab"); +  } while ((dump == NULL) && ((error = ERRNO) == EINTR)); +  if (dump == NULL) { +    logmsg("Error opening file %s error: %d %s", +           REQUEST_DUMP, error, strerror(error)); +    logmsg("Failed to write request input to " REQUEST_DUMP); +    return; +  } + +  writeleft = totalsize; +  do { +    written = fwrite(&reqbuf[totalsize-writeleft], +                     1, writeleft, dump); +    if(got_exit_signal) +      goto storerequest_cleanup; +    if(written > 0) +      writeleft -= written; +  } while ((writeleft > 0) && ((error = ERRNO) == EINTR)); + +  if(writeleft == 0) +    logmsg("Wrote request (%zu bytes) input to " REQUEST_DUMP, totalsize); +  else if(writeleft > 0) { +    logmsg("Error writing file %s error: %d %s", +           REQUEST_DUMP, error, strerror(error)); +    logmsg("Wrote only (%zu bytes) of (%zu bytes) request input to %s", +           totalsize-writeleft, totalsize, REQUEST_DUMP); +  } + +storerequest_cleanup: + +  do { +    res = fclose(dump); +  } while(res && ((error = ERRNO) == EINTR)); +  if(res) +    logmsg("Error closing file %s error: %d %s", +           REQUEST_DUMP, error, strerror(error)); +} + +/* return 0 on success, non-zero on failure */ +static int get_request(curl_socket_t sock, struct httprequest *req) +{ +  int error; +  int fail = 0; +  int done_processing = 0; +  char *reqbuf = req->reqbuf; +  ssize_t got = 0; + +  char *pipereq = NULL; +  size_t pipereq_length = 0; + +  if(req->pipelining) { +    pipereq = reqbuf + req->checkindex; +    pipereq_length = req->offset - req->checkindex; +  } + +  /*** Init the httprequest structure properly for the upcoming request ***/ + +  req->checkindex = 0; +  req->offset = 0; +  req->testno = DOCNUMBER_NOTHING; +  req->partno = 0; +  req->open = TRUE; +  req->auth_req = FALSE; +  req->auth = FALSE; +  req->cl = 0; +  req->digest = FALSE; +  req->ntlm = FALSE; +  req->pipe = 0; +  req->skip = 0; +  req->rcmd = RCMD_NORMALREQ; +  req->protocol = RPROT_NONE; +  req->prot_version = 0; +  req->pipelining = FALSE; +  req->rtp_buffer = NULL; +  req->rtp_buffersize = 0; + +  /*** end of httprequest init ***/ + +  while(!done_processing && (req->offset < REQBUFSIZ-1)) { +    if(pipereq_length && pipereq) { +      memmove(reqbuf, pipereq, pipereq_length); +      got = pipereq_length; +      pipereq_length = 0; +    } +    else { +      if(req->skip) +        /* we are instructed to not read the entire thing, so we make sure to only +           read what we're supposed to and NOT read the enire thing the client +           wants to send! */ +        got = sread(sock, reqbuf + req->offset, req->cl); +      else +        got = sread(sock, reqbuf + req->offset, REQBUFSIZ-1 - req->offset); +    } +    if(got_exit_signal) +      return 1; +    if(got == 0) { +      logmsg("Connection closed by client"); +      fail = 1; +    } +    else if(got < 0) { +      error = SOCKERRNO; +      logmsg("recv() returned error: (%d) %s", error, strerror(error)); +      fail = 1; +    } +    if(fail) { +      /* dump the request received so far to the external file */ +      reqbuf[req->offset] = '\0'; +      storerequest(reqbuf, req->offset); +      return 1; +    } + +    logmsg("Read %zd bytes", got); + +    req->offset += (size_t)got; +    reqbuf[req->offset] = '\0'; + +    done_processing = ProcessRequest(req); +    if(got_exit_signal) +      return 1; +    if(done_processing && req->pipe) { +      logmsg("Waiting for another piped request"); +      done_processing = 0; +      req->pipe--; +    } +  } + +  if((req->offset == REQBUFSIZ-1) && (got > 0)) { +    logmsg("Request would overflow buffer, closing connection"); +    /* dump request received so far to external file anyway */ +    reqbuf[REQBUFSIZ-1] = '\0'; +    fail = 1; +  } +  else if(req->offset > REQBUFSIZ-1) { +    logmsg("Request buffer overflow, closing connection"); +    /* dump request received so far to external file anyway */ +    reqbuf[REQBUFSIZ-1] = '\0'; +    fail = 1; +  } +  else +    reqbuf[req->offset] = '\0'; + +  /* dump the request to an external file */ +  storerequest(reqbuf, req->pipelining ? req->checkindex : req->offset); +  if(got_exit_signal) +    return 1; + +  return fail; /* return 0 on success */ +} + +/* returns -1 on failure */ +static int send_doc(curl_socket_t sock, struct httprequest *req) +{ +  ssize_t written; +  size_t count; +  const char *buffer; +  char *ptr=NULL; +  FILE *stream; +  char *cmd=NULL; +  size_t cmdsize=0; +  FILE *dump; +  bool persistant = TRUE; +  bool sendfailure = FALSE; +  size_t responsesize; +  int error = 0; +  int res; + +  static char weare[256]; + +  char partbuf[80]="data"; + +  logmsg("Send response number %ld part %ld", req->testno, req->partno); + +  switch(req->rcmd) { +  default: +  case RCMD_NORMALREQ: +    break; /* continue with business as usual */ +  case RCMD_STREAM: +#define STREAMTHIS "a string to stream 01234567890\n" +    count = strlen(STREAMTHIS); +    for (;;) { +      written = swrite(sock, STREAMTHIS, count); +      if(got_exit_signal) +        return -1; +      if(written != (ssize_t)count) { +        logmsg("Stopped streaming"); +        break; +      } +    } +    return -1; +  case RCMD_IDLE: +    /* Do nothing. Sit idle. Pretend it rains. */ +    return 0; +  } + +  req->open = FALSE; + +  if(req->testno < 0) { +    size_t msglen; +    char msgbuf[64]; + +    switch(req->testno) { +    case DOCNUMBER_QUIT: +      logmsg("Replying to QUIT"); +      buffer = docquit; +      break; +    case DOCNUMBER_WERULEZ: +      /* we got a "friends?" question, reply back that we sure are */ +      logmsg("Identifying ourselves as friends"); +      sprintf(msgbuf, "RTSP_SERVER WE ROOLZ: %ld\r\n", (long)getpid()); +      msglen = strlen(msgbuf); +      sprintf(weare, "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\n\r\n%s", +              msglen, msgbuf); +      buffer = weare; +      break; +    case DOCNUMBER_INTERNAL: +      logmsg("Bailing out due to internal error"); +      return -1; +    case DOCNUMBER_CONNECT: +      logmsg("Replying to CONNECT"); +      buffer = docconnect; +      break; +    case DOCNUMBER_BADCONNECT: +      logmsg("Replying to a bad CONNECT"); +      buffer = docbadconnect; +      break; +    case DOCNUMBER_404: +    default: +      logmsg("Replying to with a 404"); +      if(req->protocol == RPROT_HTTP) { +          buffer = doc404_HTTP; +      } else { +          buffer = doc404_RTSP; +      } +      break; +    } + +    count = strlen(buffer); +  } +  else { +    char *filename = test2file(req->testno); + +    if(0 != req->partno) +      sprintf(partbuf, "data%ld", req->partno); + +    stream=fopen(filename, "rb"); +    if(!stream) { +      error = ERRNO; +      logmsg("fopen() failed with error: %d %s", error, strerror(error)); +      logmsg("Error opening file: %s", filename); +      logmsg("Couldn't open test file"); +      return 0; +    } +    else { +      buffer = spitout(stream, "reply", partbuf, &count); +      ptr = (char *)buffer; +      fclose(stream); +    } + +    if(got_exit_signal) +      return -1; + +    /* re-open the same file again */ +    stream=fopen(filename, "rb"); +    if(!stream) { +      error = ERRNO; +      logmsg("fopen() failed with error: %d %s", error, strerror(error)); +      logmsg("Error opening file: %s", filename); +      logmsg("Couldn't open test file"); +      return 0; +    } +    else { +      /* get the custom server control "commands" */ +      cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize); +      fclose(stream); +    } +  } + +  if(got_exit_signal) +    return -1; + +  /* If the word 'swsclose' is present anywhere in the reply chunk, the +     connection will be closed after the data has been sent to the requesting +     client... */ +  if(strstr(buffer, "swsclose") || !count) { +    persistant = FALSE; +    logmsg("connection close instruction \"swsclose\" found in response"); +  } +  if(strstr(buffer, "swsbounce")) { +    prevbounce = TRUE; +    logmsg("enable \"swsbounce\" in the next request"); +  } +  else +    prevbounce = FALSE; + +  dump = fopen(RESPONSE_DUMP, "ab"); +  if(!dump) { +    error = ERRNO; +    logmsg("fopen() failed with error: %d %s", error, strerror(error)); +    logmsg("Error opening file: %s", RESPONSE_DUMP); +    logmsg("couldn't create logfile: " RESPONSE_DUMP); +    return -1; +  } + +  responsesize = count; +  do { +    /* Ok, we send no more than 200 bytes at a time, just to make sure that +       larger chunks are split up so that the client will need to do multiple +       recv() calls to get it and thus we exercise that code better */ +    size_t num = count; +    if(num > 200) +      num = 200; +    written = swrite(sock, buffer, num); +    if (written < 0) { +      sendfailure = TRUE; +      break; +    } +    else { +      logmsg("Sent off %zd bytes", written); +    } +    /* write to file as well */ +    fwrite(buffer, 1, (size_t)written, dump); +    if(got_exit_signal) +      break; + +    count -= written; +    buffer += written; +  } while(count>0); + +  /* Send out any RTP data */ +  if(req->rtp_buffer) { +    logmsg("About to write %zu RTP bytes", req->rtp_buffersize); +    count = req->rtp_buffersize; +    do { +      size_t num = count; +      if(num > 200) +        num = 200; +      written = swrite(sock, req->rtp_buffer + (req->rtp_buffersize - count), num); +      if(written < 0) { +        sendfailure = TRUE; +        break; +      } +      count -= written; +    } while(count > 0); + +    free(req->rtp_buffer); +    req->rtp_buffersize = 0; +  } + + +  do { +    res = fclose(dump); +  } while(res && ((error = ERRNO) == EINTR)); +  if(res) +    logmsg("Error closing file %s error: %d %s", +           RESPONSE_DUMP, error, strerror(error)); + +  if(got_exit_signal) +    return -1; + +  if(sendfailure) { +    logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) were sent", +           responsesize-count, responsesize); +    if(ptr) +      free(ptr); +    if(cmd) +      free(cmd); +    return -1; +  } + +  logmsg("Response sent (%zu bytes) and written to " RESPONSE_DUMP, +         responsesize); + +  if(ptr) +    free(ptr); + +  if(cmdsize > 0 ) { +    char command[32]; +    int quarters; +    int num; +    ptr=cmd; +    do { +      if(2 == sscanf(ptr, "%31s %d", command, &num)) { +        if(!strcmp("wait", command)) { +          logmsg("Told to sleep for %d seconds", num); +          quarters = num * 4; +          while(quarters > 0) { +            quarters--; +            res = wait_ms(250); +            if(got_exit_signal) +              break; +            if(res) { +              /* should not happen */ +              error = SOCKERRNO; +              logmsg("wait_ms() failed with error: (%d) %s", +                     error, strerror(error)); +              break; +            } +          } +          if(!quarters) +            logmsg("Continuing after sleeping %d seconds", num); +        } +        else +          logmsg("Unknown command in reply command section"); +      } +      ptr = strchr(ptr, '\n'); +      if(ptr) +        ptr++; +      else +        ptr = NULL; +    } while(ptr && *ptr); +  } +  if(cmd) +    free(cmd); + +  req->open = persistant; + +  prevtestno = req->testno; +  prevpartno = req->partno; + +  return 0; +} + + +int main(int argc, char *argv[]) +{ +  struct sockaddr_in me; +#ifdef ENABLE_IPV6 +  struct sockaddr_in6 me6; +#endif /* ENABLE_IPV6 */ +  curl_socket_t sock = CURL_SOCKET_BAD; +  curl_socket_t msgsock = CURL_SOCKET_BAD; +  int wrotepidfile = 0; +  int flag; +  unsigned short port = DEFAULT_PORT; +  char *pidname= (char *)".rtsp.pid"; +  struct httprequest req; +  int rc; +  int error; +  int arg=1; +  long pid; + +  while(argc>arg) { +    if(!strcmp("--version", argv[arg])) { +      printf("rtspd IPv4%s" +             "\n" +             , +#ifdef ENABLE_IPV6 +             "/IPv6" +#else +             "" +#endif +             ); +      return 0; +    } +    else if(!strcmp("--pidfile", argv[arg])) { +      arg++; +      if(argc>arg) +        pidname = argv[arg++]; +    } +    else if(!strcmp("--logfile", argv[arg])) { +      arg++; +      if(argc>arg) +        serverlogfile = argv[arg++]; +    } +    else if(!strcmp("--ipv4", argv[arg])) { +#ifdef ENABLE_IPV6 +      ipv_inuse = "IPv4"; +      use_ipv6 = FALSE; +#endif +      arg++; +    } +    else if(!strcmp("--ipv6", argv[arg])) { +#ifdef ENABLE_IPV6 +      ipv_inuse = "IPv6"; +      use_ipv6 = TRUE; +#endif +      arg++; +    } +    else if(!strcmp("--port", argv[arg])) { +      arg++; +      if(argc>arg) { +        port = (unsigned short)atoi(argv[arg]); +        arg++; +      } +    } +    else if(!strcmp("--srcdir", argv[arg])) { +      arg++; +      if(argc>arg) { +        path = argv[arg]; +        arg++; +      } +    } +    else { +      puts("Usage: rtspd [option]\n" +           " --version\n" +           " --logfile [file]\n" +           " --pidfile [file]\n" +           " --ipv4\n" +           " --ipv6\n" +           " --port [port]\n" +           " --srcdir [path]"); +      return 0; +    } +  } + +#ifdef WIN32 +  win32_init(); +  atexit(win32_cleanup); +#endif + +  install_signal_handlers(); + +  pid = (long)getpid(); + +#ifdef ENABLE_IPV6 +  if(!use_ipv6) +#endif +    sock = socket(AF_INET, SOCK_STREAM, 0); +#ifdef ENABLE_IPV6 +  else +    sock = socket(AF_INET6, SOCK_STREAM, 0); +#endif + +  if(CURL_SOCKET_BAD == sock) { +    error = SOCKERRNO; +    logmsg("Error creating socket: (%d) %s", +           error, strerror(error)); +    goto server_cleanup; +  } + +  flag = 1; +  if (0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, +            (void *)&flag, sizeof(flag))) { +    error = SOCKERRNO; +    logmsg("setsockopt(SO_REUSEADDR) failed with error: (%d) %s", +           error, strerror(error)); +    goto server_cleanup; +  } + +#ifdef ENABLE_IPV6 +  if(!use_ipv6) { +#endif +    memset(&me, 0, sizeof(me)); +    me.sin_family = AF_INET; +    me.sin_addr.s_addr = INADDR_ANY; +    me.sin_port = htons(port); +    rc = bind(sock, (struct sockaddr *) &me, sizeof(me)); +#ifdef ENABLE_IPV6 +  } +  else { +    memset(&me6, 0, sizeof(me6)); +    me6.sin6_family = AF_INET6; +    me6.sin6_addr = in6addr_any; +    me6.sin6_port = htons(port); +    rc = bind(sock, (struct sockaddr *) &me6, sizeof(me6)); +  } +#endif /* ENABLE_IPV6 */ +  if(0 != rc) { +    error = SOCKERRNO; +    logmsg("Error binding socket on port %hu: (%d) %s", +           port, error, strerror(error)); +    goto server_cleanup; +  } + +  logmsg("Running %s version on port %d", ipv_inuse, (int)port); + +  /* start accepting connections */ +  rc = listen(sock, 5); +  if(0 != rc) { +    error = SOCKERRNO; +    logmsg("listen() failed with error: (%d) %s", +           error, strerror(error)); +    goto server_cleanup; +  } + +  /* +  ** As soon as this server writes its pid file the test harness will +  ** attempt to connect to this server and initiate its verification. +  */ + +  wrotepidfile = write_pidfile(pidname); +  if(!wrotepidfile) +    goto server_cleanup; + +  for (;;) { +    msgsock = accept(sock, NULL, NULL); + +    if(got_exit_signal) +      break; +    if (CURL_SOCKET_BAD == msgsock) { +      error = SOCKERRNO; +      logmsg("MAJOR ERROR: accept() failed with error: (%d) %s", +             error, strerror(error)); +      break; +    } + +    /* +    ** As soon as this server acepts a connection from the test harness it +    ** must set the server logs advisor read lock to indicate that server +    ** logs should not be read until this lock is removed by this server. +    */ + +    set_advisor_read_lock(SERVERLOGS_LOCK); +    serverlogslocked = 1; + +    logmsg("====> Client connect"); + +#ifdef TCP_NODELAY +    /* +     * Disable the Nagle algorithm to make it easier to send out a large +     * response in many small segments to torture the clients more. +     */ +    flag = 1; +    if (setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY, +                   (void *)&flag, sizeof(flag)) == -1) { +      logmsg("====> TCP_NODELAY failed"); +    } +#endif + +    /* initialization of httprequest struct is done in get_request(), but due +       to pipelining treatment the pipelining struct field must be initialized +       previously to FALSE every time a new connection arrives. */ + +    req.pipelining = FALSE; + +    do { +      if(got_exit_signal) +        break; + +      if(get_request(msgsock, &req)) +        /* non-zero means error, break out of loop */ +        break; + +      if(prevbounce) { +        /* bounce treatment requested */ +        if((req.testno == prevtestno) && +           (req.partno == prevpartno)) { +          req.partno++; +          logmsg("BOUNCE part number to %ld", req.partno); +        } +        else { +          prevbounce = FALSE; +          prevtestno = -1; +          prevpartno = -1; +        } +      } + +      send_doc(msgsock, &req); +      if(got_exit_signal) +        break; + +      if((req.testno < 0) && (req.testno != DOCNUMBER_CONNECT)) { +        logmsg("special request received, no persistency"); +        break; +      } +      if(!req.open) { +        logmsg("instructed to close connection after server-reply"); +        break; +      } + +      if(req.open) +        logmsg("=> persistant connection request ended, awaits new request"); +      /* if we got a CONNECT, loop and get another request as well! */ +    } while(req.open || (req.testno == DOCNUMBER_CONNECT)); + +    if(got_exit_signal) +      break; + +    logmsg("====> Client disconnect"); +    sclose(msgsock); +    msgsock = CURL_SOCKET_BAD; + +    if(serverlogslocked) { +      serverlogslocked = 0; +      clear_advisor_read_lock(SERVERLOGS_LOCK); +    } + +    if (req.testno == DOCNUMBER_QUIT) +      break; +  } + +server_cleanup: + +  if((msgsock != sock) && (msgsock != CURL_SOCKET_BAD)) +    sclose(msgsock); + +  if(sock != CURL_SOCKET_BAD) +    sclose(sock); + +  if(got_exit_signal) +    logmsg("signalled to die"); + +  if(wrotepidfile) +    unlink(pidname); + +  if(serverlogslocked) { +    serverlogslocked = 0; +    clear_advisor_read_lock(SERVERLOGS_LOCK); +  } + +  restore_signal_handlers(); + +  if(got_exit_signal) { +    logmsg("========> %s rtspd (port: %d pid: %ld) exits with signal (%d)", +           ipv_inuse, (int)port, pid, exit_signal); +    /* +     * To properly set the return status of the process we +     * must raise the same signal SIGINT or SIGTERM that we +     * caught and let the old handler take care of it. +     */ +    raise(exit_signal); +  } + +  logmsg("========> rtspd quits"); +  return 0; +} + diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm index d34a821a1..41b7c6647 100644 --- a/tests/serverhelp.pm +++ b/tests/serverhelp.pm @@ -77,7 +77,7 @@ sub serverfactors {          $idnum  = ($3 && ($3 > 1)) ? $3 : 1;          $ipvnum = ($4 && ($4 =~ /6$/)) ? 6 : 4;      } -    elsif($server =~ /^(tftp|sftp|socks|ssh)(\d*)(-ipv6|)$/) { +    elsif($server =~ /^(tftp|sftp|socks|ssh|rtsp)(\d*)(-ipv6|)$/) {          $proto  = $1;          $idnum  = ($2 && ($2 > 1)) ? $2 : 1;          $ipvnum = ($3 && ($3 =~ /6$/)) ? 6 : 4; @@ -97,7 +97,7 @@ sub servername_str {      $proto = uc($proto) if($proto);      die "unsupported protocol: $proto" unless($proto && -        ($proto =~ /^(((FTP|HTTP|IMAP|POP3|SMTP)S?)|(TFTP|SFTP|SOCKS|SSH))$/)); +        ($proto =~ /^(((FTP|HTTP|IMAP|POP3|SMTP)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP))$/));      $ipver = (not $ipver) ? 'ipv4' : lc($ipver);      die "unsupported IP version: $ipver" unless($ipver && | 
