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