• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 override the
76                                    requested part number to prevpartno + 1 when
77                                    prevtestno and current test are the same */
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   res = fclose(dump);
645   if(res)
646     logmsg("Error closing file %s error: %d %s",
647            dumpfile, errno, strerror(errno));
648 }
649 
650 /* return 0 on success, non-zero on failure */
get_request(curl_socket_t sock,struct httprequest * req)651 static int get_request(curl_socket_t sock, struct httprequest *req)
652 {
653   int error;
654   int fail = 0;
655   int done_processing = 0;
656   char *reqbuf = req->reqbuf;
657   ssize_t got = 0;
658 
659   char *pipereq = NULL;
660   size_t pipereq_length = 0;
661 
662   if(req->pipelining) {
663     pipereq = reqbuf + req->checkindex;
664     pipereq_length = req->offset - req->checkindex;
665   }
666 
667   /*** Init the httprequest structure properly for the upcoming request ***/
668 
669   req->checkindex = 0;
670   req->offset = 0;
671   req->testno = DOCNUMBER_NOTHING;
672   req->partno = 0;
673   req->open = TRUE;
674   req->auth_req = FALSE;
675   req->auth = FALSE;
676   req->cl = 0;
677   req->digest = FALSE;
678   req->ntlm = FALSE;
679   req->pipe = 0;
680   req->skip = 0;
681   req->rcmd = RCMD_NORMALREQ;
682   req->protocol = RPROT_NONE;
683   req->prot_version = 0;
684   req->pipelining = FALSE;
685   req->rtp_buffer = NULL;
686   req->rtp_buffersize = 0;
687 
688   /*** end of httprequest init ***/
689 
690   while(!done_processing && (req->offset < REQBUFSIZ-1)) {
691     if(pipereq_length && pipereq) {
692       memmove(reqbuf, pipereq, pipereq_length);
693       got = curlx_uztosz(pipereq_length);
694       pipereq_length = 0;
695     }
696     else {
697       if(req->skip)
698         /* we are instructed to not read the entire thing, so we make sure to
699            only read what we're supposed to and NOT read the enire thing the
700            client wants to send! */
701         got = sread(sock, reqbuf + req->offset, req->cl);
702       else
703         got = sread(sock, reqbuf + req->offset, REQBUFSIZ-1 - req->offset);
704     }
705     if(got_exit_signal)
706       return 1;
707     if(got == 0) {
708       logmsg("Connection closed by client");
709       fail = 1;
710     }
711     else if(got < 0) {
712       error = SOCKERRNO;
713       logmsg("recv() returned error: (%d) %s", error, sstrerror(error));
714       fail = 1;
715     }
716     if(fail) {
717       /* dump the request received so far to the external file */
718       reqbuf[req->offset] = '\0';
719       storerequest(reqbuf, req->offset);
720       return 1;
721     }
722 
723     logmsg("Read %zd bytes", got);
724 
725     req->offset += (size_t)got;
726     reqbuf[req->offset] = '\0';
727 
728     done_processing = ProcessRequest(req);
729     if(got_exit_signal)
730       return 1;
731     if(done_processing && req->pipe) {
732       logmsg("Waiting for another piped request");
733       done_processing = 0;
734       req->pipe--;
735     }
736   }
737 
738   if((req->offset == REQBUFSIZ-1) && (got > 0)) {
739     logmsg("Request would overflow buffer, closing connection");
740     /* dump request received so far to external file anyway */
741     reqbuf[REQBUFSIZ-1] = '\0';
742     fail = 1;
743   }
744   else if(req->offset > REQBUFSIZ-1) {
745     logmsg("Request buffer overflow, closing connection");
746     /* dump request received so far to external file anyway */
747     reqbuf[REQBUFSIZ-1] = '\0';
748     fail = 1;
749   }
750   else
751     reqbuf[req->offset] = '\0';
752 
753   /* dump the request to an external file */
754   storerequest(reqbuf, req->pipelining ? req->checkindex : req->offset);
755   if(got_exit_signal)
756     return 1;
757 
758   return fail; /* return 0 on success */
759 }
760 
761 /* returns -1 on failure */
send_doc(curl_socket_t sock,struct httprequest * req)762 static int send_doc(curl_socket_t sock, struct httprequest *req)
763 {
764   ssize_t written;
765   size_t count;
766   const char *buffer;
767   char *ptr = NULL;
768   char *cmd = NULL;
769   size_t cmdsize = 0;
770   FILE *dump;
771   bool persistent = TRUE;
772   bool sendfailure = FALSE;
773   size_t responsesize;
774   int error = 0;
775   int res;
776   static char weare[256];
777   char responsedump[256];
778 
779   msnprintf(responsedump, sizeof(responsedump), "%s/%s",
780             logdir, RESPONSE_DUMP);
781 
782   logmsg("Send response number %ld part %ld", req->testno, req->partno);
783 
784   switch(req->rcmd) {
785   default:
786   case RCMD_NORMALREQ:
787     break; /* continue with business as usual */
788   case RCMD_STREAM:
789 #define STREAMTHIS "a string to stream 01234567890\n"
790     count = strlen(STREAMTHIS);
791     for(;;) {
792       written = swrite(sock, STREAMTHIS, count);
793       if(got_exit_signal)
794         return -1;
795       if(written != (ssize_t)count) {
796         logmsg("Stopped streaming");
797         break;
798       }
799     }
800     return -1;
801   case RCMD_IDLE:
802     /* Do nothing. Sit idle. Pretend it rains. */
803     return 0;
804   }
805 
806   req->open = FALSE;
807 
808   if(req->testno < 0) {
809     size_t msglen;
810     char msgbuf[64];
811 
812     switch(req->testno) {
813     case DOCNUMBER_QUIT:
814       logmsg("Replying to QUIT");
815       buffer = docquit;
816       break;
817     case DOCNUMBER_WERULEZ:
818       /* we got a "friends?" question, reply back that we sure are */
819       logmsg("Identifying ourselves as friends");
820       msnprintf(msgbuf, sizeof(msgbuf), "RTSP_SERVER WE ROOLZ: %"
821                 CURL_FORMAT_CURL_OFF_T "\r\n", our_getpid());
822       msglen = strlen(msgbuf);
823       msnprintf(weare, sizeof(weare),
824                 "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\n\r\n%s",
825                 msglen, msgbuf);
826       buffer = weare;
827       break;
828     case DOCNUMBER_INTERNAL:
829       logmsg("Bailing out due to internal error");
830       return -1;
831     case DOCNUMBER_CONNECT:
832       logmsg("Replying to CONNECT");
833       buffer = docconnect;
834       break;
835     case DOCNUMBER_BADCONNECT:
836       logmsg("Replying to a bad CONNECT");
837       buffer = docbadconnect;
838       break;
839     case DOCNUMBER_404:
840     default:
841       logmsg("Replying to with a 404");
842       if(req->protocol == RPROT_HTTP) {
843         buffer = doc404_HTTP;
844       }
845       else {
846         buffer = doc404_RTSP;
847       }
848       break;
849     }
850 
851     count = strlen(buffer);
852   }
853   else {
854     FILE *stream = test2fopen(req->testno, logdir);
855     char partbuf[80]="data";
856     if(0 != req->partno)
857       msnprintf(partbuf, sizeof(partbuf), "data%ld", req->partno);
858     if(!stream) {
859       error = errno;
860       logmsg("fopen() failed with error: %d %s", error, strerror(error));
861       logmsg("Couldn't open test file");
862       return 0;
863     }
864     else {
865       error = getpart(&ptr, &count, "reply", partbuf, stream);
866       fclose(stream);
867       if(error) {
868         logmsg("getpart() failed with error: %d", error);
869         return 0;
870       }
871       buffer = ptr;
872     }
873 
874     if(got_exit_signal) {
875       free(ptr);
876       return -1;
877     }
878 
879     /* re-open the same file again */
880     stream = test2fopen(req->testno, logdir);
881     if(!stream) {
882       error = errno;
883       logmsg("fopen() failed with error: %d %s", error, strerror(error));
884       logmsg("Couldn't open test file");
885       free(ptr);
886       return 0;
887     }
888     else {
889       /* get the custom server control "commands" */
890       error = getpart(&cmd, &cmdsize, "reply", "postcmd", stream);
891       fclose(stream);
892       if(error) {
893         logmsg("getpart() failed with error: %d", error);
894         free(ptr);
895         return 0;
896       }
897     }
898   }
899 
900   if(got_exit_signal) {
901     free(ptr);
902     free(cmd);
903     return -1;
904   }
905 
906   /* If the word 'swsclose' is present anywhere in the reply chunk, the
907      connection will be closed after the data has been sent to the requesting
908      client... */
909   if(strstr(buffer, "swsclose") || !count) {
910     persistent = FALSE;
911     logmsg("connection close instruction \"swsclose\" found in response");
912   }
913   if(strstr(buffer, "swsbounce")) {
914     prevbounce = TRUE;
915     logmsg("enable \"swsbounce\" in the next request");
916   }
917   else
918     prevbounce = FALSE;
919 
920   dump = fopen(responsedump, "ab");
921   if(!dump) {
922     error = errno;
923     logmsg("fopen() failed with error: %d %s", error, strerror(error));
924     logmsg("Error opening file: %s", responsedump);
925     logmsg("couldn't create logfile: %s", responsedump);
926     free(ptr);
927     free(cmd);
928     return -1;
929   }
930 
931   responsesize = count;
932   do {
933     /* Ok, we send no more than 200 bytes at a time, just to make sure that
934        larger chunks are split up so that the client will need to do multiple
935        recv() calls to get it and thus we exercise that code better */
936     size_t num = count;
937     if(num > 200)
938       num = 200;
939     written = swrite(sock, buffer, num);
940     if(written < 0) {
941       sendfailure = TRUE;
942       break;
943     }
944     else {
945       logmsg("Sent off %zd bytes", written);
946     }
947     /* write to file as well */
948     fwrite(buffer, 1, (size_t)written, dump);
949     if(got_exit_signal)
950       break;
951 
952     count -= written;
953     buffer += written;
954   } while(count > 0);
955 
956   /* Send out any RTP data */
957   if(req->rtp_buffer) {
958     logmsg("About to write %zu RTP bytes", req->rtp_buffersize);
959     count = req->rtp_buffersize;
960     do {
961       size_t num = count;
962       if(num > 200)
963         num = 200;
964       written = swrite(sock, req->rtp_buffer + (req->rtp_buffersize - count),
965                        num);
966       if(written < 0) {
967         sendfailure = TRUE;
968         break;
969       }
970       count -= written;
971     } while(count > 0);
972 
973     free(req->rtp_buffer);
974     req->rtp_buffersize = 0;
975   }
976 
977   res = fclose(dump);
978   if(res)
979     logmsg("Error closing file %s error: %d %s",
980            responsedump, errno, strerror(errno));
981 
982   if(got_exit_signal) {
983     free(ptr);
984     free(cmd);
985     return -1;
986   }
987 
988   if(sendfailure) {
989     logmsg("Sending response failed. Only (%zu bytes) of "
990            "(%zu bytes) were sent",
991            responsesize-count, responsesize);
992     free(ptr);
993     free(cmd);
994     return -1;
995   }
996 
997   logmsg("Response sent (%zu bytes) and written to %s",
998          responsesize, responsedump);
999   free(ptr);
1000 
1001   if(cmdsize > 0) {
1002     char command[32];
1003     int quarters;
1004     int num;
1005     ptr = cmd;
1006     do {
1007       if(2 == sscanf(ptr, "%31s %d", command, &num)) {
1008         if(!strcmp("wait", command)) {
1009           logmsg("Told to sleep for %d seconds", num);
1010           quarters = num * 4;
1011           while(quarters > 0) {
1012             quarters--;
1013             res = wait_ms(250);
1014             if(got_exit_signal)
1015               break;
1016             if(res) {
1017               /* should not happen */
1018               error = errno;
1019               logmsg("wait_ms() failed with error: (%d) %s",
1020                      error, strerror(error));
1021               break;
1022             }
1023           }
1024           if(!quarters)
1025             logmsg("Continuing after sleeping %d seconds", num);
1026         }
1027         else
1028           logmsg("Unknown command in reply command section");
1029       }
1030       ptr = strchr(ptr, '\n');
1031       if(ptr)
1032         ptr++;
1033       else
1034         ptr = NULL;
1035     } while(ptr && *ptr);
1036   }
1037   free(cmd);
1038   req->open = persistent;
1039 
1040   prevtestno = req->testno;
1041   prevpartno = req->partno;
1042 
1043   return 0;
1044 }
1045 
1046 
main(int argc,char * argv[])1047 int main(int argc, char *argv[])
1048 {
1049   srvr_sockaddr_union_t me;
1050   curl_socket_t sock = CURL_SOCKET_BAD;
1051   curl_socket_t msgsock = CURL_SOCKET_BAD;
1052   int wrotepidfile = 0;
1053   int wroteportfile = 0;
1054   int flag;
1055   unsigned short port = DEFAULT_PORT;
1056   const char *pidname = ".rtsp.pid";
1057   const char *portname = NULL; /* none by default */
1058   struct httprequest req;
1059   int rc;
1060   int error;
1061   int arg = 1;
1062 
1063   memset(&req, 0, sizeof(req));
1064 
1065   while(argc > arg) {
1066     if(!strcmp("--version", argv[arg])) {
1067       printf("rtspd IPv4%s"
1068              "\n"
1069              ,
1070 #ifdef USE_IPV6
1071              "/IPv6"
1072 #else
1073              ""
1074 #endif
1075              );
1076       return 0;
1077     }
1078     else if(!strcmp("--pidfile", argv[arg])) {
1079       arg++;
1080       if(argc > arg)
1081         pidname = argv[arg++];
1082     }
1083     else if(!strcmp("--portfile", argv[arg])) {
1084       arg++;
1085       if(argc > arg)
1086         portname = argv[arg++];
1087     }
1088     else if(!strcmp("--logfile", argv[arg])) {
1089       arg++;
1090       if(argc > arg)
1091         serverlogfile = argv[arg++];
1092     }
1093     else if(!strcmp("--logdir", argv[arg])) {
1094       arg++;
1095       if(argc > arg)
1096         logdir = argv[arg++];
1097     }
1098     else if(!strcmp("--ipv4", argv[arg])) {
1099 #ifdef USE_IPV6
1100       ipv_inuse = "IPv4";
1101       use_ipv6 = FALSE;
1102 #endif
1103       arg++;
1104     }
1105     else if(!strcmp("--ipv6", argv[arg])) {
1106 #ifdef USE_IPV6
1107       ipv_inuse = "IPv6";
1108       use_ipv6 = TRUE;
1109 #endif
1110       arg++;
1111     }
1112     else if(!strcmp("--port", argv[arg])) {
1113       arg++;
1114       if(argc > arg) {
1115         char *endptr;
1116         unsigned long ulnum = strtoul(argv[arg], &endptr, 10);
1117         port = curlx_ultous(ulnum);
1118         arg++;
1119       }
1120     }
1121     else if(!strcmp("--srcdir", argv[arg])) {
1122       arg++;
1123       if(argc > arg) {
1124         path = argv[arg];
1125         arg++;
1126       }
1127     }
1128     else {
1129       puts("Usage: rtspd [option]\n"
1130            " --version\n"
1131            " --logfile [file]\n"
1132            " --logdir [directory]\n"
1133            " --pidfile [file]\n"
1134            " --portfile [file]\n"
1135            " --ipv4\n"
1136            " --ipv6\n"
1137            " --port [port]\n"
1138            " --srcdir [path]");
1139       return 0;
1140     }
1141   }
1142 
1143   msnprintf(loglockfile, sizeof(loglockfile), "%s/%s/rtsp-%s.lock",
1144             logdir, SERVERLOGS_LOCKDIR, ipv_inuse);
1145 
1146 #ifdef _WIN32
1147   win32_init();
1148   atexit(win32_cleanup);
1149 #endif
1150 
1151   install_signal_handlers(false);
1152 
1153 #ifdef USE_IPV6
1154   if(!use_ipv6)
1155 #endif
1156     sock = socket(AF_INET, SOCK_STREAM, 0);
1157 #ifdef USE_IPV6
1158   else
1159     sock = socket(AF_INET6, SOCK_STREAM, 0);
1160 #endif
1161 
1162   if(CURL_SOCKET_BAD == sock) {
1163     error = SOCKERRNO;
1164     logmsg("Error creating socket: (%d) %s", error, sstrerror(error));
1165     goto server_cleanup;
1166   }
1167 
1168   flag = 1;
1169   if(0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
1170             (void *)&flag, sizeof(flag))) {
1171     error = SOCKERRNO;
1172     logmsg("setsockopt(SO_REUSEADDR) failed with error: (%d) %s",
1173            error, sstrerror(error));
1174     goto server_cleanup;
1175   }
1176 
1177 #ifdef USE_IPV6
1178   if(!use_ipv6) {
1179 #endif
1180     memset(&me.sa4, 0, sizeof(me.sa4));
1181     me.sa4.sin_family = AF_INET;
1182     me.sa4.sin_addr.s_addr = INADDR_ANY;
1183     me.sa4.sin_port = htons(port);
1184     rc = bind(sock, &me.sa, sizeof(me.sa4));
1185 #ifdef USE_IPV6
1186   }
1187   else {
1188     memset(&me.sa6, 0, sizeof(me.sa6));
1189     me.sa6.sin6_family = AF_INET6;
1190     me.sa6.sin6_addr = in6addr_any;
1191     me.sa6.sin6_port = htons(port);
1192     rc = bind(sock, &me.sa, sizeof(me.sa6));
1193   }
1194 #endif /* USE_IPV6 */
1195   if(0 != rc) {
1196     error = SOCKERRNO;
1197     logmsg("Error binding socket on port %hu: (%d) %s",
1198            port, error, sstrerror(error));
1199     goto server_cleanup;
1200   }
1201 
1202   if(!port) {
1203     /* The system was supposed to choose a port number, figure out which
1204        port we actually got and update the listener port value with it. */
1205     curl_socklen_t la_size;
1206     srvr_sockaddr_union_t localaddr;
1207 #ifdef USE_IPV6
1208     if(!use_ipv6)
1209 #endif
1210       la_size = sizeof(localaddr.sa4);
1211 #ifdef USE_IPV6
1212     else
1213       la_size = sizeof(localaddr.sa6);
1214 #endif
1215     memset(&localaddr.sa, 0, (size_t)la_size);
1216     if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
1217       error = SOCKERRNO;
1218       logmsg("getsockname() failed with error: (%d) %s",
1219              error, sstrerror(error));
1220       sclose(sock);
1221       goto server_cleanup;
1222     }
1223     switch(localaddr.sa.sa_family) {
1224     case AF_INET:
1225       port = ntohs(localaddr.sa4.sin_port);
1226       break;
1227 #ifdef USE_IPV6
1228     case AF_INET6:
1229       port = ntohs(localaddr.sa6.sin6_port);
1230       break;
1231 #endif
1232     default:
1233       break;
1234     }
1235     if(!port) {
1236       /* Real failure, listener port shall not be zero beyond this point. */
1237       logmsg("Apparently getsockname() succeeded, with listener port zero.");
1238       logmsg("A valid reason for this failure is a binary built without");
1239       logmsg("proper network library linkage. This might not be the only");
1240       logmsg("reason, but double check it before anything else.");
1241       sclose(sock);
1242       goto server_cleanup;
1243     }
1244   }
1245   logmsg("Running %s version on port %d", ipv_inuse, (int)port);
1246 
1247   /* start accepting connections */
1248   rc = listen(sock, 5);
1249   if(0 != rc) {
1250     error = SOCKERRNO;
1251     logmsg("listen() failed with error: (%d) %s",
1252            error, sstrerror(error));
1253     goto server_cleanup;
1254   }
1255 
1256   /*
1257   ** As soon as this server writes its pid file the test harness will
1258   ** attempt to connect to this server and initiate its verification.
1259   */
1260 
1261   wrotepidfile = write_pidfile(pidname);
1262   if(!wrotepidfile)
1263     goto server_cleanup;
1264 
1265   if(portname) {
1266     wroteportfile = write_portfile(portname, port);
1267     if(!wroteportfile)
1268       goto server_cleanup;
1269   }
1270 
1271   for(;;) {
1272     msgsock = accept(sock, NULL, NULL);
1273 
1274     if(got_exit_signal)
1275       break;
1276     if(CURL_SOCKET_BAD == msgsock) {
1277       error = SOCKERRNO;
1278       logmsg("MAJOR ERROR: accept() failed with error: (%d) %s",
1279              error, sstrerror(error));
1280       break;
1281     }
1282 
1283     /*
1284     ** As soon as this server accepts a connection from the test harness it
1285     ** must set the server logs advisor read lock to indicate that server
1286     ** logs should not be read until this lock is removed by this server.
1287     */
1288 
1289     set_advisor_read_lock(loglockfile);
1290     serverlogslocked = 1;
1291 
1292     logmsg("====> Client connect");
1293 
1294 #ifdef TCP_NODELAY
1295     /*
1296      * Disable the Nagle algorithm to make it easier to send out a large
1297      * response in many small segments to torture the clients more.
1298      */
1299     flag = 1;
1300     if(setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY,
1301                    (void *)&flag, sizeof(flag)) == -1) {
1302       logmsg("====> TCP_NODELAY failed");
1303     }
1304 #endif
1305 
1306     /* initialization of httprequest struct is done in get_request(), but due
1307        to pipelining treatment the pipelining struct field must be initialized
1308        previously to FALSE every time a new connection arrives. */
1309 
1310     req.pipelining = FALSE;
1311 
1312     do {
1313       if(got_exit_signal)
1314         break;
1315 
1316       if(get_request(msgsock, &req))
1317         /* non-zero means error, break out of loop */
1318         break;
1319 
1320       if(prevbounce) {
1321         /* bounce treatment requested */
1322         if(req.testno == prevtestno) {
1323           req.partno = prevpartno + 1;
1324           logmsg("BOUNCE part number to %ld", req.partno);
1325         }
1326         else {
1327           prevbounce = FALSE;
1328           prevtestno = -1;
1329           prevpartno = -1;
1330         }
1331       }
1332 
1333       send_doc(msgsock, &req);
1334       if(got_exit_signal)
1335         break;
1336 
1337       if((req.testno < 0) && (req.testno != DOCNUMBER_CONNECT)) {
1338         logmsg("special request received, no persistency");
1339         break;
1340       }
1341       if(!req.open) {
1342         logmsg("instructed to close connection after server-reply");
1343         break;
1344       }
1345 
1346       if(req.open)
1347         logmsg("=> persistent connection request ended, awaits new request");
1348       /* if we got a CONNECT, loop and get another request as well! */
1349     } while(req.open || (req.testno == DOCNUMBER_CONNECT));
1350 
1351     if(got_exit_signal)
1352       break;
1353 
1354     logmsg("====> Client disconnect");
1355     sclose(msgsock);
1356     msgsock = CURL_SOCKET_BAD;
1357 
1358     if(serverlogslocked) {
1359       serverlogslocked = 0;
1360       clear_advisor_read_lock(loglockfile);
1361     }
1362 
1363     if(req.testno == DOCNUMBER_QUIT)
1364       break;
1365   }
1366 
1367 server_cleanup:
1368 
1369   if((msgsock != sock) && (msgsock != CURL_SOCKET_BAD))
1370     sclose(msgsock);
1371 
1372   if(sock != CURL_SOCKET_BAD)
1373     sclose(sock);
1374 
1375   if(got_exit_signal)
1376     logmsg("signalled to die");
1377 
1378   if(wrotepidfile)
1379     unlink(pidname);
1380   if(wroteportfile)
1381     unlink(portname);
1382 
1383   if(serverlogslocked) {
1384     serverlogslocked = 0;
1385     clear_advisor_read_lock(loglockfile);
1386   }
1387 
1388   restore_signal_handlers(false);
1389 
1390   if(got_exit_signal) {
1391     logmsg("========> %s rtspd (port: %d pid: %ld) exits with signal (%d)",
1392            ipv_inuse, (int)port, (long)Curl_getpid(), exit_signal);
1393     /*
1394      * To properly set the return status of the process we
1395      * must raise the same signal SIGINT or SIGTERM that we
1396      * caught and let the old handler take care of it.
1397      */
1398     raise(exit_signal);
1399   }
1400 
1401   logmsg("========> rtspd quits");
1402   return 0;
1403 }
1404