Ticket #10310: RAOP_http_digest.2.patch

File RAOP_http_digest.2.patch, 31.9 KB (added by mythtv@…, 12 years ago)
  • mythtv/libs/libmythtv/mythraopconnection.cpp

    diff --git a/mythtv/libs/libmythtv/mythraopconnection.cpp b/mythtv/libs/libmythtv/mythraopconnection.cpp
    index 66b97c7..5ad43b7 100644
    a b  
    66#include <QUdpSocket>
    77#include <QtEndian>
    88
     9#include <QFile>
     10#include <QCryptographicHash>
    911#include "mythlogging.h"
    1012#include "mythcorecontext.h"
    1113
     
    1416
    1517#include "mythraopdevice.h"
    1618#include "mythraopconnection.h"
     19#include "mythdirs.h"
    1720
    1821#define LOC QString("RAOP Conn: ")
    1922#define MAX_PACKET_SIZE  2048
    2023#define DEFAULT_SAMPLE_RATE 44100
    2124
    22 RSA* MythRAOPConnection::g_rsa = NULL;
     25RSA *MythRAOPConnection::g_rsa = NULL;
    2326
    2427MythRAOPConnection::MythRAOPConnection(QTcpSocket *socket, QByteArray id, int port)
    25   : m_watchdogTimer(NULL), m_socket(socket), m_hardwareId(id), m_dataPort(port),
    26     m_dataSocket(NULL), m_clientControlSocket(NULL), m_clientControlPort(0),
    27     m_audio(NULL), m_codec(NULL), m_codeccontext(NULL),
    28     m_sampleRate(DEFAULT_SAMPLE_RATE), m_allowVolumeControl(true),
    29     m_seenPacket(false), m_lastPacketSequence(0), m_lastPacketTimestamp(0),
    30     m_lastSyncTime(0), m_lastSyncTimestamp(0), m_lastLatency(0), m_latencyAudio(0),
    31     m_latencyQueued(0), m_latencyCounter(0), m_avSync(0)
     28    : m_watchdogTimer(NULL), m_socket(socket), m_hardwareId(id), m_dataPort(port),
     29      m_dataSocket(NULL), m_clientControlSocket(NULL), m_clientControlPort(0),
     30      m_audio(NULL), m_codec(NULL), m_codeccontext(NULL),
     31      m_sampleRate(DEFAULT_SAMPLE_RATE), m_allowVolumeControl(true),
     32      m_seenPacket(false), m_authed(false), m_nonce(""),
     33      m_lastPacketSequence(0), m_lastPacketTimestamp(0),
     34      m_lastSyncTime(0), m_lastSyncTimestamp(0), m_lastLatency(0), m_latencyAudio(0),
     35      m_latencyQueued(0), m_latencyCounter(0), m_avSync(0)
    3236{
    3337}
    3438
    MythRAOPConnection::~MythRAOPConnection() 
    3741    // stop and delete watchdog timer
    3842    if (m_watchdogTimer)
    3943        m_watchdogTimer->stop();
     44
    4045    delete m_watchdogTimer;
    4146    m_watchdogTimer = NULL;
    4247
    MythRAOPConnection::~MythRAOPConnection() 
    4651        m_socket->close();
    4752        m_socket->deleteLater();
    4853    }
     54
    4955    m_socket = NULL;
    5056
    5157    // delete data socket
    MythRAOPConnection::~MythRAOPConnection() 
    5561        m_dataSocket->close();
    5662        m_dataSocket->deleteLater();
    5763    }
     64
    5865    m_dataSocket = NULL;
    5966
    6067    // client control socket
    MythRAOPConnection::~MythRAOPConnection() 
    6471        m_clientControlSocket->close();
    6572        m_clientControlSocket->deleteLater();
    6673    }
     74
    6775    m_clientControlSocket = NULL;
    6876
    6977    // close audio decoder
    bool MythRAOPConnection::Init(void) 
    8189    // connect up the request socket
    8290    m_textStream = new QTextStream(m_socket);
    8391    m_textStream->setCodec("UTF-8");
     92
    8493    if (!connect(m_socket, SIGNAL(readyRead()), this, SLOT(readClient())))
    8594    {
    8695        LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to connect client socket signal.");
    bool MythRAOPConnection::Init(void) 
    8998
    9099    // create the data socket
    91100    m_dataSocket = new QUdpSocket();
     101
    92102    if (!connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(udpDataReady())))
    93103    {
    94104        LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to connect data socket signal.");
    bool MythRAOPConnection::Init(void) 
    97107
    98108    // try a few ports in case the first is in use
    99109    int baseport = m_dataPort;
     110
    100111    while (m_dataPort < baseport + RAOP_PORT_RANGE)
    101112    {
    102113        if (m_dataSocket->bind(gCoreContext->MythHostAddressAny(), m_dataPort))
    bool MythRAOPConnection::Init(void) 
    105116                QString("Bound to port %1 for incoming data").arg(m_dataPort));
    106117            break;
    107118        }
     119
    108120        m_dataPort++;
    109121    }
    110122
    bool MythRAOPConnection::Init(void) 
    131143
    132144void MythRAOPConnection::udpDataReady()
    133145{
    134     QUdpSocket *sender = (QUdpSocket*)this->sender();
     146    QUdpSocket *sender = (QUdpSocket *)this->sender();
     147
    135148    if (!sender)
    136149        return;
    137150
    void MythRAOPConnection::udpDataReady() 
    146159
    147160    // read datagrams
    148161    QByteArray buf;
     162
    149163    while (sender->state() == QAbstractSocket::BoundState &&
    150164           sender->hasPendingDatagrams())
    151165    {
    void MythRAOPConnection::udpDataReady() 
    153167        QHostAddress peer;
    154168        quint16 port;
    155169
    156         sender->readDatagram(buf.data(), buf.size(),&peer, &port);
     170        sender->readDatagram(buf.data(), buf.size(), &peer, &port);
    157171
    158172        if (!m_audio || !m_codec || !m_codeccontext)
    159173            continue;
    void MythRAOPConnection::udpDataReady() 
    168182
    169183        int offset = type == 0x60 ? 0 : 4;
    170184        uint16_t this_sequence  = ntohs(*(uint16_t *)(buf.data() + offset + 2));
    171         uint64_t this_timestamp = FramesToMs(ntohl(*(uint64_t*)(buf.data() + offset + 4)));
     185        uint64_t this_timestamp = FramesToMs(ntohl(*(uint64_t *)(buf.data() + offset + 4)));
    172186        uint16_t expected_sequence = m_lastPacketSequence + 1; // should wrap ok
    173187
    174188        // regular data packet
    void MythRAOPConnection::udpDataReady() 
    199213        ExpireResendRequests(timenow);
    200214
    201215        offset += 12;
    202         char* data_in = buf.data() + offset;
     216        char *data_in = buf.data() + offset;
    203217        int len       = buf.size() - offset;
     218
    204219        if (len < 16)
    205220            continue;
    206221
    void MythRAOPConnection::udpDataReady() 
    208223        unsigned char iv[16];
    209224        unsigned char decrypted_data[MAX_PACKET_SIZE];
    210225        memcpy(iv, m_AESIV.data(), sizeof(iv));
    211         AES_cbc_encrypt((const unsigned char*)data_in,
     226        AES_cbc_encrypt((const unsigned char *)data_in,
    212227                        decrypted_data, aeslen,
    213228                        &m_aesKey, iv, AES_DECRYPT);
    214229        memcpy(decrypted_data + aeslen, data_in + aeslen, len - aeslen);
    void MythRAOPConnection::udpDataReady() 
    218233        tmp_pkt.data = decrypted_data;
    219234        tmp_pkt.size = len;
    220235        int decoded_data_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
    221         int16_t* samples = (int16_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof(int32_t));
     236        int16_t *samples = (int16_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof(int32_t));
    222237        int used = avcodec_decode_audio3(m_codeccontext, samples,
    223238                                         &decoded_data_size, &tmp_pkt);
     239
    224240        if (used != len)
    225241        {
    226242            LOG(VB_GENERAL, LOG_WARNING, LOC +
    void MythRAOPConnection::SendResendRequest(uint64_t timenow, 
    256272        return;
    257273
    258274    int16_t missed = (got < expected) ?
    259                 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
    260                 got - expected;
     275                     (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
     276                     got - expected;
    261277
    262278    LOG(VB_GENERAL, LOG_INFO, LOC + QString("Missed %1 packet(s): expected %2 got %3")
    263279        .arg(missed).arg(expected).arg(got));
    void MythRAOPConnection::ExpireResendRequests(uint64_t timenow) 
    288304        return;
    289305
    290306    uint64_t too_old = timenow - 250;
    291     QMutableMapIterator<uint16_t,uint64_t> it(m_resends);
     307    QMutableMapIterator<uint16_t, uint64_t> it(m_resends);
     308
    292309    while (it.hasNext())
    293310    {
    294311        it.next();
     312
    295313        if (it.value() < too_old)
    296314        {
    297315            LOG(VB_GENERAL, LOG_WARNING, LOC +
    void MythRAOPConnection::ExpireResendRequests(uint64_t timenow) 
    303321
    304322void MythRAOPConnection::ProcessSyncPacket(const QByteArray &buf, uint64_t timenow)
    305323{
    306     m_lastSyncTimestamp = FramesToMs(ntohl(*(uint64_t*)(buf.data() + 4)));
    307     uint64_t now   = FramesToMs(ntohl(*(uint64_t*)(buf.data() + 16)));
     324    m_lastSyncTimestamp = FramesToMs(ntohl(*(uint64_t *)(buf.data() + 4)));
     325    uint64_t now   = FramesToMs(ntohl(*(uint64_t *)(buf.data() + 16)));
    308326    m_lastLatency  = now - m_lastSyncTimestamp;
    309327    m_lastSyncTime = timenow;
    310328    uint64_t averageaudio = 0;
    311329    uint64_t averagequeue = 0;
    312330    double   averageav    = 0;
     331
    313332    if (m_latencyCounter)
    314333    {
    315334        averageaudio = m_latencyAudio / m_latencyCounter;
    void MythRAOPConnection::ProcessSyncPacket(const QByteArray &buf, uint64_t timen 
    326345            .arg(averageaudio).arg(averagequeue)
    327346            .arg(averageaudio + averagequeue).arg(m_lastLatency));
    328347    }
     348
    329349    m_latencyAudio = m_latencyQueued = m_latencyCounter = m_avSync = 0;
    330350}
    331351
    332352int MythRAOPConnection::ExpireAudio(uint64_t timestamp)
    333353{
    334354    int res = 0;
    335     QMutableMapIterator<uint64_t,int16_t*> it(m_audioQueue);
     355    QMutableMapIterator<uint64_t, int16_t *> it(m_audioQueue);
     356
    336357    while (it.hasNext())
    337358    {
    338359        it.next();
     360
    339361        if (it.key() < timestamp)
    340362        {
    341             av_freep((void *)&(it.value()));
     363            av_freep((void *) & (it.value()));
    342364            m_audioQueue.remove(it.key());
    343365            res++;
    344366        }
    345367    }
     368
    346369    return res;
    347370}
    348371
    void MythRAOPConnection::ProcessAudio(uint64_t timenow) 
    358381
    359382    // expire anything that is late
    360383    int dumped = ExpireAudio(updatedsync);
     384
    361385    if (dumped > 0)
    362386    {
    363387        LOG(VB_GENERAL, LOG_INFO, LOC + QString("Dumped %1 audio packets")
    void MythRAOPConnection::ProcessAudio(uint64_t timenow) 
    373397    m_latencyQueued += queue_length;
    374398    m_latencyCounter++;
    375399
    376     QMapIterator<uint64_t,int16_t*> it(m_audioQueue);
     400    QMapIterator<uint64_t, int16_t *> it(m_audioQueue);
     401
    377402    while (it.hasNext())
    378403    {
    379404        it.next();
     405
    380406        if (it.key() < ideal_ts)
    381407            m_audio->AddData(it.value(), 1408 /* FIXME */, it.key(), 352);
    382408    }
    void MythRAOPConnection::ResetAudio(void) 
    389415    ExpireAudio(UINT64_MAX);
    390416    m_latencyCounter = m_latencyAudio = m_latencyQueued = m_avSync = 0;
    391417    m_seenPacket = false;
     418
    392419    if (m_audio)
    393420        m_audio->Reset();
    394421}
    void MythRAOPConnection::timeout(void) 
    402429void MythRAOPConnection::readClient(void)
    403430{
    404431    QTcpSocket *socket = (QTcpSocket *)sender();
     432
    405433    if (!socket)
    406434        return;
    407435
    408436    QList<QByteArray> lines;
     437
    409438    while (socket->canReadLine())
    410439    {
    411440        QByteArray line = socket->readLine();
    void MythRAOPConnection::readClient(void) 
    417446        ProcessRequest(lines);
    418447}
    419448
     449
     450QString MythRAOPConnection::GenerateNonce(void)
     451{
     452    int nonceParts[4];
     453    QString nonce;
     454    QTime time = QTime::currentTime();
     455    qsrand((uint)time.msec());
     456    nonceParts[0] = qrand();
     457    nonceParts[1] = qrand();
     458    nonceParts[2] = qrand();
     459    nonceParts[3] = qrand();
     460
     461
     462    nonce =  QString::number(nonceParts[0], 16).toUpper();
     463    nonce += QString::number(nonceParts[1], 16).toUpper();
     464    nonce += QString::number(nonceParts[2], 16).toUpper();
     465    nonce += QString::number(nonceParts[3], 16).toUpper();
     466    return nonce;
     467}
    420468void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines)
    421469{
    422470    if (lines.isEmpty())
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    432480
    433481    QByteArray option = lines[0].left(lines[0].indexOf(" "));
    434482
     483    bool passwordAuthEnabled = gCoreContext->GetNumSettingOnHost(
     484                                   "RAOPPasswordEnabled",
     485                                   gCoreContext->GetHostName(),
     486                                   0);
     487
     488    if (passwordAuthEnabled && !m_authed)
     489    {
     490
     491        if (!tags.contains("Authorization"))
     492        {
     493            // 60 seconds to enter password.
     494            m_watchdogTimer->start(60000);
     495
     496            if (option != "OPTIONS" && option != "ANNOUNCE"
     497                && option != "TEARDOWN")
     498            {
     499                StartResponse(m_textStream);
     500                FinishResponse(m_textStream, m_socket, option, tags["Cseq"]);
     501                return;
     502            }
     503        }
     504        else
     505        {
     506
     507            if (m_nonce.isEmpty())
     508            {
     509                m_nonce = GenerateNonce();
     510            }
     511
     512            QByteArray response = tags["Authorization"].data();
     513            int authStart = response.indexOf("response=\"") + 10;
     514            QByteArray auth = response.mid(authStart, 32);
     515
     516            int uriStart = response.indexOf("uri=\"") + 5;
     517            int uriLength = response.indexOf("\"", uriStart) - uriStart;
     518            QByteArray uri = response.mid(uriStart, uriLength);
     519
     520            QString password = gCoreContext->GetSettingOnHost("RAOPPassword",
     521                                                gCoreContext->GetHostName(),
     522                                                "");
     523
     524            QString ha1input = "iTunes:raop:" + password;
     525            QString ha2input = option + ":" + uri;
     526            QString HA1, HA2, final;
     527            QByteArray hashResult;
     528
     529            m_watchdogTimer->start(10000);
     530
     531            hashResult = QCryptographicHash::hash(ha1input.toAscii(),
     532                                                    QCryptographicHash::Md5);
     533            HA1 = QString(hashResult.toHex()).toUpper();
     534               
     535            hashResult = QCryptographicHash::hash(ha2input.toAscii(),
     536                                                    QCryptographicHash::Md5);
     537            HA2 = QString(hashResult.toHex()).toUpper();
     538
     539            final = HA1 + ":" + m_nonce + ":" + HA2;
     540            hashResult = QCryptographicHash::hash(final.toAscii(),
     541                                                    QCryptographicHash::Md5);
     542            final = QString(hashResult.toHex()).toUpper();
     543
     544            if (final.toAscii().toHex() == auth.toHex())
     545            {
     546                m_authed = true;
     547                LOG(VB_GENERAL, LOG_INFO, LOC + "RAOP client Authed");
     548            }
     549            else
     550            {
     551                if (option != "OPTIONS" && option != "ANNOUNCE"
     552                    && option != "TEARDOWN")
     553                {
     554                    StartResponse(m_textStream);
     555                    FinishResponse(m_textStream, m_socket, option, tags["Cseq"]);
     556                    return;
     557                }
     558            }
     559        }
     560
     561    }
     562
    435563    if (option == "OPTIONS")
    436564    {
    437565        StartResponse(m_textStream);
     566
    438567        if (tags.contains("Apple-Challenge"))
    439568        {
    440569            *m_textStream << "Apple-Response: ";
     570
    441571            if (!LoadKey())
    442572                return;
     573
    443574            int tosize = RSA_size(LoadKey());
    444575            unsigned char to[tosize];
    445576
    446577            QByteArray challenge = QByteArray::fromBase64(tags["Apple-Challenge"].data());
    447578            int challenge_size = challenge.size();
     579
    448580            if (challenge_size != 16)
    449581            {
    450582                LOG(VB_GENERAL, LOG_ERR, LOC +
    451583                    QString("Decoded challenge size %1, expected 16").arg(challenge_size));
     584
    452585                if (challenge_size > 16)
    453586                    challenge_size = 16;
    454587            }
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    457590            unsigned char from[38];
    458591            memcpy(from, challenge.data(), challenge_size);
    459592            i += challenge_size;
     593
    460594            if (m_socket->localAddress().protocol() == QAbstractSocket::IPv4Protocol)
    461595            {
    462596                uint32_t ip = m_socket->localAddress().toIPv4Address();
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    472606                memcpy(from + i, &ip, 16);
    473607                i += 16;
    474608            }
     609
    475610            memcpy(from + i, m_hardwareId.data(), RAOP_HARDWARE_ID_SIZE);
    476611            i += RAOP_HARDWARE_ID_SIZE;
    477612
    478613            int pad = 32 - i;
     614
    479615            if (pad > 0)
    480616            {
    481617                memset(from + i, 0, pad);
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    483619            }
    484620
    485621            LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Full base64 response: '%1' size %2")
    486                 .arg(QByteArray((const char*)from, i).toBase64().data()).arg(i));
     622                .arg(QByteArray((const char *)from, i).toBase64().data()).arg(i));
    487623
    488624            RSA_private_encrypt(i, from, to, LoadKey(), RSA_PKCS1_PADDING);
    489625
    490             QByteArray base64 = QByteArray((const char*)to, tosize).toBase64();
     626            QByteArray base64 = QByteArray((const char *)to, tosize).toBase64();
    491627
    492628            for (int pos = base64.size() - 1; pos > 0; pos--)
    493629            {
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    505641    }
    506642    else if (option == "ANNOUNCE")
    507643    {
    508         foreach (QByteArray line, lines)
     644        foreach(QByteArray line, lines)
    509645        {
    510646            if (line.startsWith("a=rsaaeskey:"))
    511647            {
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    518654                {
    519655                    int size = sizeof(char) * RSA_size(LoadKey());
    520656                    char *decryptedkey = new char[size];
     657
    521658                    if (RSA_private_decrypt(decodedkey.size(),
    522                                             (const unsigned char*)decodedkey.data(),
    523                                             (unsigned char*)decryptedkey,
     659                                            (const unsigned char *)decodedkey.data(),
     660                                            (unsigned char *)decryptedkey,
    524661                                            LoadKey(), RSA_PKCS1_OAEP_PADDING))
    525662                    {
    526663                        LOG(VB_GENERAL, LOG_DEBUG, LOC +
    527664                            "Successfully decrypted AES key from RSA.");
    528                         AES_set_decrypt_key((const unsigned char*)decryptedkey,
     665                        AES_set_decrypt_key((const unsigned char *)decryptedkey,
    529666                                            128, &m_aesKey);
    530667                    }
    531668                    else
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    533670                        LOG(VB_GENERAL, LOG_WARNING, LOC +
    534671                            "Failed to decrypt AES key from RSA.");
    535672                    }
     673
    536674                    delete [] decryptedkey;
    537675                }
    538676
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    549687                m_audioFormat.clear();
    550688                QByteArray format = line.mid(7).trimmed();
    551689                QList<QByteArray> fmts = format.split(' ');
    552                 foreach (QByteArray fmt, fmts)
    553                     m_audioFormat.append(fmt.toInt());
     690                foreach(QByteArray fmt, fmts)
     691                m_audioFormat.append(fmt.toInt());
    554692
    555                 foreach (int fmt, m_audioFormat)
    556                     LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Audio parameter: %1").arg(fmt));
     693                foreach(int fmt, m_audioFormat)
     694                LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Audio parameter: %1").arg(fmt));
    557695            }
    558696        }
    559697
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    568706            int timing_port = 0;
    569707            QString data = tags["Transport"];
    570708            QStringList items = data.split(";");
    571             foreach (QString item, items)
     709            foreach(QString item, items)
    572710            {
    573711                if (item.startsWith("control_port"))
    574712                    control_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    578716
    579717            LOG(VB_GENERAL, LOG_INFO, LOC +
    580718                QString("Negotiated setup with client %1 on port %2")
    581                     .arg(m_socket->peerAddress().toString())
    582                     .arg(m_socket->peerPort()));
     719                .arg(m_socket->peerAddress().toString())
     720                .arg(m_socket->peerPort()));
    583721            LOG(VB_GENERAL, LOG_DEBUG, LOC +
    584722                QString("control port: %1 timing port: %2")
    585                     .arg(control_port).arg(timing_port));
     723                .arg(control_port).arg(timing_port));
    586724
    587725            StartResponse(m_textStream);
    588726            *m_textStream << "Transport: " << tags["Transport"];
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    600738            }
    601739
    602740            m_clientControlSocket = new QUdpSocket(this);
     741
    603742            if (!m_clientControlSocket->bind(control_port))
    604743            {
    605744                LOG(VB_GENERAL, LOG_ERR, LOC +
    void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 
    627766    }
    628767    else if (option == "SET_PARAMETER")
    629768    {
    630         foreach (QByteArray line, lines)
     769        foreach(QByteArray line, lines)
    631770        {
    632771            if (line.startsWith("volume:") && m_allowVolumeControl && m_audio)
    633772            {
    634773                QByteArray rawvol = line.mid(7).trimmed();
    635774                float volume = (rawvol.toFloat() + 30.0) / 0.3;
     775
    636776                if (volume < 0.01)
    637777                    volume = 0.0;
     778
    638779                LOG(VB_GENERAL, LOG_INFO, LOC + QString("Setting volume to %1 (raw %3)")
    639780                    .arg(volume).arg(rawvol.data()));
    640781                m_audio->SetCurrentVolume((int)volume);
    void MythRAOPConnection::StartResponse(QTextStream *stream) 
    663804{
    664805    if (!stream)
    665806        return;
     807
     808    if (gCoreContext->GetNumSettingOnHost("RAOPPasswordEnabled",
     809                                          gCoreContext->GetHostName(), 0))
     810    {
     811        if (!m_authed)
     812        {
     813
     814            if (m_nonce.isEmpty())
     815            {
     816                m_nonce = GenerateNonce();
     817            }
     818
     819            *stream << "RTSP/1.0 401 Unauthorised\r\n";
     820            *stream << "WWW-Authenticate: Digest realm=\"raop\",";
     821            *stream << " nonce=\"" + m_nonce + "\"\r\n";
     822            return;
     823        }
     824    }
     825
    666826    *stream << "RTSP/1.0 200 OK\r\n";
    667827}
    668828
    RSA* MythRAOPConnection::LoadKey(void) 
    730874RawHash MythRAOPConnection::FindTags(const QList<QByteArray> &lines)
    731875{
    732876    RawHash result;
     877
    733878    if (lines.isEmpty())
    734879        return result;
    735880
    736881    for (int i = 0; i < lines.size(); i++)
    737882    {
    738883        int index = lines[i].indexOf(":");
     884
    739885        if (index > 0)
    740886        {
    741887            result.insert(lines[i].left(index).trimmed(),
    742888                          lines[i].mid(index + 1).trimmed());
    743889        }
    744890    }
     891
    745892    return result;
    746893}
    747894
    bool MythRAOPConnection::CreateDecoder(void) 
    755902    avcodeclock->unlock();
    756903
    757904    m_codec = avcodec_find_decoder(CODEC_ID_ALAC);
     905
    758906    if (!m_codec)
    759907    {
    760908        LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create ALAC decoder- going silent...");
    bool MythRAOPConnection::CreateDecoder(void) 
    762910    }
    763911
    764912    m_codeccontext = avcodec_alloc_context();
     913
    765914    if (m_codec && m_codeccontext)
    766915    {
    767         unsigned char* extradata = new unsigned char[36];
     916        unsigned char *extradata = new unsigned char[36];
    768917        memset(extradata, 0, 36);
     918
    769919        if (m_audioFormat.size() < 12)
    770920        {
    771921            LOG(VB_GENERAL, LOG_ERR, LOC + "Creating decoder but haven't seen audio format.");
    bool MythRAOPConnection::CreateDecoder(void) 
    783933            extradata[19] = m_audioFormat[5]; // rice_initialhistory
    784934            extradata[20] = m_audioFormat[6]; // rice_kmodifier
    785935        }
     936
    786937        m_codeccontext->extradata = extradata;
    787938        m_codeccontext->extradata_size = 36;
    788939        m_codeccontext->channels = 2;
     940
    789941        if (avcodec_open(m_codeccontext, m_codec) < 0)
    790942        {
    791943            LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open ALAC decoder - going silent...");
    792944            DestroyDecoder();
    793945            return false;
    794946        }
     947
    795948        LOG(VB_GENERAL, LOG_DEBUG, LOC + "Opened ALAC decoder.");
    796949    }
    797950
    void MythRAOPConnection::DestroyDecoder(void) 
    805958        avcodec_close(m_codeccontext);
    806959        av_free(m_codeccontext);
    807960    }
     961
    808962    m_codec = NULL;
    809963    m_codeccontext = NULL;
    810964}
    bool MythRAOPConnection::OpenAudioDevice(void) 
    814968    CloseAudioDevice();
    815969
    816970    m_sampleRate = m_audioFormat.size() >= 12 ? m_audioFormat[11] : DEFAULT_SAMPLE_RATE;
     971
    817972    if (m_sampleRate < 1)
    818973        m_sampleRate = DEFAULT_SAMPLE_RATE;
    819974
    820975    QString passthru = gCoreContext->GetNumSetting("PassThruDeviceOverride", false)
    821                         ? gCoreContext->GetSetting("PassThruOutputDevice") : QString::null;
     976                       ? gCoreContext->GetSetting("PassThruOutputDevice") : QString::null;
    822977    QString device = gCoreContext->GetSetting("AudioOutputDevice");
    823978
    824979    m_audio = AudioOutput::OpenAudio(device, passthru, FORMAT_S16, 2,
    825980                                     0, m_sampleRate, AUDIOOUTPUT_MUSIC,
    826981                                     m_allowVolumeControl, false);
     982
    827983    if (!m_audio)
    828984    {
    829985        LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open audio device. Going silent...");
  • mythtv/libs/libmythtv/mythraopconnection.h

    diff --git a/mythtv/libs/libmythtv/mythraopconnection.h b/mythtv/libs/libmythtv/mythraopconnection.h
    index 8e34849..0c37047 100644
    a b class MythRAOPConnection : public QObject 
    4747    int     ExpireAudio(uint64_t timestamp);
    4848    void    ProcessAudio(uint64_t timenow);
    4949    void    ResetAudio(void);
     50    QString GenerateNonce(void);
    5051    void    ProcessRequest(const QList<QByteArray> &lines);
    5152    void    StartResponse(QTextStream *stream);
    5253    void    FinishResponse(QTextStream *stream, QTcpSocket *socket,
    class MythRAOPConnection : public QObject 
    5960    void    CloseAudioDevice(void);
    6061
    6162    QTimer         *m_watchdogTimer;
     63
    6264    // comms socket
    6365    QTcpSocket     *m_socket;
    6466    QTextStream    *m_textStream;
    class MythRAOPConnection : public QObject 
    8587    bool            m_allowVolumeControl;
    8688    // audio/packet sync
    8789    bool            m_seenPacket;
     90    bool            m_authed;
     91    QString         m_nonce;
    8892    int16_t         m_lastPacketSequence;
    8993    uint64_t        m_lastPacketTimestamp;
    9094    uint64_t        m_lastSyncTime;
  • mythtv/libs/libmythtv/mythraopdevice.cpp

    diff --git a/mythtv/libs/libmythtv/mythraopdevice.cpp b/mythtv/libs/libmythtv/mythraopdevice.cpp
    index 8bb5f6f..69212d4 100644
    a b  
    1010#include "mythraopconnection.h"
    1111#include "mythraopdevice.h"
    1212
    13 MythRAOPDevice* MythRAOPDevice::gMythRAOPDevice = NULL;
    14 MThread*        MythRAOPDevice::gMythRAOPDeviceThread = NULL;
    15 QMutex*         MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive);
     13MythRAOPDevice *MythRAOPDevice::gMythRAOPDevice = NULL;
     14MThread        *MythRAOPDevice::gMythRAOPDeviceThread = NULL;
     15QMutex         *MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive);
    1616
    1717#define LOC QString("RAOP Device: ")
    1818
    bool MythRAOPDevice::Create(void) 
    2323    // create the device thread
    2424    if (!gMythRAOPDeviceThread)
    2525        gMythRAOPDeviceThread = new MThread("RAOPDevice");
     26
    2627    if (!gMythRAOPDeviceThread)
    2728    {
    2829        LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device thread.");
    bool MythRAOPDevice::Create(void) 
    3233    // create the device object
    3334    if (!gMythRAOPDevice)
    3435        gMythRAOPDevice = new MythRAOPDevice();
     36
    3537    if (!gMythRAOPDevice)
    3638    {
    3739        LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device object.");
    void MythRAOPDevice::Cleanup(void) 
    6062        gMythRAOPDevice->Teardown();
    6163
    6264    QMutexLocker locker(gMythRAOPDeviceMutex);
     65
    6366    if (gMythRAOPDeviceThread)
    6467    {
    6568        gMythRAOPDeviceThread->exit();
    6669        gMythRAOPDeviceThread->wait();
    6770    }
     71
    6872    delete gMythRAOPDeviceThread;
    6973    gMythRAOPDeviceThread = NULL;
    7074
    void MythRAOPDevice::Cleanup(void) 
    7377}
    7478
    7579MythRAOPDevice::MythRAOPDevice()
    76   : QTcpServer(), m_name(QString("MythTV")), m_bonjour(NULL), m_valid(false),
    77     m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5000)
     80    : QTcpServer(), m_name(QString("MythTV")), m_bonjour(NULL), m_valid(false),
     81      m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5000)
    7882{
    7983    for (int i = 0; i < RAOP_HARDWARE_ID_SIZE; i++)
    8084        m_hardwareId.append((random() % 80) + 33);
    void MythRAOPDevice::Teardown(void) 
    100104    m_bonjour = NULL;
    101105
    102106    // disconnect clients
    103     foreach (MythRAOPConnection* client, m_clients)
     107    foreach(MythRAOPConnection * client, m_clients)
    104108    {
    105109        disconnect(client->GetSocket(), 0, 0, 0);
    106110        delete client;
    void MythRAOPDevice::Start(void) 
    121125
    122126    // start listening for connections (try a few ports in case the default is in use)
    123127    int baseport = m_setupPort;
     128
    124129    while (m_setupPort < baseport + RAOP_PORT_RANGE)
    125130    {
    126131        if (listen(QHostAddress::Any, m_setupPort))
    void MythRAOPDevice::Start(void) 
    129134                QString("Listening for connections on port %1").arg(m_setupPort));
    130135            break;
    131136        }
     137
    132138        m_setupPort++;
    133139    }
    134140
    void MythRAOPDevice::Start(void) 
    137143
    138144    // announce service
    139145    m_bonjour = new BonjourRegister(this);
     146
    140147    if (!m_bonjour)
    141148    {
    142149        LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create Bonjour object.");
    void MythRAOPDevice::Start(void) 
    145152
    146153    // give each frontend a unique name
    147154    int multiple = m_setupPort - baseport;
     155
    148156    if (multiple > 0)
    149157        m_name += QString::number(multiple);
    150158
    void MythRAOPDevice::Start(void) 
    164172    txt.append(4); txt.append("ch=2");
    165173    txt.append(5); txt.append("ss=16");
    166174    txt.append(8); txt.append("sr=44100");
    167     txt.append(8); txt.append("pw=false");
     175
     176    if (gCoreContext->GetNumSettingOnHost("RAOPPasswordEnabled",
     177                                        gCoreContext->GetHostName(),
     178                                        0))
     179    {
     180        txt.append(7); txt.append("pw=true");
     181    }
     182    else
     183    {
     184        txt.append(8); txt.append("pw=false");
     185    }
     186
    168187    txt.append(4); txt.append("vn=3");
    169188    txt.append(9); txt.append("txtvers=1");
    170189
    void MythRAOPDevice::newConnection(void) 
    186205        .arg(client->peerAddress().toString()).arg(client->peerPort()));
    187206
    188207    int port = 6000;
     208
    189209    while (port < (6000 + RAOP_PORT_RANGE))
    190210    {
    191211        bool found = false;
    192         foreach (MythRAOPConnection* client, m_clients)
     212        foreach(MythRAOPConnection * client, m_clients)
    193213        {
    194214            if (client->GetDataPort() == port)
    195215            {
    void MythRAOPDevice::newConnection(void) 
    197217                port++;
    198218            }
    199219        }
     220
    200221        if (!found)
    201222            break;
    202223    }
    203224
    204225    MythRAOPConnection *obj =
    205             new MythRAOPConnection(client, m_hardwareId, port);
     226        new MythRAOPConnection(client, m_hardwareId, port);
     227
    206228    if (obj->Init())
    207229    {
    208230        m_clients.append(obj);
    void MythRAOPDevice::deleteClient(void) 
    220242{
    221243    QMutexLocker locker(m_lock);
    222244    QList<MythRAOPConnection *>::iterator it;
     245
    223246    for (it = m_clients.begin(); it != m_clients.end(); ++it)
    224247    {
    225248        if ((*it)->GetSocket()->state() == QTcpSocket::UnconnectedState)
  • mythtv/programs/mythfrontend/globalsettings.cpp

    diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp b/mythtv/programs/mythfrontend/globalsettings.cpp
    index 7e6fad3..e864968 100644
    a b static HostLineEdit *UDPNotifyPort() 
    26802680    return ge;
    26812681}
    26822682
     2683static HostCheckBox *RAOPPasswordEnabled()
     2684{
     2685    HostCheckBox *gc = new HostCheckBox("RAOPPasswordEnabled");
     2686    gc->setLabel(QObject::tr("Enable password protection for RAOP"));
     2687    gc->setHelpText(QObject::tr("This enables password protection for devices"
     2688                         "connecting via RAOP. (Requires mythfrontend restart)"
     2689                    ));
     2690    gc->setValue(false);
     2691    return gc;
     2692}
     2693
     2694static HostLineEdit *RAOPPassword()
     2695{
     2696    HostLineEdit *ge = new HostLineEdit("RAOPPassword");
     2697    ge->setLabel(QObject::tr("RAOP Access Password"));
     2698
     2699    ge->setValue("");
     2700    QString help = QObject::tr(
     2701        "Enter a password.");
     2702    ge->setHelpText(help);
     2703    return ge;
     2704}
     2705
    26832706static HostCheckBox *RealtimePriority()
    26842707{
    26852708    HostCheckBox *gc = new HostCheckBox("RealtimePriority");
    MainGeneralSettings::MainGeneralSettings() 
    32953318    remotecontrol->addChild(NetworkControlEnabled());
    32963319    remotecontrol->addChild(NetworkControlPort());
    32973320    remotecontrol->addChild(UDPNotifyPort());
     3321    remotecontrol->addChild(RAOPPasswordEnabled());
     3322    remotecontrol->addChild(RAOPPassword());
    32983323    addChild(remotecontrol);
    32993324}