Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTPD output: Added support for HTTP Basic authentication #1968

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

Mrkvak
Copy link

@Mrkvak Mrkvak commented Jan 17, 2024

I want to have a publicly available streaming MPD server available from internet. However I want to protect the output with password - if not for anything else then for legal reasons (the moment someone other than me opens the stream I break the law).

This adds new optional "password" option for "httpd" output in mpd.conf. If it is specified and non-empty, it requires HTTP Basic authentication with arbitrary user and set password.

(It's been some time since I've wrote anything in C++, so please check the code if I haven't made any stupid mistakes. I've tested it and it seems to be working as intended in both cases.)

src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
src/output/plugins/httpd/HttpdOutputPlugin.cxx Outdated Show resolved Hide resolved
src/output/plugins/httpd/HttpdClient.hxx Outdated Show resolved Hide resolved
src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
@@ -102,6 +112,15 @@ HttpdClient::HandleLine(const char *line) noexcept
return true;
}

if (StringEqualsCaseASCII(line, "Authorization: Basic ", 21)) {
auto encoded_pwd_length = strlen(line) - 21;
size_t debase64_size = CalculateBase64OutputSize(encoded_pwd_length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant code - there's a DecodeBase64() overload which returns AllocatedArray. You can then cast this buffer to std::string_view (e.g. via ToStringView() from util/SpanCast.hxx) and operate on this buffer.

This is not only simpler and doesn't add redundant code, but is also faster because it reduces the two strlen() calls in your code to just one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. Should be done, hopefully correctly.

src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
@@ -283,6 +283,7 @@ input {
# bitrate "128" # do not define if quality is defined
# format "44100:16:1"
# max_clients "0" # optional 0=no limit
# password "secretPassword" # protect stream with HTTP Basic authentication (user can be anything)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user can be anything

Note that username cannot contain :, since then splitting username and password would result in part of the username being considered as part of the password.

Also, according to RFC 7617:

The user-id and password MUST NOT contain any control characters (see "CTL" in Appendix B.1 of [RFC5234]).

Perhaps we should enforce some hardcoded username, like mpd, to avoid misuse?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've explicitly written in documentation can contain all printable characters except colon (the code does not check it, though, but I don't think it should be necessary).

doc/mpdconf.example Outdated Show resolved Hide resolved
src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
@kingosticks
Copy link
Contributor

I'm sorry to be that guy but I really don't understand why you'd bother with the massive potential headache of supporting this "security" in mpd when it would be easier and safer for users to use an existing reverse-proxy server. They'd get something well tested with support for a variety of auth mechanisms. Am I missing something?

@MaxKellermann
Copy link
Member

when it would be easier and safer for users to use an existing reverse-proxy server

The whole idea of the httpd output plugin is that MPD gives you something with batteries included; it may have fewer features than other solutions (Icecast or nginx), but you can do most things with just MPD and nothing else.

Of course, this may replicate features that are available with more complex solutions, but MPD's httpd may be the sweet spot for some users. Others want a fully-featured solution which comes with complexity.

For those people who want to make a stream available but want some protection, like an unencrypted password on an unencrypted HTTP connection - then it's fine to add this feature. It's weak, but for some people, it's just the right amount of security. Sure, somebody could sniff the password from the wire, and then they can listen to the stream. But the MPD user can't be accused of copyright violation by broadcasting copyrighted works - it's not a broadcast if it's protected, even if the protection is weak.

I understand your complaint, and I know this plugin and this PR is very imperfect, but it suits some kind of MPD users. Not me, and not you. But that's okay.

@kingosticks
Copy link
Contributor

That's fair enough. My only further comment is that it's not obvious how secure this is trying to be. Maybe a note in the appropriate documentation would help. We obviously can't rely on GitHub comments.

@MaxKellermann
Copy link
Member

Maybe a note in the appropriate documentation would help.

Agree. The presence of this feature should not pretend it's truly secure, when it cannot possibly be (because the transport is not secure).

@bubbleguuum
Copy link

Basic authentication is fairly secure when used in conjunction with https.
Otherwise Digest authentication is better, but more complicated to implement.

@MaxKellermann
Copy link
Member

Looks like @Mrkvak is not interested in fixing the issues, and I only wasted my time with the code review.

@Mrkvak
Copy link
Author

Mrkvak commented Mar 11, 2024

Damn, sorrry, I've totally forgot this is still open.
@MaxKellermann can you please reopen it? I'll look at it later today.

@MaxKellermann MaxKellermann reopened this Mar 11, 2024
@Mrkvak
Copy link
Author

Mrkvak commented Mar 12, 2024

@kingosticks @MaxKellermann basically explained my motivation behind this. If someone sniffs the password, they can also sniff the audio stream. My goal was to have MPD not publicly broadcasting when accessible from network/internet. Nothing more, nothing less.
I've added clear warning that the password is transmitted over unencrypted connection - it should be obvious, but I think it's still a good idea.

I think all feedback (thanks a lot for the review, by the way) was implemented, I've tested the code and it seems to be working.

@MaxKellermann
Copy link
Member

Ouch, the PR branch now contains merge commits. This is a big mess that cannot be reviewed. Please fix this.

@Mrkvak Mrkvak force-pushed the feature/http-basic-auth branch 2 times, most recently from 358c240 to d1ad6d1 Compare March 15, 2024 17:51
@Mrkvak
Copy link
Author

Mrkvak commented Mar 15, 2024

Sigh... I guess that's my fault for trying to use github gui. Should be rebased on MPD/master without any pesky merge commits.

@MaxKellermann
Copy link
Member

There are now commits which obviously fix mistakes in earlier commits of this PR, e.g. "working version". I don't want to read known-bad commits and find all those mistakes again, only to see later that those mistakes have been fixed already. If you find a mistake in a commit that is not yet merged, please amend the commit instead of adding a fixup commit.

@Mrkvak
Copy link
Author

Mrkvak commented Mar 25, 2024

Squashed

src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
#include "net/SocketError.hxx"
#include "net/UniqueSocketDescriptor.hxx"
#include "util/SpanCast.hxx"
#include "util/StringCompare.hxx"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why include this header?

Copy link
Author

@Mrkvak Mrkvak Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed leftover, sorry.
Actually no, it's to include StringAfterPrefixIgnoreCase

src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
@@ -41,6 +50,10 @@ HttpdClient::BeginResponse() noexcept
{
assert(state != State::RESPONSE);

if (!head_method) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the head_method check?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary to password protect HEAD requests, is it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You wrote extra code to implement a special case to allow HEAD requests. What justifies this extra complexity?
Your answer isn't good because extra complexity should be justified, not the other way round.

src/output/plugins/httpd/HttpdClient.cxx Outdated Show resolved Hide resolved
/**
* Should we reject this request as unauthorized?
*/
bool should_reject_unauthorized = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field should only exist when the feature is available

@@ -169,7 +200,8 @@ HttpdClient::HttpdClient(HttpdOutput &_httpd, UniqueSocketDescriptor _fd,
bool _metadata_supported)
:BufferedSocket(_fd.Release(), _loop),
httpd(_httpd),
metadata_supported(_metadata_supported)
metadata_supported(_metadata_supported),
provided_password{""}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of this initializer?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's needed after the last simplification, sorry I missed it.

/**
* Provided password (using HTTP basic auth)
*/
std::string provided_password;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field should only exist when the feature is available

/**
* The configured password.
*/
char const *password;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field should only exist when the feature is available and it should be const

src/output/plugins/httpd/HttpdInternal.hxx Show resolved Hide resolved
@MaxKellermann
Copy link
Member

"If you find a mistake in a commit that is not yet merged, please amend the commit instead of adding a fixup commit."

@MaxKellermann MaxKellermann added the waiting Waiting for more information from reporter label May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting Waiting for more information from reporter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants