protocol_s* http1_alloc(intptr_t fd, http_settings_s* settings) { validate_mem(); // HTTP/1.1 should send a busy response // if there aren't enough available file descriptors. if (sock_max_capacity() - sock_uuid2fd(fd) <= HTTP_BUSY_UNLESS_HAS_FDS) goto is_busy; // get an http object from the pool http1_protocol_s* http = pool_pop(); // of malloc one if (http == NULL) http = malloc(HTTP1_PROTOCOL_SIZE); // review allocation if (http == NULL) return NULL; // we shouldn't update the `http` protocol as a struct, as this will waste // time as the whole buffer will be zeroed out when there is no need. // setup parsing state http->buffer_pos = 0; // setup protocol callbacks http->protocol = (protocol_s){ .service = HTTP1, .on_data = (void (*)(intptr_t, protocol_s*))http1_on_data, .on_close = (void (*)(protocol_s*))http1_free, }; // setup request data http->request = (http_request_s){ .metadata.max_headers = HTTP1_MAX_HEADER_COUNT, .metadata.fd = fd, .metadata.owner = http, }; // update settings http->settings = settings; http->on_request = settings->on_request; // set the timeout server_set_timeout(fd, settings->timeout); return (protocol_s*)http; is_busy: if (settings->public_folder && settings->public_folder_length) { size_t p_len = settings->public_folder_length; struct stat file_data = {}; char fname[p_len + 8 + 1]; memcpy(fname, settings->public_folder, p_len); if (settings->public_folder[p_len - 1] == '/' || settings->public_folder[p_len - 1] == '\\') p_len--; memcpy(fname + p_len, "/503.html", 9); p_len += 9; if (stat(fname, &file_data)) goto busy_no_file; // check that we have a file and not something else if (!S_ISREG(file_data.st_mode) && !S_ISLNK(file_data.st_mode)) goto busy_no_file; int file = open(fname, O_RDONLY); if (file == -1) goto busy_no_file; sock_packet_s* packet; packet = sock_checkout_packet(); memcpy(packet->buffer, "HTTP/1.1 503 Service Unavailable\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n" "Content-Length: ", 94); p_len = 94 + http_ul2a(packet->buffer + 94, file_data.st_size); memcpy(packet->buffer + p_len, "\r\n\r\n", 4); p_len += 4; if (BUFFER_PACKET_SIZE - p_len > file_data.st_size) { if (read(file, packet->buffer + p_len, file_data.st_size) < 0) { close(file); sock_free_packet(packet); goto busy_no_file; } close(file); packet->length = p_len + file_data.st_size; sock_send_packet(fd, packet); } else { packet->length = p_len; sock_send_packet(fd, packet); sock_sendfile(fd, file, 0, file_data.st_size); } return NULL; } busy_no_file: sock_write(fd, "HTTP/1.1 503 Service Unavailable\r\nContent-Length: " "13\r\n\r\nServer Busy.", 68); return NULL; } /* ***************************************************************************** HTTP/1.1 protocol bare-bones implementation */ #define HTTP_BODY_CHUNK_SIZE 3072 // 4096 /* parse and call callback */ static void http1_on_data(intptr_t uuid, http1_protocol_s* protocol) { ssize_t len = 0; ssize_t result; char buff[HTTP_BODY_CHUNK_SIZE]; char* buffer; http_request_s* request = &protocol->request; for (;;) { // handle requests with no file data if (request->body_file <= 0) { // request headers parsing if (len == 0) { buffer = protocol->buffer; // make sure headers don't overflow len = sock_read(uuid, buffer + protocol->buffer_pos, HTTP1_MAX_HEADER_SIZE - protocol->buffer_pos); // update buffer read position. protocol->buffer_pos += len; } if (len <= 0) { return; } // parse headers result = http1_parse_request_headers(buffer, protocol->buffer_pos, request); // review result if (result >= 0) { // headers comeplete // mark buffer position, for HTTP pipelining protocol->buffer_pos = result; // are we done? if (request->content_length == 0 || request->body_str) { goto handle_request; } if (request->content_length > protocol->settings->max_body_size) { goto body_to_big; } // initialize or submit body data result = http1_parse_request_body(buffer + result, len - result, request); if (result >= 0) { protocol->buffer_pos += result; goto handle_request; } else if (result == -1) // parser error goto parser_error; goto parse_body; } else if (result == -1) // parser error goto parser_error; // assume incomplete (result == -2), even if wrong, we're right. len = 0; continue; } if (request->body_file > 0) { parse_body: buffer = buff; // request body parsing len = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE); if (len <= 0) return; result = http1_parse_request_body(buffer, len, request); if (result >= 0) { // set buffer pos for piplining support protocol->buffer_pos = result; goto handle_request; } else if (result == -1) // parser error goto parser_error; if (len < HTTP_BODY_CHUNK_SIZE) // pause parser for more data return; goto parse_body; } continue; handle_request: // review required headers / data if (request->host == NULL) goto bad_request; http_settings_s* settings = protocol->settings; // call request callback if (protocol && settings && (protocol->settings->public_folder == NULL || http_response_sendfile2(NULL, request, settings->public_folder, settings->public_folder_length, request->path, request->path_len, settings->log_static))) { protocol->on_request(request); } // clear request state http_request_clear(request); // rotate buffer for HTTP pipelining if (result >= len) { len = 0; } else { memmove(protocol->buffer, buffer + protocol->buffer_pos, len - result); len -= result; } // restart buffer position protocol->buffer_pos = 0; buffer = protocol->buffer; } // no routes lead here. fprintf(stderr, "I am lost on a deserted island, no code can reach me here :-)\n"); return; // How did we get here? parser_error: if (request->headers_count == request->metadata.max_headers) goto too_big; bad_request: /* handle generally bad requests */ { http_response_s response = http_response_init(request); response.status = 400; http_response_write_body(&response, "Bad Request", 11); http_response_finish(&response); sock_close(uuid); protocol->buffer_pos = 0; return; } too_big: /* handle oversized headers */ { http_response_s response = http_response_init(request); response.status = 431; http_response_write_body(&response, "Request Header Fields Too Large", 31); http_response_finish(&response); sock_close(uuid); protocol->buffer_pos = 0; return; body_to_big: /* handle oversized body */ { http_response_s response = http_response_init(request); response.status = 413; http_response_write_body(&response, "Payload Too Large", 17); http_response_finish(&response); sock_close(uuid); protocol->buffer_pos = 0; return; } } } /* ***************************************************************************** HTTP/1.1 listenning API implementation */ #undef http1_listen static void http1_on_init(http_settings_s* settings) { if (settings->timeout == 0) settings->timeout = 5; if (settings->max_body_size == 0) settings->max_body_size = HTTP_DEFAULT_BODY_LIMIT; if (settings->public_folder) { settings->public_folder_length = strlen(settings->public_folder); if (settings->public_folder[0] == '~' && settings->public_folder[1] == '/' && getenv("HOME")) { char* home = getenv("HOME"); size_t home_len = strlen(home); char* tmp = malloc(settings->public_folder_length + home_len + 1); memcpy(tmp, home, home_len); if (home[home_len - 1] == '/') --home_len; memcpy(tmp + home_len, settings->public_folder + 1, settings->public_folder_length); // copy also the NULL settings->public_folder = tmp; settings->private_metaflags |= 1; settings->public_folder_length = strlen(settings->public_folder); } } } static void http1_on_finish(http_settings_s* settings) { if (settings->private_metaflags & 1) free((void*)settings->public_folder); if (settings->private_metaflags & 2) free(settings); } int http1_listen(const char* port, const char* address, http_settings_s settings) { if (settings.on_request == NULL) { fprintf( stderr, "ERROR: http1_listen requires the .on_request parameter to be set\n"); exit(11); } http_settings_s* settings_copy = malloc(sizeof(*settings_copy)); *settings_copy = settings; settings_copy->private_metaflags = 2; return server_listen(.port = port, .address = address, .on_start = (void*)http1_on_init, .on_finish = (void*)http1_on_finish, .on_open = (void*)http1_alloc, .udata = settings_copy); }
static void conn_request(void *ptr, http_request_t *request, http_response_t **response) { const char realm[] = "airplay"; raop_conn_t *conn = ptr; raop_t *raop = conn->raop; http_response_t *res; const char *method; const char *cseq; const char *challenge; int require_auth = 0; method = http_request_get_method(request); cseq = http_request_get_header(request, "CSeq"); if (!method || !cseq) { return; } res = http_response_init("RTSP/1.0", 200, "OK"); /* We need authorization for everything else than OPTIONS request */ if (strcmp(method, "OPTIONS") != 0 && strlen(raop->password)) { const char *authorization; authorization = http_request_get_header(request, "Authorization"); if (authorization) { logger_log(conn->raop->logger, LOGGER_DEBUG, "Our nonce: %s", conn->nonce); logger_log(conn->raop->logger, LOGGER_DEBUG, "Authorization: %s", authorization); } if (!digest_is_valid(realm, raop->password, conn->nonce, method, http_request_get_url(request), authorization)) { char *authstr; int authstrlen; /* Allocate the authenticate string */ authstrlen = sizeof("Digest realm=\"\", nonce=\"\"") + sizeof(realm) + sizeof(conn->nonce) + 1; authstr = malloc(authstrlen); /* Concatenate the authenticate string */ memset(authstr, 0, authstrlen); strcat(authstr, "Digest realm=\""); strcat(authstr, realm); strcat(authstr, "\", nonce=\""); strcat(authstr, conn->nonce); strcat(authstr, "\""); /* Construct a new response */ require_auth = 1; http_response_destroy(res); res = http_response_init("RTSP/1.0", 401, "Unauthorized"); http_response_add_header(res, "WWW-Authenticate", authstr); free(authstr); logger_log(conn->raop->logger, LOGGER_DEBUG, "Authentication unsuccessful, sending Unauthorized"); } else { logger_log(conn->raop->logger, LOGGER_DEBUG, "Authentication successful!"); } } http_response_add_header(res, "CSeq", cseq); http_response_add_header(res, "Apple-Jack-Status", "connected; type=analog"); challenge = http_request_get_header(request, "Apple-Challenge"); if (!require_auth && challenge) { char signature[MAX_SIGNATURE_LEN]; memset(signature, 0, sizeof(signature)); rsakey_sign(raop->rsakey, signature, sizeof(signature), challenge, conn->local, conn->locallen, raop->hwaddr, raop->hwaddrlen); http_response_add_header(res, "Apple-Response", signature); logger_log(conn->raop->logger, LOGGER_DEBUG, "Got challenge: %s", challenge); logger_log(conn->raop->logger, LOGGER_DEBUG, "Got response: %s", signature); } if (require_auth) { /* Do nothing in case of authentication request */ } else if (!strcmp(method, "OPTIONS")) { http_response_add_header(res, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER"); } else if (!strcmp(method, "ANNOUNCE")) { const char *data; int datalen; unsigned char aeskey[16]; unsigned char aesiv[16]; int aeskeylen, aesivlen; data = http_request_get_data(request, &datalen); if (data) { sdp_t *sdp; const char *remotestr, *rtpmapstr, *fmtpstr, *aeskeystr, *aesivstr; sdp = sdp_init(data, datalen); remotestr = sdp_get_connection(sdp); rtpmapstr = sdp_get_rtpmap(sdp); fmtpstr = sdp_get_fmtp(sdp); aeskeystr = sdp_get_rsaaeskey(sdp); aesivstr = sdp_get_aesiv(sdp); logger_log(conn->raop->logger, LOGGER_DEBUG, "connection: %s", remotestr); logger_log(conn->raop->logger, LOGGER_DEBUG, "rtpmap: %s", rtpmapstr); logger_log(conn->raop->logger, LOGGER_DEBUG, "fmtp: %s", fmtpstr); logger_log(conn->raop->logger, LOGGER_DEBUG, "rsaaeskey: %s", aeskeystr); logger_log(conn->raop->logger, LOGGER_DEBUG, "aesiv: %s", aesivstr); aeskeylen = rsakey_decrypt(raop->rsakey, aeskey, sizeof(aeskey), aeskeystr); aesivlen = rsakey_parseiv(raop->rsakey, aesiv, sizeof(aesiv), aesivstr); logger_log(conn->raop->logger, LOGGER_DEBUG, "aeskeylen: %d", aeskeylen); logger_log(conn->raop->logger, LOGGER_DEBUG, "aesivlen: %d", aesivlen); if (conn->raop_rtp) { /* This should never happen */ raop_rtp_destroy(conn->raop_rtp); conn->raop_rtp = NULL; } conn->raop_rtp = raop_rtp_init(raop->logger, &raop->callbacks, remotestr, rtpmapstr, fmtpstr, aeskey, aesiv); if (!conn->raop_rtp) { logger_log(conn->raop->logger, LOGGER_ERR, "Error initializing the audio decoder"); http_response_set_disconnect(res, 1); } sdp_destroy(sdp); } } else if (!strcmp(method, "SETUP")) { unsigned short remote_cport=0, remote_tport=0; unsigned short cport=0, tport=0, dport=0; const char *transport; char buffer[1024]; int use_udp; const char *dacp_id; const char *active_remote_header; dacp_id = http_request_get_header(request, "DACP-ID"); active_remote_header = http_request_get_header(request, "Active-Remote"); if (dacp_id && active_remote_header) { logger_log(conn->raop->logger, LOGGER_DEBUG, "DACP-ID: %s", dacp_id); logger_log(conn->raop->logger, LOGGER_DEBUG, "Active-Remote: %s", active_remote_header); if (conn->raop_rtp) { raop_rtp_remote_control_id(conn->raop_rtp, dacp_id, active_remote_header); } } transport = http_request_get_header(request, "Transport"); assert(transport); logger_log(conn->raop->logger, LOGGER_INFO, "Transport: %s", transport); use_udp = strncmp(transport, "RTP/AVP/TCP", 11); if (use_udp) { char *original, *current, *tmpstr; current = original = strdup(transport); if (original) { while ((tmpstr = utils_strsep(¤t, ";")) != NULL) { unsigned short value; int ret; ret = sscanf(tmpstr, "control_port=%hu", &value); if (ret == 1) { logger_log(conn->raop->logger, LOGGER_DEBUG, "Found remote control port: %hu", value); remote_cport = value; } ret = sscanf(tmpstr, "timing_port=%hu", &value); if (ret == 1) { logger_log(conn->raop->logger, LOGGER_DEBUG, "Found remote timing port: %hu", value); remote_tport = value; } } } free(original); } if (conn->raop_rtp) { raop_rtp_start(conn->raop_rtp, use_udp, remote_cport, remote_tport, &cport, &tport, &dport); } else { logger_log(conn->raop->logger, LOGGER_ERR, "RAOP not initialized at SETUP, playing will fail!"); http_response_set_disconnect(res, 1); } memset(buffer, 0, sizeof(buffer)); if (use_udp) { snprintf(buffer, sizeof(buffer)-1, "RTP/AVP/UDP;unicast;mode=record;timing_port=%hu;events;control_port=%hu;server_port=%hu", tport, cport, dport); } else { snprintf(buffer, sizeof(buffer)-1, "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record;server_port=%u", dport); } logger_log(conn->raop->logger, LOGGER_INFO, "Responding with %s", buffer); http_response_add_header(res, "Transport", buffer); http_response_add_header(res, "Session", "DEADBEEF"); } else if (!strcmp(method, "SET_PARAMETER")) { const char *content_type; const char *data; int datalen; content_type = http_request_get_header(request, "Content-Type"); data = http_request_get_data(request, &datalen); if (!strcmp(content_type, "text/parameters")) { char *datastr; datastr = calloc(1, datalen+1); if (data && datastr && conn->raop_rtp) { memcpy(datastr, data, datalen); if (!strncmp(datastr, "volume: ", 8)) { float vol = 0.0; sscanf(datastr+8, "%f", &vol); raop_rtp_set_volume(conn->raop_rtp, vol); } else if (!strncmp(datastr, "progress: ", 10)) { unsigned int start, curr, end; sscanf(datastr+10, "%u/%u/%u", &start, &curr, &end); raop_rtp_set_progress(conn->raop_rtp, start, curr, end); } } else if (!conn->raop_rtp) { logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER"); } free(datastr); } else if (!strcmp(content_type, "image/jpeg") || !strcmp(content_type, "image/png")) { logger_log(conn->raop->logger, LOGGER_INFO, "Got image data of %d bytes", datalen); if (conn->raop_rtp) { raop_rtp_set_coverart(conn->raop_rtp, data, datalen); } else { logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER coverart"); } } else if (!strcmp(content_type, "application/x-dmap-tagged")) { logger_log(conn->raop->logger, LOGGER_INFO, "Got metadata of %d bytes", datalen); if (conn->raop_rtp) { raop_rtp_set_metadata(conn->raop_rtp, data, datalen); } else { logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER metadata"); } } } else if (!strcmp(method, "FLUSH")) { const char *rtpinfo; int next_seq = -1; rtpinfo = http_request_get_header(request, "RTP-Info"); if (rtpinfo) { logger_log(conn->raop->logger, LOGGER_INFO, "Flush with RTP-Info: %s", rtpinfo); if (!strncmp(rtpinfo, "seq=", 4)) { next_seq = strtol(rtpinfo+4, NULL, 10); } } if (conn->raop_rtp) { raop_rtp_flush(conn->raop_rtp, next_seq); } else { logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at FLUSH"); } } else if (!strcmp(method, "TEARDOWN")) { http_response_add_header(res, "Connection", "close"); if (conn->raop_rtp) { /* Destroy our RTP session */ raop_rtp_stop(conn->raop_rtp); raop_rtp_destroy(conn->raop_rtp); conn->raop_rtp = NULL; } } http_response_finish(res, NULL, 0); logger_log(conn->raop->logger, LOGGER_DEBUG, "Handled request %s with URL %s", method, http_request_get_url(request)); *response = res; }