From 8d94cd9cabf35514d6928544fd2274ad26149d5b Mon Sep 17 00:00:00 2001 From: jcorporation Date: Mon, 9 Jul 2018 22:58:25 +0100 Subject: [PATCH 01/22] Enable ssl options --- CMakeLists.txt | 14 ++++--- README.md | 4 ++ mympd.1 | 14 ++++++- src/mongoose/mongoose.c | 1 - src/mongoose/mongoose.h | 2 +- src/mympd.c | 90 ++++++++++++++++++++++++++++++++++++----- 6 files changed, 106 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e5bf6e18..fabe70314 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 2.6) project (mympd C) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/") set(CPACK_PACKAGE_VERSION_MAJOR "3") -set(CPACK_PACKAGE_VERSION_MINOR "1") -set(CPACK_PACKAGE_VERSION_PATCH "1") +set(CPACK_PACKAGE_VERSION_MINOR "2") +set(CPACK_PACKAGE_VERSION_PATCH "0") if(CMAKE_BUILD_TYPE MATCHES RELEASE) set(ASSETS_PATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/htdocs") @@ -12,7 +12,6 @@ if(CMAKE_BUILD_TYPE MATCHES RELEASE) else() set(ASSETS_PATH "${PROJECT_SOURCE_DIR}/htdocs") set(DEBUG "ON") - set(CS_NDEBUG "ON") endif() find_package(LibMPDClient REQUIRED) @@ -23,9 +22,13 @@ include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_I include(CheckCSourceCompiles) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_DISABLE_SSL -D MG_ENABLE_IPV6 -D MG_DISABLE_MQTT -D MG_DISABLE_MQTT_BROKER -D MG_DISABLE_DNS_SERVER -D MG_DISABLE_COAP -D MG_DISABLE_HTTP_CGI -D MG_DISABLE_HTTP_SSI -D MG_DISABLE_HTTP_WEBDAV") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_ENABLE_SSL -D MG_ENABLE_IPV6 -D MG_DISABLE_MQTT -D MG_DISABLE_MQTT_BROKER -D MG_DISABLE_DNS_SERVER -D MG_DISABLE_COAP -D MG_DISABLE_HTTP_CGI -D MG_DISABLE_HTTP_SSI -D MG_DISABLE_HTTP_WEBDAV") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -D_FORTIFY_SOURCE=2 -fstack-protector -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=shift -fsanitize=integer-divide-by-zero -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null -fsanitize=return -fsanitize=signed-integer-overflow -fsanitize=bounds -fsanitize=bounds-strict -fsanitize=alignment -fsanitize=object-size -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fsanitize=nonnull-attribute -fsanitize=returns-nonnull-attribute -fsanitize=bool -fsanitize=enum -fsanitize=vptr -static-libasan") +find_package(OpenSSL REQUIRED) +include_directories(${OPENSSL_INCLUDE_DIR}) +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_SSL) + set(SOURCES src/mympd.c src/mpd_client.c @@ -34,7 +37,7 @@ set(SOURCES ) add_executable(mympd ${SOURCES}) -target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES}) install(TARGETS mympd DESTINATION bin) install(FILES mympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1) @@ -47,3 +50,4 @@ install(FILES htdocs/css/bootstrap.min.css DESTINATION share/${PROJECT_NAME}/htd install(FILES htdocs/css/mpd.min.css DESTINATION share/${PROJECT_NAME}/htdocs/css/) install(DIRECTORY htdocs/assets DESTINATION share/${PROJECT_NAME}/htdocs) install(DIRECTORY DESTINATION /var/lib/${PROJECT_NAME}/) +install(DIRECTORY DESTINATION /etc/${PROJECT_NAME}/) diff --git a/README.md b/README.md index 6733e5cbd..e6fcd8437 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ Usage: ./mympd [OPTION]... -h, --host connect to mpd at host [localhost] -p, --port connect to mpd at port [6600] -w, --webport listen port for webserver [80] + -S, --ssl enable ssl + -W, --sslport listen port for ssl webserver [443] + -C, --sslcert filename for ssl certificate [/etc/mympd/server.pem] + -K, --sslkey filename for ssl key [/etc/mympd/server.key] -s, --streamport connect to mpd http stream at port [8000] -u, --user drop priviliges to user after socket bind -m, --mpdpass specifies the password to use when connecting to mpd diff --git a/mympd.1 b/mympd.1 index 92bf37585..aa068092c 100644 --- a/mympd.1 +++ b/mympd.1 @@ -19,7 +19,19 @@ connect to mpd at host, defaults to localhost connect to mpd at port, defaults to 6600 .TP \fB\-w\fR, \fB\-\-webport PORT\fR -specifies the port for the webserver to listen to, defaults to 8080 +listen interface/port for webserver [80] +.TP +\fB\-S\fR, \fB\-\-ssl\fR +enable ssl +.TP +\fB\-W\fR, \fB\-\-sslport PORT\fR +listen interface/port for ssl webserver [443] +.TP +\fB\-C\fR, \fB\-\-sslcert FILENAME\fR +filename for ssl certificate [/etc/mympd/server.pem] +.TP +\fB\-K\fR, \fB\-\-sslkey FILENAME\fR +filename for ssl key [/etc/mympd/server.key] .TP \fB-s\fR, \fB\-\-streamport PORT connect to mpd http stream at port [8000] diff --git a/src/mongoose/mongoose.c b/src/mongoose/mongoose.c index 0bb94ae7b..141e50176 100644 --- a/src/mongoose/mongoose.c +++ b/src/mongoose/mongoose.c @@ -9479,7 +9479,6 @@ static struct mg_ws_proto_data *mg_ws_get_proto_data(struct mg_connection *nc) { */ static void mg_ws_close(struct mg_connection *nc, const void *data, size_t len) { - printf("SEND: %.*s\n",len,data); if ((int) len == ~0) { len = strlen((const char *) data); } diff --git a/src/mongoose/mongoose.h b/src/mongoose/mongoose.h index 2529bdf50..eecbbeff1 100644 --- a/src/mongoose/mongoose.h +++ b/src/mongoose/mongoose.h @@ -4682,7 +4682,7 @@ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, #ifdef __GNUC__ __attribute__((deprecated)); #endif - +; /* * Gets and parses the Authorization: Basic header diff --git a/src/mympd.c b/src/mympd.c index dbf47c092..df197ddfa 100644 --- a/src/mympd.c +++ b/src/mympd.c @@ -37,6 +37,7 @@ extern char *optarg; static sig_atomic_t s_signal_received = 0; static struct mg_serve_http_opts s_http_server_opts; +char s_redirect[250]; static void signal_handler(int sig_num) { signal(sig_num, signal_handler); // Reinstantiate signal handler @@ -44,14 +45,14 @@ static void signal_handler(int sig_num) { } static void handle_api(struct mg_connection *nc, struct http_message *hm) { - if(!is_websocket(nc)) { + if (!is_websocket(nc)) { mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n\r\n"); } char buf[1000] = {0}; memcpy(buf, hm->body.p,sizeof(buf) - 1 < hm->body.len ? sizeof(buf) - 1 : hm->body.len); struct mg_str d = {buf, strlen(buf)}; callback_mympd(nc, d); - if(!is_websocket(nc)) { + if (!is_websocket(nc)) { mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */ } } @@ -82,7 +83,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { case MG_EV_CLOSE: { if (is_websocket(nc)) { #ifdef DEBUG - fprintf(stdout,"Websocket connection closed\n"); + printf("Websocket connection closed\n"); #endif mympd_close_handler(nc); } @@ -96,24 +97,49 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { } } +static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data) { + switch(ev) { + case MG_EV_HTTP_REQUEST: { + + printf("Redirecting to %s\n", s_redirect); + mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL)); + break; + } + } +} + int main(int argc, char **argv) { int n, option_index = 0; struct mg_mgr mgr; struct mg_connection *nc; + struct mg_connection *nc_http; unsigned int current_timer = 0, last_timer = 0; char *run_as_user = NULL; char *webport = "80"; + char *sslport = "443"; mpd.port = 6600; strcpy(mpd.host, "127.0.0.1"); streamport = 8000; strcpy(coverimage, "folder.jpg"); mpd.statefile="/var/lib/mympd/mympd.state"; + struct mg_bind_opts bind_opts; + const char *err; + bool ssl = false; + char *s_ssl_cert = "/etc/mympd/server.pem"; + char *s_ssl_key = "/etc/mympd/server.key"; + char hostname[1024]; + hostname[1023] = '\0'; + gethostname(hostname, 1023); static struct option long_options[] = { {"host", required_argument, 0, 'h'}, {"port", required_argument, 0, 'p'}, {"webport", required_argument, 0, 'w'}, + {"ssl", no_argument, 0, 'S'}, + {"sslport", required_argument, 0, 'W'}, + {"sslcert", required_argument, 0, 'C'}, + {"sslkey", required_argument, 0, 'K'}, {"user", required_argument, 0, 'u'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 0 }, @@ -124,7 +150,7 @@ int main(int argc, char **argv) {0, 0, 0, 0 } }; - while((n = getopt_long(argc, argv, "D:h:p:w:u:vm:s:i:c:t:", + while((n = getopt_long(argc, argv, "h:p:w:SW:C:K:u:vm:s:i:t:", long_options, &option_index)) != -1) { switch (n) { case 't': @@ -139,6 +165,18 @@ int main(int argc, char **argv) case 'w': webport = strdup(optarg); break; + case 'S': + ssl = true; + break; + case 'W': + sslport = strdup(optarg); + break; + case 'C': + s_ssl_cert = strdup(optarg); + break; + case 'K': + s_ssl_key = strdup(optarg); + break; case 'u': run_as_user = strdup(optarg); break; @@ -163,7 +201,11 @@ int main(int argc, char **argv) fprintf(stderr, "Usage: %s [OPTION]...\n\n" " -h, --host \t\tconnect to mpd at host [localhost]\n" " -p, --port \t\tconnect to mpd at port [6600]\n" - " -w, --webport [ip:]\tlisten interface/port for webserver [8080]\n" + " -w, --webport [ip:]\tlisten interface/port for webserver [80]\n" + " -S, --ssl\tenable ssl\n" + " -W, --sslport [ip:]\tlisten interface/port for ssl webserver [443]\n" + " -C, --sslcert \tfilename for ssl certificate [/etc/mympd/server.pem]\n" + " -K, --sslkey \tfilename for ssl key [/etc/mympd/server.key]\n" " -u, --user \t\tdrop priviliges to user after socket bind\n" " -v, --version\t\t\tget version\n" " -m, --mpdpass \tspecifies the password to use when connecting to mpd\n" @@ -184,10 +226,29 @@ int main(int argc, char **argv) mg_mgr_init(&mgr, NULL); - nc = mg_bind(&mgr, webport, ev_handler); - if (nc == NULL) { - fprintf(stderr, "Error starting server on port %s\n", webport); - return EXIT_FAILURE; + if (ssl == true) { + snprintf(s_redirect, 200, "https://%s:%s/", hostname, sslport); + nc_http = mg_bind(&mgr, webport, ev_handler_http); + if (nc_http == NULL) { + fprintf(stderr, "Error starting server on port %s", webport ); + return EXIT_FAILURE; + } + memset(&bind_opts, 0, sizeof(bind_opts)); + bind_opts.ssl_cert = s_ssl_cert; + bind_opts.ssl_key = s_ssl_key; + bind_opts.error_string = &err; + nc = mg_bind_opt(&mgr, sslport, ev_handler, bind_opts); + if (nc == NULL) { + fprintf(stderr, "Error starting server on port %s: %s\n", sslport, err); + return EXIT_FAILURE; + } + } + else { + nc = mg_bind(&mgr, webport, ev_handler); + if (nc == NULL) { + fprintf(stderr, "Error starting server on port %s", webport ); + return EXIT_FAILURE; + } } if(run_as_user != NULL) { @@ -210,11 +271,18 @@ int main(int argc, char **argv) mg_mgr_free(&mgr); return EXIT_FAILURE; } - + + if (ssl == true) + mg_set_protocol_http_websocket(nc_http); + mg_set_protocol_http_websocket(nc); s_http_server_opts.document_root = SRC_PATH; + s_http_server_opts.enable_directory_listing = "no"; - printf("myMPD started on port %s\n", webport); + printf("myMPD started on http port %s\n", webport); + if (ssl == true) + printf("myMPD started on ssl port %s\n", sslport); + while (s_signal_received == 0) { mg_mgr_poll(&mgr, 200); current_timer = time(NULL); From 2d58da89a2bfda22bd10f8b1ce207211dda28191 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Mon, 9 Jul 2018 23:51:02 +0100 Subject: [PATCH 02/22] =?UTF-8?q?First=20code=20f=C3=BCr=20pwa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- htdocs/assets/appicon-167.png | Bin 0 -> 2387 bytes htdocs/assets/appicon-192.png | Bin 0 -> 2789 bytes htdocs/assets/appicon-512.png | Bin 0 -> 8203 bytes htdocs/assets/appicon.png | Bin 6489 -> 0 bytes htdocs/index.html | 14 ++++++++------ htdocs/mympd.webmanifest | 21 +++++++++++++++++++++ 6 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 htdocs/assets/appicon-167.png create mode 100644 htdocs/assets/appicon-192.png create mode 100644 htdocs/assets/appicon-512.png delete mode 100644 htdocs/assets/appicon.png create mode 100644 htdocs/mympd.webmanifest diff --git a/htdocs/assets/appicon-167.png b/htdocs/assets/appicon-167.png new file mode 100644 index 0000000000000000000000000000000000000000..b58e6c81e7126c90b8b8e38796be1d09137354e0 GIT binary patch literal 2387 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 FV1ndpr`iAj literal 0 HcmV?d00001 diff --git a/htdocs/assets/appicon-192.png b/htdocs/assets/appicon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b6b7e7e64b2722174170517bb2dc7bedf747f0dc GIT binary patch literal 2789 zcmVeSaefwW^{L9a%BK_cXuvnZfkR6VQ^(G zZ*pgw?mQX*019PEL_t(|+U=eHS5(Ip!2O?Qm)$ikunSoEsyQhx3z9U)#DId)8d|N2 zS>s}iVve*qvM3c#YXFs4w0mfesg3r;a}bFTD?|_zl3=37ASNgaF4G@-=e@V@&DXa} z`|i(gXYOa;o0&WJ-np|3Epb`q05|{+fCJzFH~UKt>EiElA z(df>)s$$<#0dTKri1iIW#9CQ=INTR&SnbYefV{08pFPEddaZ2)t^9 z(CJnK;QPD2ud!MrC>!tSTkZ3y0Pqj$E&8a0EpFBUy+J$Rmk*FTtn21tzMNfQphwsA zt)Ogx{M*_-?$^uPnZNl!H?if*1;`oE_V5p>zJ=Vs=|)#TCV>B@ruk*xYwh`&6%a0jgHF!h-jRg zAE7qGJ6KijW@S zPhEllN63%zFj6iD$R3-;04mA-;|ItFIZ6(L3K0Mwxp(~wvPI65du^WpKs&j9dF%o3 z4iMcQ;|Hi9{6F{dkWCVpBHU!789_D*eb*)c**_5W zKeAOW!oDBnZ~^?8*nb7>1qcuZKnD*%39$s!Bb#Llu^1F_0QABZ;>dO(HX%Li058J$ z(R7~u00q$7vQWkh@K^W;L{S=qFp7Fv0Se(7Hj$MM0QWe21%-?NCm}wa|1g>@nV=5p zaDolM554#Obp4HU2kKJEb)du(KNG-y2>&W20LG)QNdD++0l45qbebN(GsEVaAptP{_phXF z@qr>gW>EudhNJ%lb^yE>UnvJ*Ih+kQQUhE7zv@C902n`w$;q=kW3?qW0?2}2P?!$@ z-0F1A!mNpMhuh1PmDF#1z#}WW~<%5a= z@O&qYxtkAu@vbGn``~H6Y5mU043n*gQ@`f%X!V|!lk&O z=>!`ZZ~C{CPg>M&DVP4HzE7oFWsoCt(D$6ptrE**$JS9s*W&I>a4j}6x)#@f=esk( zxqBA0QcB}qpZ)LiUu1%N9ke^^q6u((2IhC){LCeg8 zQe6@N1cQ7%<{>)+fFLj?z7_@$4zq&uamgGi05l5v|D_m!c%VB3qYH=*03LgYi2%q* zHLy~~2w*+<%~t{d{W6k0CP9O*Py!I?HkG^pa?)*}vwWI1-G<2f?&brKmGE@dDg)dQ z0J_+J%AAz58fpN~H2B%u>;P(#{vNrh0f-kj=3)epnRa={a#1w~fSGD|6BEEQnTfA3 zeKJM%UW(M?QTE(#6{*M9Ez-4812jSO*Qfy`Cx5>|aR?&?z>3G%Y$t6DAU*va)*_f; zOTQ=2(Ql;cPWk}|Vg$4!Hh@+0X+MIJ1xsNie!&Ppo{4=o9RSdyh|eKbfHHUr=y8Gl z`mcnG@O&vV0Q!iiQDHy8$Jp}hfH^=haU^!L-TH4Ojv^Lv0FWoK>^=;d_mE}^G6(Pz zZCtV!;1l9FZZ;PH+DmLu-mz2v{nQmoOpl}!RfXoN?9=~cV*Cy99!W^dnk&8?a{bA)s19VnyCurYsiuxwbIwKI!(Swpirw3M3D}pj_uA zsXLs!Pjm%GrIzb7Dwz6Ui^Tn-?%J18+F>SAvi3@$9Q6z0`fOP0dYTMHg9r36$O95Q zUY(hr(C+bA?uQw>e9%oo+gbQa6*NCXUe!!@3Hp{kyt#o;#QR(vj zO`Q82SN(N^=H{4^lvs<$S4|nE6O2VTQkh|a^!65xtHqq z<7?Tq;krbmE%kjS$+7O;;S{r=x3clfceZ&Xo7)FxEs8P>9t06k{*Cz2ZDrD9Jj$;3EehA}h(?dJv>b^>chrUp75P)03zi)K r2fzVv02}}ZzyWXo8~_L4{}|vug4qu6Kq%)E00000NkvXXu0mjfE1L{9 literal 0 HcmV?d00001 diff --git a/htdocs/assets/appicon-512.png b/htdocs/assets/appicon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..67f664aca037f6e33c486daa09838294ba8d3945 GIT binary patch literal 8203 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 z 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 d7Y3^VO + myMPD - myMPD + + + - - - - + + +
@@ -582,7 +584,7 @@