File indexing completed on 2024-04-28 16:59:41

0001 /*
0002    Copyright (C) 2013 Andreas Hartmetz <ahartmetz@gmail.com>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LGPL.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 
0019    Alternatively, this file is available under the Mozilla Public License
0020    Version 1.1.  You may obtain a copy of the License at
0021    http://www.mozilla.org/MPL/
0022 */
0023 
0024 #include "authclient.h"
0025 
0026 #include "icompletionlistener.h"
0027 #include "itransport.h"
0028 #include "stringtools.h"
0029 
0030 #include <cassert>
0031 #include <cstring>
0032 #include <sstream>
0033 
0034 #ifdef __unix__
0035 #include <sys/types.h>
0036 #include <unistd.h>
0037 #endif
0038 
0039 #ifdef _WIN32
0040 #define WIN32_LEAN_AND_MEAN
0041 #include <windows.h>
0042 #include <sddl.h>
0043 #include "winutil.h"
0044 #endif
0045 
0046 AuthClient::AuthClient(ITransport *transport)
0047    : m_state(InitialState),
0048      m_nextAuthMethod(0),
0049      m_fdPassingEnabled(false),
0050      m_completionListener(nullptr)
0051 {
0052     transport->setReadListener(this);
0053     byte nullBuf[1] = { 0 };
0054     transport->write(chunk(nullBuf, 1));
0055 
0056     sendNextAuthMethod();
0057 }
0058 
0059 bool AuthClient::isFinished() const
0060 {
0061     return m_state >= AuthenticationFailedState;
0062 }
0063 
0064 bool AuthClient::isAuthenticated() const
0065 {
0066     return m_state == AuthenticatedState;
0067 }
0068 
0069 bool AuthClient::isUnixFdPassingEnabled() const
0070 {
0071     return m_fdPassingEnabled;
0072 }
0073 
0074 void AuthClient::setCompletionListener(ICompletionListener *listener)
0075 {
0076     m_completionListener = listener;
0077 }
0078 
0079 IO::Status AuthClient::handleTransportCanRead()
0080 {
0081     const bool wasFinished = isFinished();
0082     while (!isFinished() && readLine()) {
0083         advanceState();
0084     }
0085     if (!readTransport()->isOpen()) {
0086         m_state = AuthenticationFailedState;
0087     }
0088     // The handleCompletion() callback can (and in fact will) delete us. Query m_state before.
0089     const IO::Status ret = m_state == AuthenticationFailedState ? IO::Status::RemoteClosed : IO::Status::OK;
0090     if (isFinished() && !wasFinished && m_completionListener) {
0091         m_completionListener->handleCompletion(this);
0092     }
0093     return ret;
0094 }
0095 
0096 bool AuthClient::readLine()
0097 {
0098     // don't care about performance here, this doesn't run often or process much data
0099     if (isEndOfLine()) {
0100         m_line.clear(); // start a new line
0101     }
0102 
0103     byte readBuf[1];
0104     while (true) {
0105         const IO::Result iores = readTransport()->read(readBuf, 1);
0106         if (iores.length != 1 || iores.status != IO::Status::OK) {
0107             return false;
0108         }
0109         m_line += char(readBuf[0]);
0110 
0111         if (isEndOfLine()) {
0112             return true;
0113         }
0114     }
0115     return false;
0116 }
0117 
0118 bool AuthClient::isEndOfLine() const
0119 {
0120     return m_line.length() >= 2 &&
0121            m_line[m_line.length() - 2] == '\r' && m_line[m_line.length() - 1] == '\n';
0122 }
0123 
0124 void AuthClient::sendNextAuthMethod()
0125 {
0126     switch (m_nextAuthMethod) {
0127     case AuthExternal: {
0128         std::stringstream uidEncoded;
0129 #ifdef _WIN32
0130         uidEncoded << fetchWindowsSid();
0131 #else
0132         // The numeric UID is first encoded to ASCII ("1000") and the ASCII to hex... because.
0133         uidEncoded << geteuid();
0134 #endif
0135         std::string extLine = "AUTH EXTERNAL " + hexEncode(uidEncoded.str()) + "\r\n";
0136         readTransport()->write(chunk(extLine.c_str(), extLine.length()));
0137 
0138         m_nextAuthMethod++;
0139         m_state = ExpectOkState;
0140         break;}
0141     case AuthAnonymous: {
0142         // This is basically "trust me bro" auth. The server must be configured to accept this, obviously.
0143         // The part after ANONYMOUS seems arbitrary, we send a hex-encoded "dferry". libdbus-1 seems to send
0144         // something like a hex-encoded "libdbus 1.14.10".
0145 
0146         cstring anonLine("AUTH ANONYMOUS 646665727279\r\n");
0147         readTransport()->write(chunk(anonLine.ptr, anonLine.length));
0148 
0149         m_nextAuthMethod++;
0150         m_state = ExpectOkState;
0151         break; }
0152     default:
0153         m_state = AuthenticationFailedState;
0154         break;
0155     }
0156 }
0157 
0158 void AuthClient::advanceState()
0159 {
0160     // Note: since the connection is new and send buffers are typically several megabytes, there is basically
0161     // no chance that writing will block or write only partial data. Therefore we simplify things by doing
0162     // write synchronously, which means that we don't need to register as write readiness listener.
0163     // Therefore, writeTransport() is nullptr, so we have to do the unintuitive readTransport()->write().
0164 
0165     // some findings:
0166     // - the string after the server OK is its UUID that also appears in the address string
0167 
0168     switch (m_state) {
0169     case ExpectOkState: {
0170         const bool authOk = m_line.substr(0, strlen("OK ")) == "OK ";
0171         if (authOk) {
0172             // continue below, either negotiate FD passing or just send BEGIN
0173         } else {
0174             const bool rejected = m_line.substr(0, strlen("REJECTED")) == "REJECTED";
0175             if (rejected) {
0176                 m_state = ExpectOkState;
0177                 // TODO read possible authentication methods from REJECTED [space separated list of methods]
0178                 sendNextAuthMethod();
0179             } else {
0180                 m_state = AuthenticationFailedState; // protocol violation -> we're out
0181             }
0182             break;
0183         }
0184 #ifdef __unix__
0185         if (readTransport()->supportedPassingUnixFdsCount() > 0) {
0186             cstring negotiateLine("NEGOTIATE_UNIX_FD\r\n");
0187             readTransport()->write(chunk(negotiateLine.ptr, negotiateLine.length));
0188             m_state = ExpectUnixFdResponseState;
0189             break;
0190         }
0191         }
0192         // fall through
0193     case ExpectUnixFdResponseState: {
0194         m_fdPassingEnabled = m_line == "AGREE_UNIX_FD\r\n";
0195 #endif
0196         cstring beginLine("BEGIN\r\n");
0197         readTransport()->write(chunk(beginLine.ptr, beginLine.length));
0198         m_state = AuthenticatedState;
0199         break; }
0200     default:
0201         m_state = AuthenticationFailedState;
0202         break;
0203     }
0204 }