#include <iostream> #include <string> //#include <thread> #include <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> #define K_MAX_MB 8 #define K_VER_MAJOR 0 #define K_VER_MINOR 1 #define K_FN_LENGTH 5 //int http_redirect(const char* http_port, const char* https_port) { // // ** TODO: untested // // ** TODO: sync process exits, thread monitoring... // // redirects http requests on port http to https // BIO* s_accept = BIO_new_accept(http_port); // BIO_set_bind_mode(s_accept, BIO_BIND_REUSEADDR); // if (BIO_do_accept(s_accept) != 1) { // std::cout << "\ncreate HTTP socket attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n\n"; // return -1; // } // while (true) { // if (BIO_do_accept(s_accept) != 1) { // std::cout << "\nlisten HTTP socket attempt failed!\n" << ERR_error_string(ERR_get_error(), NULL) << "\nretrying...\n\n"; // continue; // } BIO* s_socket = BIO_pop(s_accept); // const char* reply = "HTTP/1.1 301 Moved Permanently\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: https:///\r\nContent-Length: 9\r\n\r\nuse https"; // BIO_write(s_socket, reply, strlen(reply)); // BIO_reset(s_socket); // BIO_free(s_socket); // } // BIO_free(s_accept); // return 0; //} int rand_name(char* out, int length) { char* buf = new char[length+1]; buf[length] = 0x00; srand(static_cast<int>(time(NULL))); for (int i = 0; i < length; i++) { int z = -1 + (rand() % (1 - -1 + 1)); if (z == 1 || z == -1) buf[i] = 65 + (rand() % (122 - 97 + 1)); else buf[i] = 97 + (rand() % (90 - 65 + 1)); } int ret = strcpy(out, buf); // assumed your buffer is large enough for 0x00 !!! delete[] buf; return ret; } int main() { std::cout << "kaoru, version " << K_VER_MAJOR << '.' << K_VER_MINOR << "\n\n"; // ** TODO: cmdline arguments for http_port, https_port, listen interfaces etc // start http:80 redirector // http_redirect("80", "443"); // listen // setup SSL_load_error_strings(); SSL_CTX* s_ctx = SSL_CTX_new(TLS_server_method()); SSL_CTX_set_min_proto_version(s_ctx, TLS1_2_VERSION); SSL_CTX_set_options(s_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_RENEGOTIATION); // insert cert code here SSL_CTX_use_certificate_chain_file(s_ctx, "cert.pem"); SSL_CTX_use_PrivateKey_file(s_ctx, "key.pem", SSL_FILETYPE_PEM); SSL_CTX_set_verify(s_ctx, SSL_VERIFY_NONE, NULL); //SSL* s_ssl = NULL; //BIO* s_bssl = BIO_new_ssl(s_ctx, 0); BIO* s_accept = BIO_new_accept("420"); BIO_set_bind_mode(s_accept, BIO_BIND_REUSEADDR); //BIO_make_bio_pair(s_accept, s_bssl); //BIO_get_ssl(s_bssl, &s_ssl); //SSL_set_read_ahead(s_ssl, 0); //ERR_clear_error(); if (BIO_do_accept(s_accept) != 1) { std::cout << "\ncreate socket attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n\n"; return -1; } std::cout << "created ssl socket, listen on " << BIO_get_accept_name(s_accept) << ' ' << BIO_get_accept_port(s_accept) << "\n\n"; net_accept: if (BIO_do_accept(s_accept) != 1) { std::cout << "\nlisten socket attempt failed!\n" << ERR_error_string(ERR_get_error(), NULL) << "\nretrying...\n\n"; goto net_accept; } // connection is made BIO* s_socket = BIO_pop(s_accept); SSL* s_ssl = SSL_new(s_ctx); SSL_set_bio(s_ssl, s_socket, s_socket); if (SSL_accept(s_ssl) != 1) { std::cout << "\nssl handshake attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nclosing socket...\n\n"; SSL_free(s_ssl); goto net_accept; } std::cout << "Established SSL connection with " << BIO_get_peer_name(s_accept) << ' ' << BIO_get_peer_port(s_accept) << '\n'; while (true) { // begin the request/response loop std::string buf_s = ""; size_t len = 0; unsigned char* b_cache = NULL; size_t b_len = 0; while (true) { static bool first = true; // header char* buf = new char[201]; size_t len_n = 0; if (SSL_read_ex(s_ssl, buf, 200, &len_n) != 1) { if (BIO_eof(s_socket)) { BIO_reset(s_socket); goto net_accept; } // reset int err = ERR_get_error(); // if (err == SSL_ERROR_WANT_READ) continue; // wait std::cout << "read attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n"; delete[] buf; return -1; } buf[200 - (200 - len_n)] = 0x00; buf_s.append(buf); delete[] buf; len = len + len_n; if (first) { first = false; std::cout << "Data received, length read: " << len_n; } else std::cout << ' ' << len_n; // if (strcmp(buf_s.c_str() + (buf_s.size() - 4), "\r\n\r\n") == 0) break; // end http header const char* pos = strstr(buf_s.c_str(), "\r\n\r\n"); if (pos != NULL) { if (((buf_s.c_str()+len) - pos) > 4) { // split header, cache body b_len = (buf_s.c_str() + len) - pos - 4; b_cache = new unsigned char[b_len + 1]; // strcpy_s(b_cache, b_len + 1, pos+4); memcpy(b_cache, pos+4, b_len); } buf_s = buf_s.substr(0, ((pos+4) - buf_s.c_str())-1); first = true; break; } } std::cout << '\n'; std::cout << "request-> " << buf_s.substr(0, buf_s.find("\r\n")) << '\n'; // handle request if (strncmp(buf_s.c_str(), "GET /", 5) == 0) { // GET request if (strncmp(buf_s.c_str(), "GET / HTTP/1.", 13) == 0) { // home page std::cout << "HTTP request for / detected, serving home page... \n"; const char* header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 236\r\n\r\n"; const char* body = "<html><head><script>function p(){let e=document.getElementById(\"f\").files[0];var r=new XMLHttpRequest();r.open(\"POST\",\"/p\",true);r.send(e);}</script></head>ayo<br><input id=\"f\" type=\"file\"/><br><button onclick=\"p()\">post</button></html>"; SSL_write(s_ssl, header, strlen(header)); SSL_write(s_ssl, body, strlen(body)); } else { if (strncmp(buf_s.c_str(), "GET /s/", 7) == 0) { const char* s_s = strstr(buf_s.c_str(), "GET /s/"); const char* s_e = strstr(buf_s.c_str(), "HTTP/1."); if ( s_s == NULL || s_e == NULL) { std::cout << "malformed request detected, ignoring and closing... \n"; goto net_clean; } int req_len = (s_e - 1) - (s_s + 7); if (req_len <1) { // 403 forbidden to view root of hosted files std::cout << "forbidden http get request detected, 403ing and closing... \n"; const char* reply = "HTTP/1.1 403 Forbidden\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>403</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); goto net_clean; } char* fn = new char[req_len+1]; fn[req_len] = 0x00; strncpy(fn, buf_s.c_str()+7, req_len); std::cout << "request for file \"" << fn << "\"\n"; FILE* fp = fopen(fn, "rb"); if (fp == NULL) { // maybe file doesn't exist, handle gracefully // even if notfound or not, remain ambigious std::cout << "unable to open file \""<< fn << "\", 404ing and closing... \n"; const char* reply = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>404</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); goto net_clean; } fseek(fp, 0, SEEK_END); size_t sz = ftell(fp); if (sz < 1) { // don't serve 0 files std::cout << "file is 0 bytes, or couldn't determine file size \""<< fn << "\", 500ing and closing... \n"; const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); goto net_clean; } else if (sz > (K_MAX_MB * 1000000)) { // don't serve files larger than limit std::cout << "file to be served is too large. \""<< fn << "\" (" << sz << "). 500ing and closing... \n"; const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); goto net_clean; } fseek(fp, 0, SEEK_SET); unsigned char* data = new unsigned char[sz]; std::cout << "read " << fread(data, 1, sz, fp) << " bytes.\n"; fclose(fp); // *** determine content-type // serve file const char* header = "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nConnection: close\r\nContent-Length: "; // *** TODO: get better snprintf_s to replace below code for buffer allocation int h = 12; // 12 digits // int h = sprintf_s(NULL, NULL, "%u", sz); char* cl_s = new char[h+1]; sprintf(cl_s, "%u", sz); // ** add error handling SSL_write(s_ssl, header, strlen(header)); SSL_write(s_ssl, cl_s, strlen(cl_s)); SSL_write(s_ssl, "\r\n\r\n", 4); SSL_write(s_ssl, data, sz); // ** add error handling delete[] cl_s; delete[] data; delete[] fn; } else { std::cout << "unknown request detected, 404ing and closing... \n"; const char* reply = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>404</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); } } } else if (strncmp(buf_s.c_str(), "POST /p HTTP/1.", 15) == 0) { // POST request const char* cl_s = strstr(buf_s.c_str(), "Content-Length:"); if (cl_s == NULL)if (cl_s == NULL) { // no content-length std::cout << "bad http post request detected, 400ing and closing... \n"; const char* reply = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>400</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); goto net_clean; } cl_s = cl_s + 15; // Content-Length: //const char* cl_e = strstr(cl_s, "\r\n"); //if (cl_e == NULL) { // bad content-length // std::cout << "bad http post request detected, 400ing and closing... \n"; // const char* reply = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>400</html>\r\n\r\n"; // SSL_write(s_ssl, reply, strlen(reply)); //} // if (cl_s + 1 == ' ') cl_s++; // not compact // int cl_l = cl_e - cl_s; int cl = strtol(cl_s, NULL, 10); if (cl < 1 || cl > (K_MAX_MB * 1000000)){ // bad content-length, or reuqest too large std::cout << "bad http post request detected, 400ing and closing... \n"; const char* reply = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>400</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); goto net_clean; } // *** find content-type const char* filetype = "bin"; // gen filename char* filename = new char[K_FN_LENGTH+4+1]; filename[K_FN_LENGTH+4] = 0x00; // <K_FN_LENGTH>.xyz if (rand_name(filename, K_FN_LENGTH) != 0) { std::cout << "cannot create file to write, 500ing and closing... \n"; const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); return -1; } sprintf(filename+K_FN_LENGTH, ".%s", filetype); // open file for writing FILE* out = fopen(filename, "wb"); if (out == NULL) { std::cout << "cannot create file to write, 500ing and closing... \n"; const char* reply = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>500</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); return -1; } size_t len_p = 0; unsigned char* buf2 = new unsigned char[cl+1]; buf2[cl] = 0x00; // b_cache if (b_cache != NULL) { memcpy(buf2, b_cache, b_len); delete[] b_cache; b_cache = NULL; } size_t h = cl - b_len; size_t offset = 0; if (h > 0) { // SSL_read 0 bytes still blocks... while (true) { if (SSL_read_ex(s_ssl, buf2 + b_len + offset, h, &len_p) != 1) { // bad read std::cout << "read POST attempt failed.\n" << ERR_error_string(ERR_get_error(), NULL) << "\nfatal error. exiting...\n"; delete[] buf2; return -1; } if (len_p >= h) { offset = offset + len_p; break; } else { h = h - len_p; offset = len_p; } } } // std::cout << "POST request receieved, len: " << len_p << " body:\n" << buf2 << '\n'; std::cout << "POST request receieved, len: " << offset << ", writing to file...\n"; std::cout << "wrote " << fwrite(buf2, 1, offset, out) << " bytes to file.\n"; fclose(out); delete[] buf2; // reply std::cout << "File created. 201ing...\n"; const char* reply = "HTTP/1.1 201 Created\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: /s/"; const char* reply2 = "\r\nContent-Length: 16\r\n\r\n<html>201</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); SSL_write(s_ssl, filename, strlen(filename)); SSL_write(s_ssl, reply2, strlen(reply2)); delete[] filename; } else { std::cout << "unknown request detected, 404ing and closing... \n"; const char* reply = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 16\r\n\r\n<html>404</html>\r\n\r\n"; SSL_write(s_ssl, reply, strlen(reply)); } net_clean: std::cout << "Goodbye. Closing socket...\n\n"; SSL_free(s_ssl); // BIO_free(s_socket); // BIO_reset(s_socket); // reset and close connection goto net_accept; } BIO_reset(s_socket); // reset and close connection BIO_reset(s_accept); BIO_free_all(s_socket); BIO_free_all(s_accept); SSL_CTX_free(s_ctx); return 0; }