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