diff --git a/htdocs/js/mpd.js b/htdocs/js/mpd.js index 663745a..5e02bc1 100644 --- a/htdocs/js/mpd.js +++ b/htdocs/js/mpd.js @@ -30,6 +30,7 @@ var MAX_ELEMENTS_PER_PAGE = 512; var isTouch = Modernizr.touch ? 1 : 0; var filter = ''; var scrobbler = ''; +var wss_auth_token = ''; var app = $.sammy(function () { function runBrowse() { @@ -38,7 +39,8 @@ var app = $.sammy(function () { $('#breadcrump').addClass('hide'); $('#filter').addClass('hide'); $('#salamisandwich').removeClass('hide').find('tr:gt(0)').remove(); - socket.send('MPD_API_GET_QUEUE,' + pagination); + if (wss_auth_token !== '') + socket.send('MPD_API_GET_QUEUE,' + pagination); $('#panel-heading').text('Queue'); $('#panel-heading-info').empty(); @@ -249,6 +251,25 @@ $(document).ready(function () { ); }); +function webSocketAuthenticate() { + var u = document.URL.split('#'); + var separator; + + if (/\/$/.test(u[0])) { + separator = ''; + } else { + separator = '/'; + } + + $.ajax({ + url: u[0] + separator + 'wss-auth', + success: function (data) { + wss_auth_token = data; + socket.send('MPD_API_AUTHORIZE,' + wss_auth_token); + }, + }); +} + function webSocketConnect() { if (typeof MozWebSocket != 'undefined') { socket = new MozWebSocket(get_appropriate_ws_url()); @@ -267,9 +288,8 @@ function webSocketConnect() { .show(); app.run(); - /* emit initial request for output names */ - socket.send('MPD_API_GET_OUTPUTS'); - socket.send('MPD_API_GET_CHANNELS'); + + if (wss_auth_token === '') webSocketAuthenticate(); }; socket.onmessage = function got_packet(msg) { @@ -904,6 +924,15 @@ function webSocketConnect() { $('#mpd_password_set').removeClass('hide'); break; + case 'authorized': + if (obj.data === 'true') { + /* emit initial request for output names */ + socket.send('MPD_API_GET_OUTPUTS'); + socket.send('MPD_API_GET_CHANNELS'); + } else webSocketAuthenticate(); + + break; + case 'error': $('.top-right') .notify({ @@ -918,6 +947,7 @@ function webSocketConnect() { socket.onclose = function () { console.log('disconnected'); + wss_auth_token = ''; $('.top-right') .notify({ message: { diff --git a/src/http_server.c b/src/http_server.c index 1552db6..e059955 100644 --- a/src/http_server.c +++ b/src/http_server.c @@ -18,8 +18,11 @@ #include "http_server.h" +#include #include +#include "mpd_client.h" + int callback_http(struct mg_connection *c) { const struct embedded_file *req_file; @@ -35,6 +38,22 @@ int callback_http(struct mg_connection *c) { return MG_TRUE; } + if (!strcmp(c->uri, "/wss-auth")) { + unsigned char salt[WSS_AUTH_TOKEN_SIZE + 1]; + + RAND_bytes(salt, WSS_AUTH_TOKEN_SIZE); + for (int i = 0; i <= WSS_AUTH_TOKEN_SIZE; i++) salt[i] = salt[i] % 26 + 65; + salt[WSS_AUTH_TOKEN_SIZE] = 0; + if (mpd.wss_auth_token) + free(mpd.wss_auth_token); + mpd.wss_auth_token = strdup((char *)salt); + + mg_send_header(c, "Content-Type", "text/plain"); + mg_send_data(c, salt, WSS_AUTH_TOKEN_SIZE); + + return MG_TRUE; + } + mg_send_status(c, 404); mg_printf_data(c, "Not Found"); return MG_TRUE; diff --git a/src/mpd_client.c b/src/mpd_client.c index 4aa6565..ac594bc 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -58,6 +58,18 @@ int callback_mpd(struct mg_connection *c) { int int_buf; char *p_charbuf = NULL, *token; + if (!c->connection_param) + c->connection_param = calloc(1, sizeof(struct t_mpd_client_session)); + + struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param; + + if (!s->authorized && (cmd_id != MPD_API_AUTHORIZE)) { + n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"not authorized\"}"); + mg_websocket_write(c, 1, mpd.buf, n); + + return MG_TRUE; + } + if (cmd_id == -1) return MG_TRUE; @@ -66,6 +78,25 @@ int callback_mpd(struct mg_connection *c) { return MG_TRUE; switch (cmd_id) { + case MPD_API_AUTHORIZE: + p_charbuf = strdup(c->content); + if (strcmp(strtok(p_charbuf, ","), "MPD_API_AUTHORIZE")) + goto out_authorize; + + if ((token = strtok(NULL, ",")) == NULL) + goto out_authorize; + + free(p_charbuf); + p_charbuf = strdup(c->content); + s->auth_token = strdup(get_arg1(p_charbuf)); + if (!strcmp(mpd.wss_auth_token, s->auth_token)) + s->authorized = 1; + + n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"authorized\", \"data\":\"%s\"}", + s->authorized ? "true" : "false"); + out_authorize: + free(p_charbuf); + break; case MPD_API_UPDATE_DB: mpd_run_update(mpd.conn, NULL); break; @@ -332,8 +363,13 @@ int callback_mpd(struct mg_connection *c) { int mpd_close_handler(struct mg_connection *c) { /* Cleanup session data */ - if (c->connection_param) + if (c->connection_param) { + struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param; + if (s->auth_token) + free(s->auth_token); free(c->connection_param); + } + return 0; } @@ -343,6 +379,14 @@ static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) { if (!c->is_websocket) return MG_TRUE; + if (!c->connection_param) + return MG_TRUE; + + struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param; + + if (!s->authorized) + return MG_TRUE; + if (c->callback_param) { /* error message? */ n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}", @@ -352,11 +396,6 @@ static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) { return MG_TRUE; } - if (!c->connection_param) - c->connection_param = calloc(1, sizeof(struct t_mpd_client_session)); - - struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param; - if (mpd.conn_state != MPD_CONNECTED) { n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}"); mg_websocket_write(c, 1, mpd.buf, n); @@ -444,6 +483,11 @@ void mpd_poll(struct mg_server *s) { c->callback_param = NULL; mpd_notify_callback(c, MG_POLL); } + mpd.buf_size = mpd_put_channels(mpd.buf); + for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) { + c->callback_param = NULL; + mpd_notify_callback(c, MG_POLL); + } break; } } diff --git a/src/mpd_client.h b/src/mpd_client.h index fe6e9f7..ca9c6e3 100644 --- a/src/mpd_client.h +++ b/src/mpd_client.h @@ -34,6 +34,8 @@ #define MAX_SIZE 1024 * 100 #define MAX_ELEMENTS_PER_PAGE 512 +#define WSS_AUTH_TOKEN_SIZE 50 + #define GEN_ENUM(X) X, #define GEN_STR(X) #X, #define MPD_CMDS(X) \ @@ -68,7 +70,8 @@ X(MPD_API_TOGGLE_CONSUME) \ X(MPD_API_TOGGLE_SINGLE) \ X(MPD_API_TOGGLE_CROSSFADE) \ - X(MPD_API_TOGGLE_REPEAT) + X(MPD_API_TOGGLE_REPEAT) \ + X(MPD_API_AUTHORIZE) enum mpd_cmd_ids { MPD_CMDS(GEN_ENUM) }; @@ -86,6 +89,7 @@ struct t_mpd { char host[128]; char *password; char *gpass; + char *wss_auth_token; struct mpd_connection *conn; enum mpd_conn_states conn_state; @@ -103,6 +107,8 @@ extern struct t_mpd mpd; struct t_mpd_client_session { int song_id; unsigned queue_version; + int authorized; + char *auth_token; }; void mpd_poll(struct mg_server *s);