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