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