xref: /third_party/curl/tests/server/rtspd.c (revision 13498266)
1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24#include "server_setup.h"
25
26/*
27 * curl's test suite Real Time Streaming Protocol (RTSP) server.
28 *
29 * This source file was started based on curl's HTTP test suite server.
30 */
31
32#include <signal.h>
33#ifdef HAVE_NETINET_IN_H
34#include <netinet/in.h>
35#endif
36#ifdef HAVE_NETINET_IN6_H
37#include <netinet/in6.h>
38#endif
39#ifdef HAVE_ARPA_INET_H
40#include <arpa/inet.h>
41#endif
42#ifdef HAVE_NETDB_H
43#include <netdb.h>
44#endif
45#ifdef HAVE_NETINET_TCP_H
46#include <netinet/tcp.h> /* for TCP_NODELAY */
47#endif
48
49#define ENABLE_CURLX_PRINTF
50/* make the curlx header define all printf() functions to use the curlx_*
51   versions instead */
52#include "curlx.h" /* from the private lib dir */
53#include "getpart.h"
54#include "util.h"
55#include "server_sockaddr.h"
56
57/* include memdebug.h last */
58#include "memdebug.h"
59
60#ifdef USE_WINSOCK
61#undef  EINTR
62#define EINTR    4 /* errno.h value */
63#undef  ERANGE
64#define ERANGE  34 /* errno.h value */
65#endif
66
67#ifdef ENABLE_IPV6
68static bool use_ipv6 = FALSE;
69#endif
70static const char *ipv_inuse = "IPv4";
71static int serverlogslocked = 0;
72
73#define REQBUFSIZ 150000
74#define REQBUFSIZ_TXT "149999"
75
76static long prevtestno = -1;    /* previous test number we served */
77static long prevpartno = -1;    /* previous part number we served */
78static bool prevbounce = FALSE; /* instructs the server to increase the part
79                                   number for a test in case the identical
80                                   testno+partno request shows up again */
81
82#define RCMD_NORMALREQ 0 /* default request, use the tests file normally */
83#define RCMD_IDLE      1 /* told to sit idle */
84#define RCMD_STREAM    2 /* told to stream */
85
86typedef enum {
87  RPROT_NONE = 0,
88  RPROT_RTSP = 1,
89  RPROT_HTTP = 2
90} reqprot_t;
91
92#define SET_RTP_PKT_CHN(p,c)  ((p)[1] = (unsigned char)((c) & 0xFF))
93
94#define SET_RTP_PKT_LEN(p,l) (((p)[2] = (unsigned char)(((l) >> 8) & 0xFF)), \
95                              ((p)[3] = (unsigned char)((l) & 0xFF)))
96
97struct httprequest {
98  char reqbuf[REQBUFSIZ]; /* buffer area for the incoming request */
99  size_t checkindex; /* where to start checking of the request */
100  size_t offset;     /* size of the incoming request */
101  long testno;       /* test number found in the request */
102  long partno;       /* part number found in the request */
103  bool open;      /* keep connection open info, as found in the request */
104  bool auth_req;  /* authentication required, don't wait for body unless
105                     there's an Authorization header */
106  bool auth;      /* Authorization header present in the incoming request */
107  size_t cl;      /* Content-Length of the incoming request */
108  bool digest;    /* Authorization digest header found */
109  bool ntlm;      /* Authorization ntlm header found */
110  int pipe;       /* if non-zero, expect this many requests to do a "piped"
111                     request/response */
112  int skip;       /* if non-zero, the server is instructed to not read this
113                     many bytes from a PUT/POST request. Ie the client sends N
114                     bytes said in Content-Length, but the server only reads N
115                     - skip bytes. */
116  int rcmd;       /* doing a special command, see defines above */
117  reqprot_t protocol; /* request protocol, HTTP or RTSP */
118  int prot_version;   /* HTTP or RTSP version (major*10 + minor) */
119  bool pipelining;    /* true if request is pipelined */
120  char *rtp_buffer;
121  size_t rtp_buffersize;
122};
123
124static int ProcessRequest(struct httprequest *req);
125static void storerequest(char *reqbuf, size_t totalsize);
126
127#define DEFAULT_PORT 8999
128
129#ifndef DEFAULT_LOGFILE
130#define DEFAULT_LOGFILE "log/rtspd.log"
131#endif
132
133const char *serverlogfile = DEFAULT_LOGFILE;
134static const char *logdir = "log";
135static char loglockfile[256];
136
137#define RTSPDVERSION "curl test suite RTSP server/0.1"
138
139#define REQUEST_DUMP  "server.input"
140#define RESPONSE_DUMP "server.response"
141
142/* very-big-path support */
143#define MAXDOCNAMELEN 140000
144#define MAXDOCNAMELEN_TXT "139999"
145
146#define REQUEST_KEYWORD_SIZE 256
147#define REQUEST_KEYWORD_SIZE_TXT "255"
148
149#define CMD_AUTH_REQUIRED "auth_required"
150
151/* 'idle' means that it will accept the request fine but never respond
152   any data. Just keep the connection alive. */
153#define CMD_IDLE "idle"
154
155/* 'stream' means to send a never-ending stream of data */
156#define CMD_STREAM "stream"
157
158#define END_OF_HEADERS "\r\n\r\n"
159
160enum {
161  DOCNUMBER_NOTHING = -7,
162  DOCNUMBER_QUIT    = -6,
163  DOCNUMBER_BADCONNECT = -5,
164  DOCNUMBER_INTERNAL = -4,
165  DOCNUMBER_CONNECT = -3,
166  DOCNUMBER_WERULEZ = -2,
167  DOCNUMBER_404     = -1
168};
169
170
171/* sent as reply to a QUIT */
172static const char *docquit =
173"HTTP/1.1 200 Goodbye" END_OF_HEADERS;
174
175/* sent as reply to a CONNECT */
176static const char *docconnect =
177"HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS;
178
179/* sent as reply to a "bad" CONNECT */
180static const char *docbadconnect =
181"HTTP/1.1 501 Forbidden you fool" END_OF_HEADERS;
182
183/* send back this on HTTP 404 file not found */
184static const char *doc404_HTTP = "HTTP/1.1 404 Not Found\r\n"
185    "Server: " RTSPDVERSION "\r\n"
186    "Connection: close\r\n"
187    "Content-Type: text/html"
188    END_OF_HEADERS
189    "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
190    "<HTML><HEAD>\n"
191    "<TITLE>404 Not Found</TITLE>\n"
192    "</HEAD><BODY>\n"
193    "<H1>Not Found</H1>\n"
194    "The requested URL was not found on this server.\n"
195    "<P><HR><ADDRESS>" RTSPDVERSION "</ADDRESS>\n" "</BODY></HTML>\n";
196
197/* send back this on RTSP 404 file not found */
198static const char *doc404_RTSP = "RTSP/1.0 404 Not Found\r\n"
199    "Server: " RTSPDVERSION
200    END_OF_HEADERS;
201
202/* Default size to send away fake RTP data */
203#define RTP_DATA_SIZE 12
204static const char *RTP_DATA = "$_1234\n\0Rsdf";
205
206static int ProcessRequest(struct httprequest *req)
207{
208  char *line = &req->reqbuf[req->checkindex];
209  bool chunked = FALSE;
210  static char request[REQUEST_KEYWORD_SIZE];
211  static char doc[MAXDOCNAMELEN];
212  static char prot_str[5];
213  int prot_major, prot_minor;
214  char *end = strstr(line, END_OF_HEADERS);
215
216  logmsg("ProcessRequest() called with testno %ld and line [%s]",
217         req->testno, line);
218
219  /* try to figure out the request characteristics as soon as possible, but
220     only once! */
221  if((req->testno == DOCNUMBER_NOTHING) &&
222     sscanf(line,
223            "%" REQUEST_KEYWORD_SIZE_TXT"s %" MAXDOCNAMELEN_TXT "s %4s/%d.%d",
224            request,
225            doc,
226            prot_str,
227            &prot_major,
228            &prot_minor) == 5) {
229    char *ptr;
230    char logbuf[256];
231
232    if(!strcmp(prot_str, "HTTP")) {
233      req->protocol = RPROT_HTTP;
234    }
235    else if(!strcmp(prot_str, "RTSP")) {
236      req->protocol = RPROT_RTSP;
237    }
238    else {
239      req->protocol = RPROT_NONE;
240      logmsg("got unknown protocol %s", prot_str);
241      return 1;
242    }
243
244    req->prot_version = prot_major*10 + prot_minor;
245
246    /* find the last slash */
247    ptr = strrchr(doc, '/');
248
249    /* get the number after it */
250    if(ptr) {
251      FILE *stream;
252      if((strlen(doc) + strlen(request)) < 200)
253        msnprintf(logbuf, sizeof(logbuf), "Got request: %s %s %s/%d.%d",
254                  request, doc, prot_str, prot_major, prot_minor);
255      else
256        msnprintf(logbuf, sizeof(logbuf), "Got a *HUGE* request %s/%d.%d",
257                  prot_str, prot_major, prot_minor);
258      logmsg("%s", logbuf);
259
260      if(!strncmp("/verifiedserver", ptr, 15)) {
261        logmsg("Are-we-friendly question received");
262        req->testno = DOCNUMBER_WERULEZ;
263        return 1; /* done */
264      }
265
266      if(!strncmp("/quit", ptr, 5)) {
267        logmsg("Request-to-quit received");
268        req->testno = DOCNUMBER_QUIT;
269        return 1; /* done */
270      }
271
272      ptr++; /* skip the slash */
273
274      /* skip all non-numericals following the slash */
275      while(*ptr && !ISDIGIT(*ptr))
276        ptr++;
277
278      req->testno = strtol(ptr, &ptr, 10);
279
280      if(req->testno > 10000) {
281        req->partno = req->testno % 10000;
282        req->testno /= 10000;
283      }
284      else
285        req->partno = 0;
286
287      msnprintf(logbuf, sizeof(logbuf), "Requested test number %ld part %ld",
288                req->testno, req->partno);
289      logmsg("%s", logbuf);
290
291      stream = test2fopen(req->testno, logdir);
292
293      if(!stream) {
294        int error = errno;
295        logmsg("fopen() failed with error: %d %s", error, strerror(error));
296        logmsg("Couldn't open test file %ld", req->testno);
297        req->open = FALSE; /* closes connection */
298        return 1; /* done */
299      }
300      else {
301        char *cmd = NULL;
302        size_t cmdsize = 0;
303        int num = 0;
304
305        int rtp_channel = 0;
306        int rtp_size = 0;
307        int rtp_size_err = 0;
308        int rtp_partno = -1;
309        char *rtp_scratch = NULL;
310
311        /* get the custom server control "commands" */
312        int error = getpart(&cmd, &cmdsize, "reply", "servercmd", stream);
313        fclose(stream);
314        if(error) {
315          logmsg("getpart() failed with error: %d", error);
316          req->open = FALSE; /* closes connection */
317          return 1; /* done */
318        }
319        ptr = cmd;
320
321        if(cmdsize) {
322          logmsg("Found a reply-servercmd section!");
323          do {
324            rtp_size_err = 0;
325            if(!strncmp(CMD_AUTH_REQUIRED, ptr, strlen(CMD_AUTH_REQUIRED))) {
326              logmsg("instructed to require authorization header");
327              req->auth_req = TRUE;
328            }
329            else if(!strncmp(CMD_IDLE, ptr, strlen(CMD_IDLE))) {
330              logmsg("instructed to idle");
331              req->rcmd = RCMD_IDLE;
332              req->open = TRUE;
333            }
334            else if(!strncmp(CMD_STREAM, ptr, strlen(CMD_STREAM))) {
335              logmsg("instructed to stream");
336              req->rcmd = RCMD_STREAM;
337            }
338            else if(1 == sscanf(ptr, "pipe: %d", &num)) {
339              logmsg("instructed to allow a pipe size of %d", num);
340              if(num < 0)
341                logmsg("negative pipe size ignored");
342              else if(num > 0)
343                req->pipe = num-1; /* decrease by one since we don't count the
344                                      first request in this number */
345            }
346            else if(1 == sscanf(ptr, "skip: %d", &num)) {
347              logmsg("instructed to skip this number of bytes %d", num);
348              req->skip = num;
349            }
350            else if(3 <= sscanf(ptr,
351                                "rtp: part %d channel %d size %d size_err %d",
352                                &rtp_partno, &rtp_channel, &rtp_size,
353                                &rtp_size_err)) {
354
355              if(rtp_partno == req->partno) {
356                int i = 0;
357                logmsg("RTP: part %d channel %d size %d size_err %d",
358                       rtp_partno, rtp_channel, rtp_size, rtp_size_err);
359
360                /* Make our scratch buffer enough to fit all the
361                 * desired data and one for padding */
362                rtp_scratch = malloc(rtp_size + 4 + RTP_DATA_SIZE);
363
364                /* RTP is signalled with a $ */
365                rtp_scratch[0] = '$';
366
367                /* The channel follows and is one byte */
368                SET_RTP_PKT_CHN(rtp_scratch, rtp_channel);
369
370                /* Length follows and is a two byte short in network order */
371                SET_RTP_PKT_LEN(rtp_scratch, rtp_size + rtp_size_err);
372
373                /* Fill it with junk data */
374                for(i = 0; i < rtp_size; i += RTP_DATA_SIZE) {
375                  memcpy(rtp_scratch + 4 + i, RTP_DATA, RTP_DATA_SIZE);
376                }
377
378                if(!req->rtp_buffer) {
379                  req->rtp_buffer = rtp_scratch;
380                  req->rtp_buffersize = rtp_size + 4;
381                }
382                else {
383                  req->rtp_buffer = realloc(req->rtp_buffer,
384                                            req->rtp_buffersize +
385                                            rtp_size + 4);
386                  memcpy(req->rtp_buffer + req->rtp_buffersize, rtp_scratch,
387                         rtp_size + 4);
388                  req->rtp_buffersize += rtp_size + 4;
389                  free(rtp_scratch);
390                }
391                logmsg("rtp_buffersize is %zu, rtp_size is %d.",
392                       req->rtp_buffersize, rtp_size);
393              }
394            }
395            else {
396              logmsg("funny instruction found: %s", ptr);
397            }
398
399            ptr = strchr(ptr, '\n');
400            if(ptr)
401              ptr++;
402            else
403              ptr = NULL;
404          } while(ptr && *ptr);
405          logmsg("Done parsing server commands");
406        }
407        free(cmd);
408      }
409    }
410    else {
411      if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
412                doc, &prot_major, &prot_minor) == 3) {
413        msnprintf(logbuf, sizeof(logbuf),
414                  "Received a CONNECT %s HTTP/%d.%d request",
415                  doc, prot_major, prot_minor);
416        logmsg("%s", logbuf);
417
418        if(req->prot_version == 10)
419          req->open = FALSE; /* HTTP 1.0 closes connection by default */
420
421        if(!strncmp(doc, "bad", 3))
422          /* if the host name starts with bad, we fake an error here */
423          req->testno = DOCNUMBER_BADCONNECT;
424        else if(!strncmp(doc, "test", 4)) {
425          /* if the host name starts with test, the port number used in the
426             CONNECT line will be used as test number! */
427          char *portp = strchr(doc, ':');
428          if(portp && (*(portp + 1) != '\0') && ISDIGIT(*(portp + 1)))
429            req->testno = strtol(portp + 1, NULL, 10);
430          else
431            req->testno = DOCNUMBER_CONNECT;
432        }
433        else
434          req->testno = DOCNUMBER_CONNECT;
435      }
436      else {
437        logmsg("Did not find test number in PATH");
438        req->testno = DOCNUMBER_404;
439      }
440    }
441  }
442
443  if(!end) {
444    /* we don't have a complete request yet! */
445    logmsg("ProcessRequest returned without a complete request");
446    return 0; /* not complete yet */
447  }
448  logmsg("ProcessRequest found a complete request");
449
450  if(req->pipe)
451    /* we do have a full set, advance the checkindex to after the end of the
452       headers, for the pipelining case mostly */
453    req->checkindex += (end - line) + strlen(END_OF_HEADERS);
454
455  /* **** Persistence ****
456   *
457   * If the request is an HTTP/1.0 one, we close the connection unconditionally
458   * when we're done.
459   *
460   * If the request is an HTTP/1.1 one, we MUST check for a "Connection:"
461   * header that might say "close". If it does, we close a connection when
462   * this request is processed. Otherwise, we keep the connection alive for X
463   * seconds.
464   */
465
466  do {
467    if(got_exit_signal)
468      return 1; /* done */
469
470    if((req->cl == 0) && strncasecompare("Content-Length:", line, 15)) {
471      /* If we don't ignore content-length, we read it and we read the whole
472         request including the body before we return. If we've been told to
473         ignore the content-length, we will return as soon as all headers
474         have been received */
475      char *endptr;
476      char *ptr = line + 15;
477      unsigned long clen = 0;
478      while(*ptr && ISSPACE(*ptr))
479        ptr++;
480      endptr = ptr;
481      errno = 0;
482      clen = strtoul(ptr, &endptr, 10);
483      if((ptr == endptr) || !ISSPACE(*endptr) || (ERANGE == errno)) {
484        /* this assumes that a zero Content-Length is valid */
485        logmsg("Found invalid Content-Length: (%s) in the request", ptr);
486        req->open = FALSE; /* closes connection */
487        return 1; /* done */
488      }
489      req->cl = clen - req->skip;
490
491      logmsg("Found Content-Length: %lu in the request", clen);
492      if(req->skip)
493        logmsg("... but will abort after %zu bytes", req->cl);
494      break;
495    }
496    else if(strncasecompare("Transfer-Encoding: chunked", line,
497                            strlen("Transfer-Encoding: chunked"))) {
498      /* chunked data coming in */
499      chunked = TRUE;
500    }
501
502    if(chunked) {
503      if(strstr(req->reqbuf, "\r\n0\r\n\r\n"))
504        /* end of chunks reached */
505        return 1; /* done */
506      else
507        return 0; /* not done */
508    }
509
510    line = strchr(line, '\n');
511    if(line)
512      line++;
513
514  } while(line);
515
516  if(!req->auth && strstr(req->reqbuf, "Authorization:")) {
517    req->auth = TRUE; /* Authorization: header present! */
518    if(req->auth_req)
519      logmsg("Authorization header found, as required");
520  }
521
522  if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) {
523    /* If the client is passing this Digest-header, we set the part number
524       to 1000. Not only to spice up the complexity of this, but to make
525       Digest stuff to work in the test suite. */
526    req->partno += 1000;
527    req->digest = TRUE; /* header found */
528    logmsg("Received Digest request, sending back data %ld", req->partno);
529  }
530  else if(!req->ntlm &&
531          strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAD")) {
532    /* If the client is passing this type-3 NTLM header */
533    req->partno += 1002;
534    req->ntlm = TRUE; /* NTLM found */
535    logmsg("Received NTLM type-3, sending back data %ld", req->partno);
536    if(req->cl) {
537      logmsg("  Expecting %zu POSTed bytes", req->cl);
538    }
539  }
540  else if(!req->ntlm &&
541          strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAB")) {
542    /* If the client is passing this type-1 NTLM header */
543    req->partno += 1001;
544    req->ntlm = TRUE; /* NTLM found */
545    logmsg("Received NTLM type-1, sending back data %ld", req->partno);
546  }
547  else if((req->partno >= 1000) &&
548          strstr(req->reqbuf, "Authorization: Basic")) {
549    /* If the client is passing this Basic-header and the part number is
550       already >=1000, we add 1 to the part number.  This allows simple Basic
551       authentication negotiation to work in the test suite. */
552    req->partno += 1;
553    logmsg("Received Basic request, sending back data %ld", req->partno);
554  }
555  if(strstr(req->reqbuf, "Connection: close"))
556    req->open = FALSE; /* close connection after this request */
557
558  if(!req->pipe &&
559     req->open &&
560     req->prot_version >= 11 &&
561     req->reqbuf + req->offset > end + strlen(END_OF_HEADERS) &&
562     (!strncmp(req->reqbuf, "GET", strlen("GET")) ||
563      !strncmp(req->reqbuf, "HEAD", strlen("HEAD")))) {
564    /* If we have a persistent connection, HTTP version >= 1.1
565       and GET/HEAD request, enable pipelining. */
566    req->checkindex = (end - req->reqbuf) + strlen(END_OF_HEADERS);
567    req->pipelining = TRUE;
568  }
569
570  while(req->pipe) {
571    if(got_exit_signal)
572      return 1; /* done */
573    /* scan for more header ends within this chunk */
574    line = &req->reqbuf[req->checkindex];
575    end = strstr(line, END_OF_HEADERS);
576    if(!end)
577      break;
578    req->checkindex += (end - line) + strlen(END_OF_HEADERS);
579    req->pipe--;
580  }
581
582  /* If authentication is required and no auth was provided, end now. This
583     makes the server NOT wait for PUT/POST data and you can then make the
584     test case send a rejection before any such data has been sent. Test case
585     154 uses this.*/
586  if(req->auth_req && !req->auth)
587    return 1; /* done */
588
589  if(req->cl > 0) {
590    if(req->cl <= req->offset - (end - req->reqbuf) - strlen(END_OF_HEADERS))
591      return 1; /* done */
592    else
593      return 0; /* not complete yet */
594  }
595
596  return 1; /* done */
597}
598
599/* store the entire request in a file */
600static void storerequest(char *reqbuf, size_t totalsize)
601{
602  int res;
603  int error = 0;
604  size_t written;
605  size_t writeleft;
606  FILE *dump;
607  char dumpfile[256];
608
609  msnprintf(dumpfile, sizeof(dumpfile), "%s/%s", logdir, REQUEST_DUMP);
610
611  if(!reqbuf)
612    return;
613  if(totalsize == 0)
614    return;
615
616  do {
617    dump = fopen(dumpfile, "ab");
618  } while(!dump && ((error = errno) == EINTR));
619  if(!dump) {
620    logmsg("Error opening file %s error: %d %s",
621           dumpfile, error, strerror(error));
622    logmsg("Failed to write request input to %s", dumpfile);
623    return;
624  }
625
626  writeleft = totalsize;
627  do {
628    written = fwrite(&reqbuf[totalsize-writeleft],
629                     1, writeleft, dump);
630    if(got_exit_signal)
631      goto storerequest_cleanup;
632    if(written > 0)
633      writeleft -= written;
634  } while((writeleft > 0) && ((error = errno) == EINTR));
635
636  if(writeleft == 0)
637    logmsg("Wrote request (%zu bytes) input to %s", totalsize, dumpfile);
638  else if(writeleft > 0) {
639    logmsg("Error writing file %s error: %d %s",
640           dumpfile, error, strerror(error));
641    logmsg("Wrote only (%zu bytes) of (%zu bytes) request input to %s",
642           totalsize-writeleft, totalsize, dumpfile);
643  }
644
645storerequest_cleanup:
646
647  do {
648    res = fclose(dump);
649  } while(res && ((error = errno) == EINTR));
650  if(res)
651    logmsg("Error closing file %s error: %d %s",
652           dumpfile, error, strerror(error));
653}
654
655/* return 0 on success, non-zero on failure */
656static int get_request(curl_socket_t sock, struct httprequest *req)
657{
658  int error;
659  int fail = 0;
660  int done_processing = 0;
661  char *reqbuf = req->reqbuf;
662  ssize_t got = 0;
663
664  char *pipereq = NULL;
665  size_t pipereq_length = 0;
666
667  if(req->pipelining) {
668    pipereq = reqbuf + req->checkindex;
669    pipereq_length = req->offset - req->checkindex;
670  }
671
672  /*** Init the httprequest structure properly for the upcoming request ***/
673
674  req->checkindex = 0;
675  req->offset = 0;
676  req->testno = DOCNUMBER_NOTHING;
677  req->partno = 0;
678  req->open = TRUE;
679  req->auth_req = FALSE;
680  req->auth = FALSE;
681  req->cl = 0;
682  req->digest = FALSE;
683  req->ntlm = FALSE;
684  req->pipe = 0;
685  req->skip = 0;
686  req->rcmd = RCMD_NORMALREQ;
687  req->protocol = RPROT_NONE;
688  req->prot_version = 0;
689  req->pipelining = FALSE;
690  req->rtp_buffer = NULL;
691  req->rtp_buffersize = 0;
692
693  /*** end of httprequest init ***/
694
695  while(!done_processing && (req->offset < REQBUFSIZ-1)) {
696    if(pipereq_length && pipereq) {
697      memmove(reqbuf, pipereq, pipereq_length);
698      got = curlx_uztosz(pipereq_length);
699      pipereq_length = 0;
700    }
701    else {
702      if(req->skip)
703        /* we are instructed to not read the entire thing, so we make sure to
704           only read what we're supposed to and NOT read the enire thing the
705           client wants to send! */
706        got = sread(sock, reqbuf + req->offset, req->cl);
707      else
708        got = sread(sock, reqbuf + req->offset, REQBUFSIZ-1 - req->offset);
709    }
710    if(got_exit_signal)
711      return 1;
712    if(got == 0) {
713      logmsg("Connection closed by client");
714      fail = 1;
715    }
716    else if(got < 0) {
717      error = SOCKERRNO;
718      logmsg("recv() returned error: (%d) %s", error, sstrerror(error));
719      fail = 1;
720    }
721    if(fail) {
722      /* dump the request received so far to the external file */
723      reqbuf[req->offset] = '\0';
724      storerequest(reqbuf, req->offset);
725      return 1;
726    }
727
728    logmsg("Read %zd bytes", got);
729
730    req->offset += (size_t)got;
731    reqbuf[req->offset] = '\0';
732
733    done_processing = ProcessRequest(req);
734    if(got_exit_signal)
735      return 1;
736    if(done_processing && req->pipe) {
737      logmsg("Waiting for another piped request");
738      done_processing = 0;
739      req->pipe--;
740    }
741  }
742
743  if((req->offset == REQBUFSIZ-1) && (got > 0)) {
744    logmsg("Request would overflow buffer, closing connection");
745    /* dump request received so far to external file anyway */
746    reqbuf[REQBUFSIZ-1] = '\0';
747    fail = 1;
748  }
749  else if(req->offset > REQBUFSIZ-1) {
750    logmsg("Request buffer overflow, closing connection");
751    /* dump request received so far to external file anyway */
752    reqbuf[REQBUFSIZ-1] = '\0';
753    fail = 1;
754  }
755  else
756    reqbuf[req->offset] = '\0';
757
758  /* dump the request to an external file */
759  storerequest(reqbuf, req->pipelining ? req->checkindex : req->offset);
760  if(got_exit_signal)
761    return 1;
762
763  return fail; /* return 0 on success */
764}
765
766/* returns -1 on failure */
767static int send_doc(curl_socket_t sock, struct httprequest *req)
768{
769  ssize_t written;
770  size_t count;
771  const char *buffer;
772  char *ptr = NULL;
773  char *cmd = NULL;
774  size_t cmdsize = 0;
775  FILE *dump;
776  bool persistent = TRUE;
777  bool sendfailure = FALSE;
778  size_t responsesize;
779  int error = 0;
780  int res;
781  static char weare[256];
782  char responsedump[256];
783
784  msnprintf(responsedump, sizeof(responsedump), "%s/%s",
785            logdir, RESPONSE_DUMP);
786
787  logmsg("Send response number %ld part %ld", req->testno, req->partno);
788
789  switch(req->rcmd) {
790  default:
791  case RCMD_NORMALREQ:
792    break; /* continue with business as usual */
793  case RCMD_STREAM:
794#define STREAMTHIS "a string to stream 01234567890\n"
795    count = strlen(STREAMTHIS);
796    for(;;) {
797      written = swrite(sock, STREAMTHIS, count);
798      if(got_exit_signal)
799        return -1;
800      if(written != (ssize_t)count) {
801        logmsg("Stopped streaming");
802        break;
803      }
804    }
805    return -1;
806  case RCMD_IDLE:
807    /* Do nothing. Sit idle. Pretend it rains. */
808    return 0;
809  }
810
811  req->open = FALSE;
812
813  if(req->testno < 0) {
814    size_t msglen;
815    char msgbuf[64];
816
817    switch(req->testno) {
818    case DOCNUMBER_QUIT:
819      logmsg("Replying to QUIT");
820      buffer = docquit;
821      break;
822    case DOCNUMBER_WERULEZ:
823      /* we got a "friends?" question, reply back that we sure are */
824      logmsg("Identifying ourselves as friends");
825      msnprintf(msgbuf, sizeof(msgbuf), "RTSP_SERVER WE ROOLZ: %"
826                CURL_FORMAT_CURL_OFF_T "\r\n", our_getpid());
827      msglen = strlen(msgbuf);
828      msnprintf(weare, sizeof(weare),
829                "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\n\r\n%s",
830                msglen, msgbuf);
831      buffer = weare;
832      break;
833    case DOCNUMBER_INTERNAL:
834      logmsg("Bailing out due to internal error");
835      return -1;
836    case DOCNUMBER_CONNECT:
837      logmsg("Replying to CONNECT");
838      buffer = docconnect;
839      break;
840    case DOCNUMBER_BADCONNECT:
841      logmsg("Replying to a bad CONNECT");
842      buffer = docbadconnect;
843      break;
844    case DOCNUMBER_404:
845    default:
846      logmsg("Replying to with a 404");
847      if(req->protocol == RPROT_HTTP) {
848        buffer = doc404_HTTP;
849      }
850      else {
851        buffer = doc404_RTSP;
852      }
853      break;
854    }
855
856    count = strlen(buffer);
857  }
858  else {
859    FILE *stream = test2fopen(req->testno, logdir);
860    char partbuf[80]="data";
861    if(0 != req->partno)
862      msnprintf(partbuf, sizeof(partbuf), "data%ld", req->partno);
863    if(!stream) {
864      error = errno;
865      logmsg("fopen() failed with error: %d %s", error, strerror(error));
866      logmsg("Couldn't open test file");
867      return 0;
868    }
869    else {
870      error = getpart(&ptr, &count, "reply", partbuf, stream);
871      fclose(stream);
872      if(error) {
873        logmsg("getpart() failed with error: %d", error);
874        return 0;
875      }
876      buffer = ptr;
877    }
878
879    if(got_exit_signal) {
880      free(ptr);
881      return -1;
882    }
883
884    /* re-open the same file again */
885    stream = test2fopen(req->testno, logdir);
886    if(!stream) {
887      error = errno;
888      logmsg("fopen() failed with error: %d %s", error, strerror(error));
889      logmsg("Couldn't open test file");
890      free(ptr);
891      return 0;
892    }
893    else {
894      /* get the custom server control "commands" */
895      error = getpart(&cmd, &cmdsize, "reply", "postcmd", stream);
896      fclose(stream);
897      if(error) {
898        logmsg("getpart() failed with error: %d", error);
899        free(ptr);
900        return 0;
901      }
902    }
903  }
904
905  if(got_exit_signal) {
906    free(ptr);
907    free(cmd);
908    return -1;
909  }
910
911  /* If the word 'swsclose' is present anywhere in the reply chunk, the
912     connection will be closed after the data has been sent to the requesting
913     client... */
914  if(strstr(buffer, "swsclose") || !count) {
915    persistent = FALSE;
916    logmsg("connection close instruction \"swsclose\" found in response");
917  }
918  if(strstr(buffer, "swsbounce")) {
919    prevbounce = TRUE;
920    logmsg("enable \"swsbounce\" in the next request");
921  }
922  else
923    prevbounce = FALSE;
924
925  dump = fopen(responsedump, "ab");
926  if(!dump) {
927    error = errno;
928    logmsg("fopen() failed with error: %d %s", error, strerror(error));
929    logmsg("Error opening file: %s", responsedump);
930    logmsg("couldn't create logfile: %s", responsedump);
931    free(ptr);
932    free(cmd);
933    return -1;
934  }
935
936  responsesize = count;
937  do {
938    /* Ok, we send no more than 200 bytes at a time, just to make sure that
939       larger chunks are split up so that the client will need to do multiple
940       recv() calls to get it and thus we exercise that code better */
941    size_t num = count;
942    if(num > 200)
943      num = 200;
944    written = swrite(sock, buffer, num);
945    if(written < 0) {
946      sendfailure = TRUE;
947      break;
948    }
949    else {
950      logmsg("Sent off %zd bytes", written);
951    }
952    /* write to file as well */
953    fwrite(buffer, 1, (size_t)written, dump);
954    if(got_exit_signal)
955      break;
956
957    count -= written;
958    buffer += written;
959  } while(count>0);
960
961  /* Send out any RTP data */
962  if(req->rtp_buffer) {
963    logmsg("About to write %zu RTP bytes", req->rtp_buffersize);
964    count = req->rtp_buffersize;
965    do {
966      size_t num = count;
967      if(num > 200)
968        num = 200;
969      written = swrite(sock, req->rtp_buffer + (req->rtp_buffersize - count),
970                       num);
971      if(written < 0) {
972        sendfailure = TRUE;
973        break;
974      }
975      count -= written;
976    } while(count > 0);
977
978    free(req->rtp_buffer);
979    req->rtp_buffersize = 0;
980  }
981
982  do {
983    res = fclose(dump);
984  } while(res && ((error = errno) == EINTR));
985  if(res)
986    logmsg("Error closing file %s error: %d %s",
987           responsedump, error, strerror(error));
988
989  if(got_exit_signal) {
990    free(ptr);
991    free(cmd);
992    return -1;
993  }
994
995  if(sendfailure) {
996    logmsg("Sending response failed. Only (%zu bytes) of "
997           "(%zu bytes) were sent",
998           responsesize-count, responsesize);
999    free(ptr);
1000    free(cmd);
1001    return -1;
1002  }
1003
1004  logmsg("Response sent (%zu bytes) and written to %s",
1005         responsesize, responsedump);
1006  free(ptr);
1007
1008  if(cmdsize > 0) {
1009    char command[32];
1010    int quarters;
1011    int num;
1012    ptr = cmd;
1013    do {
1014      if(2 == sscanf(ptr, "%31s %d", command, &num)) {
1015        if(!strcmp("wait", command)) {
1016          logmsg("Told to sleep for %d seconds", num);
1017          quarters = num * 4;
1018          while(quarters > 0) {
1019            quarters--;
1020            res = wait_ms(250);
1021            if(got_exit_signal)
1022              break;
1023            if(res) {
1024              /* should not happen */
1025              error = errno;
1026              logmsg("wait_ms() failed with error: (%d) %s",
1027                     error, strerror(error));
1028              break;
1029            }
1030          }
1031          if(!quarters)
1032            logmsg("Continuing after sleeping %d seconds", num);
1033        }
1034        else
1035          logmsg("Unknown command in reply command section");
1036      }
1037      ptr = strchr(ptr, '\n');
1038      if(ptr)
1039        ptr++;
1040      else
1041        ptr = NULL;
1042    } while(ptr && *ptr);
1043  }
1044  free(cmd);
1045  req->open = persistent;
1046
1047  prevtestno = req->testno;
1048  prevpartno = req->partno;
1049
1050  return 0;
1051}
1052
1053
1054int main(int argc, char *argv[])
1055{
1056  srvr_sockaddr_union_t me;
1057  curl_socket_t sock = CURL_SOCKET_BAD;
1058  curl_socket_t msgsock = CURL_SOCKET_BAD;
1059  int wrotepidfile = 0;
1060  int wroteportfile = 0;
1061  int flag;
1062  unsigned short port = DEFAULT_PORT;
1063  const char *pidname = ".rtsp.pid";
1064  const char *portname = NULL; /* none by default */
1065  struct httprequest req;
1066  int rc;
1067  int error;
1068  int arg = 1;
1069
1070  memset(&req, 0, sizeof(req));
1071
1072  while(argc>arg) {
1073    if(!strcmp("--version", argv[arg])) {
1074      printf("rtspd IPv4%s"
1075             "\n"
1076             ,
1077#ifdef ENABLE_IPV6
1078             "/IPv6"
1079#else
1080             ""
1081#endif
1082             );
1083      return 0;
1084    }
1085    else if(!strcmp("--pidfile", argv[arg])) {
1086      arg++;
1087      if(argc>arg)
1088        pidname = argv[arg++];
1089    }
1090    else if(!strcmp("--portfile", argv[arg])) {
1091      arg++;
1092      if(argc>arg)
1093        portname = argv[arg++];
1094    }
1095    else if(!strcmp("--logfile", argv[arg])) {
1096      arg++;
1097      if(argc>arg)
1098        serverlogfile = argv[arg++];
1099    }
1100    else if(!strcmp("--logdir", argv[arg])) {
1101      arg++;
1102      if(argc>arg)
1103        logdir = argv[arg++];
1104    }
1105    else if(!strcmp("--ipv4", argv[arg])) {
1106#ifdef ENABLE_IPV6
1107      ipv_inuse = "IPv4";
1108      use_ipv6 = FALSE;
1109#endif
1110      arg++;
1111    }
1112    else if(!strcmp("--ipv6", argv[arg])) {
1113#ifdef ENABLE_IPV6
1114      ipv_inuse = "IPv6";
1115      use_ipv6 = TRUE;
1116#endif
1117      arg++;
1118    }
1119    else if(!strcmp("--port", argv[arg])) {
1120      arg++;
1121      if(argc>arg) {
1122        char *endptr;
1123        unsigned long ulnum = strtoul(argv[arg], &endptr, 10);
1124        port = curlx_ultous(ulnum);
1125        arg++;
1126      }
1127    }
1128    else if(!strcmp("--srcdir", argv[arg])) {
1129      arg++;
1130      if(argc>arg) {
1131        path = argv[arg];
1132        arg++;
1133      }
1134    }
1135    else {
1136      puts("Usage: rtspd [option]\n"
1137           " --version\n"
1138           " --logfile [file]\n"
1139           " --logdir [directory]\n"
1140           " --pidfile [file]\n"
1141           " --portfile [file]\n"
1142           " --ipv4\n"
1143           " --ipv6\n"
1144           " --port [port]\n"
1145           " --srcdir [path]");
1146      return 0;
1147    }
1148  }
1149
1150  msnprintf(loglockfile, sizeof(loglockfile), "%s/%s/rtsp-%s.lock",
1151            logdir, SERVERLOGS_LOCKDIR, ipv_inuse);
1152
1153#ifdef _WIN32
1154  win32_init();
1155  atexit(win32_cleanup);
1156#endif
1157
1158  install_signal_handlers(false);
1159
1160#ifdef ENABLE_IPV6
1161  if(!use_ipv6)
1162#endif
1163    sock = socket(AF_INET, SOCK_STREAM, 0);
1164#ifdef ENABLE_IPV6
1165  else
1166    sock = socket(AF_INET6, SOCK_STREAM, 0);
1167#endif
1168
1169  if(CURL_SOCKET_BAD == sock) {
1170    error = SOCKERRNO;
1171    logmsg("Error creating socket: (%d) %s", error, sstrerror(error));
1172    goto server_cleanup;
1173  }
1174
1175  flag = 1;
1176  if(0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
1177            (void *)&flag, sizeof(flag))) {
1178    error = SOCKERRNO;
1179    logmsg("setsockopt(SO_REUSEADDR) failed with error: (%d) %s",
1180           error, sstrerror(error));
1181    goto server_cleanup;
1182  }
1183
1184#ifdef ENABLE_IPV6
1185  if(!use_ipv6) {
1186#endif
1187    memset(&me.sa4, 0, sizeof(me.sa4));
1188    me.sa4.sin_family = AF_INET;
1189    me.sa4.sin_addr.s_addr = INADDR_ANY;
1190    me.sa4.sin_port = htons(port);
1191    rc = bind(sock, &me.sa, sizeof(me.sa4));
1192#ifdef ENABLE_IPV6
1193  }
1194  else {
1195    memset(&me.sa6, 0, sizeof(me.sa6));
1196    me.sa6.sin6_family = AF_INET6;
1197    me.sa6.sin6_addr = in6addr_any;
1198    me.sa6.sin6_port = htons(port);
1199    rc = bind(sock, &me.sa, sizeof(me.sa6));
1200  }
1201#endif /* ENABLE_IPV6 */
1202  if(0 != rc) {
1203    error = SOCKERRNO;
1204    logmsg("Error binding socket on port %hu: (%d) %s",
1205           port, error, sstrerror(error));
1206    goto server_cleanup;
1207  }
1208
1209  if(!port) {
1210    /* The system was supposed to choose a port number, figure out which
1211       port we actually got and update the listener port value with it. */
1212    curl_socklen_t la_size;
1213    srvr_sockaddr_union_t localaddr;
1214#ifdef ENABLE_IPV6
1215    if(!use_ipv6)
1216#endif
1217      la_size = sizeof(localaddr.sa4);
1218#ifdef ENABLE_IPV6
1219    else
1220      la_size = sizeof(localaddr.sa6);
1221#endif
1222    memset(&localaddr.sa, 0, (size_t)la_size);
1223    if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
1224      error = SOCKERRNO;
1225      logmsg("getsockname() failed with error: (%d) %s",
1226             error, sstrerror(error));
1227      sclose(sock);
1228      goto server_cleanup;
1229    }
1230    switch(localaddr.sa.sa_family) {
1231    case AF_INET:
1232      port = ntohs(localaddr.sa4.sin_port);
1233      break;
1234#ifdef ENABLE_IPV6
1235    case AF_INET6:
1236      port = ntohs(localaddr.sa6.sin6_port);
1237      break;
1238#endif
1239    default:
1240      break;
1241    }
1242    if(!port) {
1243      /* Real failure, listener port shall not be zero beyond this point. */
1244      logmsg("Apparently getsockname() succeeded, with listener port zero.");
1245      logmsg("A valid reason for this failure is a binary built without");
1246      logmsg("proper network library linkage. This might not be the only");
1247      logmsg("reason, but double check it before anything else.");
1248      sclose(sock);
1249      goto server_cleanup;
1250    }
1251  }
1252  logmsg("Running %s version on port %d", ipv_inuse, (int)port);
1253
1254  /* start accepting connections */
1255  rc = listen(sock, 5);
1256  if(0 != rc) {
1257    error = SOCKERRNO;
1258    logmsg("listen() failed with error: (%d) %s",
1259           error, sstrerror(error));
1260    goto server_cleanup;
1261  }
1262
1263  /*
1264  ** As soon as this server writes its pid file the test harness will
1265  ** attempt to connect to this server and initiate its verification.
1266  */
1267
1268  wrotepidfile = write_pidfile(pidname);
1269  if(!wrotepidfile)
1270    goto server_cleanup;
1271
1272  if(portname) {
1273    wroteportfile = write_portfile(portname, port);
1274    if(!wroteportfile)
1275      goto server_cleanup;
1276  }
1277
1278  for(;;) {
1279    msgsock = accept(sock, NULL, NULL);
1280
1281    if(got_exit_signal)
1282      break;
1283    if(CURL_SOCKET_BAD == msgsock) {
1284      error = SOCKERRNO;
1285      logmsg("MAJOR ERROR: accept() failed with error: (%d) %s",
1286             error, sstrerror(error));
1287      break;
1288    }
1289
1290    /*
1291    ** As soon as this server accepts a connection from the test harness it
1292    ** must set the server logs advisor read lock to indicate that server
1293    ** logs should not be read until this lock is removed by this server.
1294    */
1295
1296    set_advisor_read_lock(loglockfile);
1297    serverlogslocked = 1;
1298
1299    logmsg("====> Client connect");
1300
1301#ifdef TCP_NODELAY
1302    /*
1303     * Disable the Nagle algorithm to make it easier to send out a large
1304     * response in many small segments to torture the clients more.
1305     */
1306    flag = 1;
1307    if(setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY,
1308                   (void *)&flag, sizeof(flag)) == -1) {
1309      logmsg("====> TCP_NODELAY failed");
1310    }
1311#endif
1312
1313    /* initialization of httprequest struct is done in get_request(), but due
1314       to pipelining treatment the pipelining struct field must be initialized
1315       previously to FALSE every time a new connection arrives. */
1316
1317    req.pipelining = FALSE;
1318
1319    do {
1320      if(got_exit_signal)
1321        break;
1322
1323      if(get_request(msgsock, &req))
1324        /* non-zero means error, break out of loop */
1325        break;
1326
1327      if(prevbounce) {
1328        /* bounce treatment requested */
1329        if((req.testno == prevtestno) &&
1330           (req.partno == prevpartno)) {
1331          req.partno++;
1332          logmsg("BOUNCE part number to %ld", req.partno);
1333        }
1334        else {
1335          prevbounce = FALSE;
1336          prevtestno = -1;
1337          prevpartno = -1;
1338        }
1339      }
1340
1341      send_doc(msgsock, &req);
1342      if(got_exit_signal)
1343        break;
1344
1345      if((req.testno < 0) && (req.testno != DOCNUMBER_CONNECT)) {
1346        logmsg("special request received, no persistency");
1347        break;
1348      }
1349      if(!req.open) {
1350        logmsg("instructed to close connection after server-reply");
1351        break;
1352      }
1353
1354      if(req.open)
1355        logmsg("=> persistent connection request ended, awaits new request");
1356      /* if we got a CONNECT, loop and get another request as well! */
1357    } while(req.open || (req.testno == DOCNUMBER_CONNECT));
1358
1359    if(got_exit_signal)
1360      break;
1361
1362    logmsg("====> Client disconnect");
1363    sclose(msgsock);
1364    msgsock = CURL_SOCKET_BAD;
1365
1366    if(serverlogslocked) {
1367      serverlogslocked = 0;
1368      clear_advisor_read_lock(loglockfile);
1369    }
1370
1371    if(req.testno == DOCNUMBER_QUIT)
1372      break;
1373  }
1374
1375server_cleanup:
1376
1377  if((msgsock != sock) && (msgsock != CURL_SOCKET_BAD))
1378    sclose(msgsock);
1379
1380  if(sock != CURL_SOCKET_BAD)
1381    sclose(sock);
1382
1383  if(got_exit_signal)
1384    logmsg("signalled to die");
1385
1386  if(wrotepidfile)
1387    unlink(pidname);
1388  if(wroteportfile)
1389    unlink(portname);
1390
1391  if(serverlogslocked) {
1392    serverlogslocked = 0;
1393    clear_advisor_read_lock(loglockfile);
1394  }
1395
1396  restore_signal_handlers(false);
1397
1398  if(got_exit_signal) {
1399    logmsg("========> %s rtspd (port: %d pid: %ld) exits with signal (%d)",
1400           ipv_inuse, (int)port, (long)getpid(), exit_signal);
1401    /*
1402     * To properly set the return status of the process we
1403     * must raise the same signal SIGINT or SIGTERM that we
1404     * caught and let the old handler take care of it.
1405     */
1406    raise(exit_signal);
1407  }
1408
1409  logmsg("========> rtspd quits");
1410  return 0;
1411}
1412