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