2018-10-14 00:08:33 +02:00
/*
* This file was part of Clementine .
* Copyright 2012 , 2014 , John Maguire < john . maguire @ gmail . com >
* Copyright 2014 , Krzysztof Sobiecki < sobkas @ gmail . com >
2019-04-15 22:17:40 +02:00
* Copyright 2018 - 2019 , Jonas Kvinge < jonas @ jkvinge . net >
2018-10-14 00:08:33 +02:00
*
* Strawberry is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Strawberry is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with Strawberry . If not , see < http : //www.gnu.org/licenses/>.
*
*/
# include "localredirectserver.h"
2019-04-16 17:48:11 +02:00
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
# include <gnutls/abstract.h>
2019-04-15 22:17:40 +02:00
2018-10-14 00:08:33 +02:00
# include <QApplication>
2019-04-15 22:44:21 +02:00
# include <QIODevice>
2018-10-14 00:08:33 +02:00
# include <QBuffer>
# include <QFile>
2020-02-09 02:29:35 +01:00
# include <QList>
# include <QByteArray>
# include <QString>
# include <QUrl>
# include <QIcon>
# include <QImage>
# include <QPixmap>
2018-10-14 00:08:33 +02:00
# include <QRegExp>
# include <QStyle>
2020-02-09 02:29:35 +01:00
# include <QHostAddress>
# include <QSsl>
2019-04-15 22:17:40 +02:00
# include <QSslKey>
# include <QSslCertificate>
2020-02-09 02:29:35 +01:00
# include <QSslError>
2018-10-14 00:08:33 +02:00
# include <QTcpServer>
2019-04-15 22:17:40 +02:00
# include <QAbstractSocket>
2018-10-14 00:08:33 +02:00
# include <QTcpSocket>
2019-04-15 22:17:40 +02:00
# include <QSslSocket>
2019-04-16 17:48:11 +02:00
# include <QDateTime>
2018-10-14 00:08:33 +02:00
2020-05-08 18:35:36 +02:00
LocalRedirectServer : : LocalRedirectServer ( QObject * parent )
2019-04-15 22:17:40 +02:00
: QTcpServer ( parent ) ,
2020-05-08 18:35:36 +02:00
https_ ( false ) ,
port_ ( 0 ) ,
2019-04-15 22:17:40 +02:00
socket_ ( nullptr )
{ }
2019-11-14 21:07:30 +01:00
LocalRedirectServer : : ~ LocalRedirectServer ( ) {
if ( isListening ( ) ) close ( ) ;
}
2019-04-15 22:17:40 +02:00
bool LocalRedirectServer : : GenerateCertificate ( ) {
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_global_init ( ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to initialize GnuTLS: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
return false ;
}
2019-04-15 22:17:40 +02:00
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_t key ;
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_privkey_init ( & key ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to initialize the private key structure: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-04-15 22:17:40 +02:00
2019-04-16 17:48:11 +02:00
unsigned int bits = gnutls_sec_param_to_pk_bits ( GNUTLS_PK_RSA , GNUTLS_SEC_PARAM_MEDIUM ) ;
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_privkey_generate ( key , GNUTLS_PK_RSA , bits , 0 ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to generate random private key: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-04-15 22:17:40 +02:00
2019-04-16 17:48:11 +02:00
char buffer [ 4096 ] = " " ;
size_t buffer_size = sizeof ( buffer ) ;
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_privkey_export ( key , GNUTLS_X509_FMT_PEM , buffer , & buffer_size ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed export private key: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
gnutls_x509_privkey_deinit ( key ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-04-15 22:17:40 +02:00
2019-04-16 17:48:11 +02:00
QSslKey ssl_key ( QByteArray ( buffer , buffer_size ) , QSsl : : Rsa ) ;
if ( ssl_key . isNull ( ) ) {
2019-05-21 22:51:53 +02:00
error_ = QString ( " Failed to generate random private key. " ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_global_deinit ( ) ;
2019-04-15 22:17:40 +02:00
return false ;
}
2019-04-16 17:48:11 +02:00
gnutls_x509_crt_t crt ;
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_init ( & crt ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to initialize an X.509 certificate structure: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_set_version ( crt , 1 ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to set the version of the certificate: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_set_dn_by_oid ( crt , GNUTLS_OID_X520_COUNTRY_NAME , 0 , " US " , 2 ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to set part of the name of the certificate subject: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_set_dn_by_oid ( crt , GNUTLS_OID_X520_ORGANIZATION_NAME , 0 , " Strawberry Music Player " , strlen ( " Strawberry Music Player " ) ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to set part of the name of the certificate subject: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_set_dn_by_oid ( crt , GNUTLS_OID_X520_COMMON_NAME , 0 , " localhost " , strlen ( " localhost " ) ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to set part of the name of the certificate subject: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_set_key ( crt , key ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to set the public parameters from the given private key to the certificate: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
return false ;
}
quint64 time = QDateTime : : currentDateTime ( ) . toTime_t ( ) ;
gnutls_x509_crt_set_activation_time ( crt , time ) ;
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_set_expiration_time ( crt , time + 31536000L ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to set the activation time of the certificate: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
2019-04-15 22:17:40 +02:00
return false ;
}
2019-04-16 17:48:11 +02:00
quint64 serial = ( 9999999 + qrand ( ) % 1000000 ) ;
QByteArray q_serial ;
q_serial . setNum ( serial ) ;
2019-04-15 22:44:21 +02:00
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_set_serial ( crt , q_serial . constData ( ) , sizeof ( q_serial . size ( ) ) ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to set the serial of the certificate: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-04-15 22:17:40 +02:00
2019-04-16 17:48:11 +02:00
gnutls_privkey_t pkey ;
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_privkey_init ( & pkey ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to initialize a private key object: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_global_deinit ( ) ;
return false ;
}
if ( int result = gnutls_privkey_import_x509 ( pkey , key , 0 ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to import the given private key to the abstract private key object: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_privkey_deinit ( pkey ) ;
gnutls_global_deinit ( ) ;
return false ;
}
if ( int result = gnutls_x509_crt_privkey_sign ( crt , crt , pkey , GNUTLS_DIG_SHA256 , 0 ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to sign the certificate with the issuer's private key: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_privkey_deinit ( pkey ) ;
gnutls_global_deinit ( ) ;
return false ;
}
2019-04-16 17:48:11 +02:00
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_sign2 ( crt , crt , key , GNUTLS_DIG_SHA256 , 0 ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to sign the certificate: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_privkey_deinit ( pkey ) ;
gnutls_global_deinit ( ) ;
2019-04-15 22:17:40 +02:00
return false ;
}
2019-05-21 22:51:53 +02:00
if ( int result = gnutls_x509_crt_export ( crt , GNUTLS_X509_FMT_PEM , buffer , & buffer_size ) ! = GNUTLS_E_SUCCESS ) {
error_ = QString ( " Failed to export the certificate to PEM format: %1 " ) . arg ( gnutls_strerror ( result ) ) ;
2019-04-16 17:48:11 +02:00
gnutls_x509_privkey_deinit ( key ) ;
gnutls_x509_crt_deinit ( crt ) ;
gnutls_privkey_deinit ( pkey ) ;
gnutls_global_deinit ( ) ;
return false ;
}
gnutls_x509_crt_deinit ( crt ) ;
gnutls_x509_privkey_deinit ( key ) ;
gnutls_privkey_deinit ( pkey ) ;
2019-04-15 22:17:40 +02:00
2019-04-16 17:48:11 +02:00
QSslCertificate ssl_certificate ( QByteArray ( buffer , buffer_size ) ) ;
if ( ssl_certificate . isNull ( ) ) {
2019-05-21 22:51:53 +02:00
error_ = " Failed to generate random client certificate. " ;
2019-04-16 17:48:11 +02:00
gnutls_global_deinit ( ) ;
2019-04-15 22:17:40 +02:00
return false ;
}
2019-04-16 17:48:11 +02:00
gnutls_global_deinit ( ) ;
2019-04-15 22:17:40 +02:00
ssl_certificate_ = ssl_certificate ;
ssl_key_ = ssl_key ;
return true ;
}
2018-10-14 00:08:33 +02:00
2019-04-15 22:17:40 +02:00
bool LocalRedirectServer : : Listen ( ) {
2018-10-14 00:08:33 +02:00
2019-04-15 22:17:40 +02:00
if ( https_ ) {
if ( ! GenerateCertificate ( ) ) return false ;
}
2020-05-08 18:35:36 +02:00
if ( ! listen ( QHostAddress : : LocalHost , port_ ) ) {
2019-04-15 22:17:40 +02:00
error_ = errorString ( ) ;
return false ;
}
if ( https_ ) url_ . setScheme ( " https " ) ;
else url_ . setScheme ( " http " ) ;
2018-10-14 00:08:33 +02:00
url_ . setHost ( " localhost " ) ;
2019-04-15 22:17:40 +02:00
url_ . setPort ( serverPort ( ) ) ;
2018-10-14 00:08:33 +02:00
url_ . setPath ( " / " ) ;
2019-04-15 22:17:40 +02:00
connect ( this , SIGNAL ( newConnection ( ) ) , this , SLOT ( NewConnection ( ) ) ) ;
return true ;
2018-10-14 00:08:33 +02:00
}
void LocalRedirectServer : : NewConnection ( ) {
2019-04-15 22:17:40 +02:00
while ( hasPendingConnections ( ) ) {
incomingConnection ( nextPendingConnection ( ) - > socketDescriptor ( ) ) ;
}
}
void LocalRedirectServer : : incomingConnection ( qintptr socket_descriptor ) {
if ( socket_ ) {
if ( socket_ - > state ( ) = = QAbstractSocket : : ConnectedState ) socket_ - > close ( ) ;
socket_ - > deleteLater ( ) ;
socket_ = nullptr ;
}
buffer_ . clear ( ) ;
if ( https_ ) {
QSslSocket * ssl_socket = new QSslSocket ( this ) ;
if ( ! ssl_socket - > setSocketDescriptor ( socket_descriptor ) ) {
delete ssl_socket ;
2019-04-16 17:48:11 +02:00
close ( ) ;
2019-04-15 22:17:40 +02:00
error_ = " Unable to set socket descriptor " ;
emit Finished ( ) ;
return ;
}
ssl_socket - > ignoreSslErrors ( { QSslError : : SelfSignedCertificate } ) ;
ssl_socket - > setPrivateKey ( ssl_key_ ) ;
ssl_socket - > setLocalCertificate ( ssl_certificate_ ) ;
ssl_socket - > setProtocol ( QSsl : : TlsV1_2 ) ;
ssl_socket - > startServerEncryption ( ) ;
connect ( ssl_socket , SIGNAL ( sslErrors ( QList < QSslError > ) ) , this , SLOT ( SSLErrors ( QList < QSslError > ) ) ) ;
2019-04-16 17:48:11 +02:00
connect ( ssl_socket , SIGNAL ( encrypted ( ) ) , this , SLOT ( Encrypted ( ) ) ) ;
2019-04-15 22:17:40 +02:00
socket_ = ssl_socket ;
}
else {
QTcpSocket * tcp_socket = new QTcpSocket ( this ) ;
if ( ! tcp_socket - > setSocketDescriptor ( socket_descriptor ) ) {
delete tcp_socket ;
2019-04-16 17:48:11 +02:00
close ( ) ;
2019-04-15 22:17:40 +02:00
error_ = " Unable to set socket descriptor " ;
emit Finished ( ) ;
return ;
}
socket_ = tcp_socket ;
}
connect ( socket_ , SIGNAL ( connected ( ) ) , this , SLOT ( Connected ( ) ) ) ;
connect ( socket_ , SIGNAL ( disconnected ( ) ) , this , SLOT ( Disconnected ( ) ) ) ;
connect ( socket_ , SIGNAL ( readyRead ( ) ) , this , SLOT ( ReadyRead ( ) ) ) ;
2018-10-14 00:08:33 +02:00
}
2019-09-15 20:27:32 +02:00
void LocalRedirectServer : : SSLErrors ( const QList < QSslError > & errors ) { Q_UNUSED ( errors ) ; }
2019-04-15 22:17:40 +02:00
void LocalRedirectServer : : Encrypted ( ) { }
void LocalRedirectServer : : Connected ( ) { }
void LocalRedirectServer : : Disconnected ( ) { }
void LocalRedirectServer : : ReadyRead ( ) {
buffer_ . append ( socket_ - > readAll ( ) ) ;
if ( socket_ - > atEnd ( ) | | buffer_ . endsWith ( " \r \n \r \n " ) ) {
WriteTemplate ( ) ;
socket_ - > close ( ) ;
socket_ - > deleteLater ( ) ;
socket_ = nullptr ;
request_url_ = ParseUrlFromRequest ( buffer_ ) ;
close ( ) ;
2018-10-14 00:08:33 +02:00
emit Finished ( ) ;
}
else {
2019-04-15 22:17:40 +02:00
connect ( socket_ , SIGNAL ( readyRead ( ) ) , this , SLOT ( ReadyRead ( ) ) ) ;
2018-10-14 00:08:33 +02:00
}
2019-04-15 22:17:40 +02:00
2018-10-14 00:08:33 +02:00
}
2019-04-15 22:17:40 +02:00
void LocalRedirectServer : : WriteTemplate ( ) const {
2018-10-14 00:08:33 +02:00
2019-02-13 20:04:05 +01:00
QFile page_file ( " :/html/oauthsuccess.html " ) ;
2018-10-14 00:08:33 +02:00
page_file . open ( QIODevice : : ReadOnly ) ;
QString page_data = QString : : fromUtf8 ( page_file . readAll ( ) ) ;
QRegExp tr_regexp ( " tr \\ ( \" ([^ \" ]+) \ " \\ ) " ) ;
int offset = 0 ;
forever {
offset = tr_regexp . indexIn ( page_data , offset ) ;
if ( offset = = - 1 ) {
break ;
}
page_data . replace ( offset , tr_regexp . matchedLength ( ) , tr ( tr_regexp . cap ( 1 ) . toUtf8 ( ) ) ) ;
offset + = tr_regexp . matchedLength ( ) ;
}
QBuffer image_buffer ;
image_buffer . open ( QIODevice : : ReadWrite ) ;
QApplication : : style ( )
- > standardIcon ( QStyle : : SP_DialogOkButton )
. pixmap ( 16 )
. toImage ( )
. save ( & image_buffer , " PNG " ) ;
page_data . replace ( " @IMAGE_DATA@ " , image_buffer . data ( ) . toBase64 ( ) ) ;
2019-04-15 22:17:40 +02:00
socket_ - > write ( " HTTP/1.0 200 OK \r \n " ) ;
socket_ - > write ( " Content-type: text/html;charset=UTF-8 \r \n " ) ;
socket_ - > write ( " \r \n \r \n " ) ;
socket_ - > write ( page_data . toUtf8 ( ) ) ;
socket_ - > flush ( ) ;
2018-10-14 00:08:33 +02:00
}
2019-04-15 22:17:40 +02:00
QUrl LocalRedirectServer : : ParseUrlFromRequest ( const QByteArray & request ) const {
2018-10-14 00:08:33 +02:00
QList < QByteArray > lines = request . split ( ' \r ' ) ;
2019-04-15 22:17:40 +02:00
const QByteArray & request_line = lines [ 0 ] ;
2018-10-14 00:08:33 +02:00
QByteArray path = request_line . split ( ' ' ) [ 1 ] ;
2019-04-15 22:17:40 +02:00
QUrl base_url = url_ ;
2018-10-14 00:08:33 +02:00
QUrl request_url ( base_url . toString ( ) + path . mid ( 1 ) , QUrl : : StrictMode ) ;
return request_url ;
2019-04-15 22:17:40 +02:00
2018-10-14 00:08:33 +02:00
}