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 }