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