blob: 32ab0afb0593f498506c945554de0b44e6709d86 [file] [log] [blame]
xf.li6c8fc1e2023-08-12 00:11:09 -07001/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2022, 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
70static bool use_ipv6 = FALSE;
71#endif
72static const char *ipv_inuse = "IPv4";
73static int serverlogslocked = 0;
74
75#define REQBUFSIZ 150000
76#define REQBUFSIZ_TXT "149999"
77
78static long prevtestno = -1; /* previous test number we served */
79static long prevpartno = -1; /* previous part number we served */
80static 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
88typedef 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
99struct 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
126static int ProcessRequest(struct httprequest *req);
127static 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
135const 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
160enum {
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 */
172static const char *docquit =
173"HTTP/1.1 200 Goodbye" END_OF_HEADERS;
174
175/* sent as reply to a CONNECT */
176static const char *docconnect =
177"HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS;
178
179/* sent as reply to a "bad" CONNECT */
180static 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 */
184static 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 */
198static 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
204static const char *RTP_DATA = "$_1234\n\0asdf";
205
206static 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 a HTTP/1.0 one, we close the connection unconditionally
454 * when we're done.
455 *
456 * If the request is a 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 */
596static 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
638storerequest_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 */
649static 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 */
760static 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
1044int 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
1357server_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}