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