From 35fbeda0032860929292896f73ab37a274cb5fa1 Mon Sep 17 00:00:00 2001 From: Yang Tse Date: Mon, 1 Feb 2010 12:05:08 +0000 Subject: Test suite support for RTSP --- tests/FILEFORMAT | 8 + tests/Makefile.am | 2 +- tests/data/DISABLED | 5 - tests/rtspserver.pl | 109 ++++ tests/runtests.pl | 187 +++++- tests/server/Makefile.inc | 5 +- tests/server/rtspd.c | 1410 +++++++++++++++++++++++++++++++++++++++++++++ tests/serverhelp.pm | 4 +- 8 files changed, 1720 insertions(+), 10 deletions(-) create mode 100755 tests/rtspserver.pl create mode 100644 tests/server/rtspd.c 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 @@ -148,6 +152,8 @@ scp sftp socks4 socks5 +rtsp +rtsp-ipv6 Give only one per line. This subsection is mandatory. @@ -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, , 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); @@ -802,6 +804,79 @@ sub verifyftp { return $pid; } +####################################################################### +# 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 = ) { + logmsg "RUN: $string" if($string !~ /^([ \t]*)$/); + } + close(FILE); + } + } + + my $data; + if(open(FILE, "<$verifyout")) { + while(my $string = ) { + $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 @@ -901,6 +976,7 @@ sub verifysocks { my %protofunc = ('http' => \&verifyhttp, 'https' => \&verifyhttp, + 'rtsp' => \&verifyrtsp, 'ftp' => \&verifyftp, 'pop3' => \&verifyftp, 'imap' => \&verifyftp, @@ -1358,6 +1434,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 # @@ -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, , 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 +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_NETINET_TCP_H +#include /* 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 + "\n" + "\n" + "404 Not Found\n" + "\n" + "

Not Found

\n" + "The requested URL was not found on this server.\n" + "


" RTSPDVERSION "
\n" "\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 && -- cgit v1.2.3