Skip to content

Commit

Permalink
Add support for sending/receiving unix file descriptors.
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-ancell committed Sep 1, 2021
1 parent 4006e9d commit 0af2e02
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 40 deletions.
26 changes: 21 additions & 5 deletions lib/src/dbus_auth_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class DBusAuthClient {
final _requestsController = StreamController<String>();
var _attemptedExternal = false;
var _isAuthenticated = false;
final bool _requestUnixFd;
var _unixFdSupported = false;
DBusUUID? _uuid;
String? _errorMessage;
Expand All @@ -34,7 +35,7 @@ class DBusAuthClient {
String? get errorMessage => _errorMessage;

/// Creates a new authentication client.
DBusAuthClient() {
DBusAuthClient({bool requestUnixFd = true}) : _requestUnixFd = requestUnixFd {
// On start, end an empty byte, as this is required if sending the credentials as a socket control message.
// We rely on the server using SO_PEERCRED to check out credentials.
// Then request the supported mechanisms.
Expand Down Expand Up @@ -83,18 +84,27 @@ class DBusAuthClient {
return;
}
_isAuthenticated = true;
_send('BEGIN');
_doneCompleter.complete();
if (_requestUnixFd) {
_send('NEGOTIATE_UNIX_FD');
} else {
_begin();
}
break;
case 'DATA':
_fail('Unable to handle DATA command');
break;
case 'ERROR':
_errorMessage = args;
_doneCompleter.complete();
if (isAuthenticated) {
// Error was from NEGOTIATE_UNIX_FD, can continue without this.
_begin();
} else {
_errorMessage = args;
_doneCompleter.complete();
}
break;
case 'AGREE_UNIX_FD':
_unixFdSupported = true;
_begin();
break;
default:
_fail("Unknown command '$command'");
Expand Down Expand Up @@ -130,4 +140,10 @@ class DBusAuthClient {
}
_send('AUTH EXTERNAL $authIdHex');
}

/// Complete authentication.
void _begin() {
_send('BEGIN');
_doneCompleter.complete();
}
}
12 changes: 8 additions & 4 deletions lib/src/dbus_auth_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'dbus_uuid.dart';
class DBusAuthServer {
final _responsesController = StreamController<String>();
bool _readSocketControlMessage = false;
var _externalDone = false;
var _sentOk = false;
var _isAuthenticated = false;

/// Unique ID for this connection.
Expand Down Expand Up @@ -63,7 +63,7 @@ class DBusAuthServer {
_reject('Cancelled');
break;
case 'BEGIN':
if (_externalDone) {
if (_sentOk) {
_isAuthenticated = true;
}
break;
Expand All @@ -74,7 +74,11 @@ class DBusAuthServer {
_reject('Received error');
break;
case 'NEGOTIATE_UNIX_FD':
_error('Unix fd not supported');
if (_sentOk) {
_send('AGREE_UNIX_FD');
} else {
_error('Not authenticated');
}
break;
default:
_error("Unknown command '$command'");
Expand All @@ -100,7 +104,7 @@ class DBusAuthServer {
/// Do authentication using the EXTERNAL mechanism.
void _authenticateExternal(String uid) {
// Note uid isn't checked.
_externalDone = true;
_sentOk = true;
_send('OK ${uuid.toHexString()}');
}

Expand Down
57 changes: 41 additions & 16 deletions lib/src/dbus_client.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

Expand Down Expand Up @@ -175,9 +176,10 @@ class DBusSignalSignatureException implements Exception {
/// A client connection to a D-Bus server.
class DBusClient {
final DBusAddress _address;
Socket? _socket;
RawSocket? _socket;
final _readBuffer = DBusReadBuffer();
final _authClient = DBusAuthClient();
var _authComplete = false;
Completer? _connectCompleter;
var _lastSerial = 0;
final _methodCalls = <int, Completer<DBusMethodResponse>>{};
Expand Down Expand Up @@ -719,16 +721,13 @@ class DBusClient {
throw 'D-Bus address transport not supported: $_address';
}

_socket = await Socket.connect(socketAddress, port);
_socket?.listen(_processData);
}

/// Performs authentication with D-Bus server.
Future<bool> _authenticate() async {
_authClient.requests.listen((message) => _socket?.write(message + '\r\n'));
await _authClient.done;

return _authClient.isAuthenticated;
_socket = await RawSocket.connect(socketAddress, port);
_socket?.listen((event) {
// NOTE: Should wait for RawSocketEvent.write for writes too.
if (event == RawSocketEvent.read) {
_readData();
}
});
}

/// Connects to the D-Bus server.
Expand All @@ -740,7 +739,11 @@ class DBusClient {
_connectCompleter = Completer();

await _openSocket();
if (!await _authenticate()) {
_authClient.requests
.listen((message) => _socket?.write(utf8.encode(message + '\r\n')));
await _authClient.done;
_authComplete = true;
if (!_authClient.isAuthenticated) {
await _socket?.close();
return;
}
Expand Down Expand Up @@ -859,13 +862,27 @@ class DBusClient {
}
}

/// Processes incoming data from the D-Bus server.
void _processData(Uint8List data) {
/// Read incoming data from the D-Bus server.
void _readData() {
var data = _socket?.read();
if (data == null) {
return;
}
_readBuffer.writeBytes(data);

// FIXME: Once supported the above should be replaced with:
//var message = _socket?.receiveMessage();
//if (message == null) {
// return;
//}
//_readBuffer.writeBytes(message.data);
//for (var message in message.controlMessages.where((message) => message is UnixFileDescriptorsControlMessage)) {
// readBuffer.addFileDescriptors(message.fileDescriptors);
//}

var complete = false;
while (!complete) {
if (!_authClient.isAuthenticated) {
if (!_authComplete) {
complete = _processAuth();
} else {
complete = _processMessages();
Expand Down Expand Up @@ -1126,7 +1143,15 @@ class DBusClient {
void _sendMessage(DBusMessage message) {
var buffer = DBusWriteBuffer();
buffer.writeMessage(message);
_socket?.add(buffer.data);

_socket?.write(buffer.data);

// FIXME: Once supported the above should be replaced with:
//var controlMessages = <SocketControlMessage>[];
//if (buffer.fileDescriptors.isNotEmpty) {
// controlMessages.add(UnixFileDescriptorsControlMessage(buffer.fileDescriptors));
//}
//_socket?.sendMessage(controlMessages, buffer.data);
}

@override
Expand Down
39 changes: 34 additions & 5 deletions lib/src/dbus_read_buffer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class DBusReadBuffer extends DBusBuffer {
/// Data in the buffer.
var _data = Uint8List(0);

/// Unix file descriptors available.
var _fileDescriptors = <int>[];

/// View of the buffer to allow accessing fixed width integers and floats.
late ByteData _view;

Expand All @@ -34,6 +37,11 @@ class DBusReadBuffer extends DBusBuffer {
_view = ByteData.view(_data.buffer);
}

/// Add received file descriptors.
void addFileDescriptors(List<int> fds) {
_fileDescriptors.addAll(fds);
}

/// Read a single byte from the buffer.
int _readByte() {
readOffset++;
Expand Down Expand Up @@ -152,9 +160,6 @@ class DBusReadBuffer extends DBusBuffer {
signature = value as DBusSignature;
} else if (code == 9) {
fdCount = (value as DBusUint32).value;
if (fdCount != 0) {
throw 'Message contains file descriptors, which are not supported';
}
}
}
if (!align(8)) {
Expand All @@ -170,7 +175,7 @@ class DBusReadBuffer extends DBusBuffer {
if (signature != null) {
var signatures = signature.split();
for (var s in signatures) {
var value = readDBusValue(s, endian);
var value = readDBusValue(s, endian, fdCount);
if (value == null) {
return null;
}
Expand All @@ -188,6 +193,13 @@ class DBusReadBuffer extends DBusBuffer {
}
}

// Remove file descriptors that were part of this message.
// Note: This could remove descriptors added after the end of the message.
if (_fileDescriptors.length < fdCount) {
throw 'Insufficient file descriptors received';
}
_fileDescriptors.removeRange(0, fdCount);

return DBusMessage(type,
flags: flags,
serial: serial,
Expand Down Expand Up @@ -392,6 +404,21 @@ class DBusReadBuffer extends DBusBuffer {
return DBusVariant(childValue);
}

/// Reads a [DBusUnixFd] from the buffer or returns null if not enough data.
DBusUnixFd? readDBusUnixFd(int fdCount, [Endian endian = Endian.little]) {
var index = readDBusUint32()?.value;
if (index == null) {
return null;
}
if (index > fdCount) {
throw 'Unix fd index out of bounds';
}
if (index > _fileDescriptors.length) {
throw 'Unix fd $index not yet received';
}
return DBusUnixFd(_fileDescriptors[index]);
}

/// Reads a [DBusStruct] from the buffer or returns null if not enough data.
DBusStruct? readDBusStruct(Iterable<DBusSignature> childSignatures,
[Endian endian = Endian.little]) {
Expand Down Expand Up @@ -458,7 +485,7 @@ class DBusReadBuffer extends DBusBuffer {

/// Reads a [DBusValue] with [signature].
DBusValue? readDBusValue(DBusSignature signature,
[Endian endian = Endian.little]) {
[Endian endian = Endian.little, int fdCount = 0]) {
var s = signature.value;
if (s == 'y') {
return readDBusByte();
Expand Down Expand Up @@ -488,6 +515,8 @@ class DBusReadBuffer extends DBusBuffer {
return readDBusVariant(endian);
} else if (s == 'm') {
throw 'D-Bus reserved maybe type not valid';
} else if (s == 'h') {
return readDBusUnixFd(fdCount, endian);
} else if (s.startsWith('a{') && s.endsWith('}')) {
var childSignature = DBusSignature(s.substring(2, s.length - 1));
var signatures = childSignature.split();
Expand Down
43 changes: 35 additions & 8 deletions lib/src/dbus_server.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
Expand Down Expand Up @@ -62,7 +63,7 @@ class _DBusRemoteClient {
DBusServer get server => serverSocket.server;

/// The socket this client is communicating on.
final Socket _socket;
final RawSocket _socket;

/// Incoming data.
final _readBuffer = DBusReadBuffer();
Expand All @@ -81,8 +82,13 @@ class _DBusRemoteClient {

_DBusRemoteClient(this.serverSocket, this._socket, this.uniqueName)
: _authServer = DBusAuthServer(serverSocket.uuid) {
_authServer.responses.listen((message) => _socket.write(message + '\r\n'));
_socket.listen(_processData);
_authServer.responses
.listen((message) => _socket.write(utf8.encode(message + '\r\n')));
_socket.listen((event) {
if (event == RawSocketEvent.read) {
_readData();
}
});
}

/// True if this client has a rule that matches [message].
Expand All @@ -109,17 +115,38 @@ class _DBusRemoteClient {
void sendMessage(DBusMessage message) {
var buffer = DBusWriteBuffer();
buffer.writeMessage(message);
_socket.add(buffer.data);
_socket.write(buffer.data);

// FIXME: Once supported the above should be replaced with:
//var controlMessages = <SocketControlMessage>[];
//if (buffer.fileDescriptors.isNotEmpty) {
// controlMessages.add(UnixFileDescriptorsControlMessage(buffer.fileDescriptors));
//}
//_socket?.sendMessage(controlMessages, buffer.data);
}

Future<void> close() async {
await _socket.close();
}

/// Processes incoming data from this D-Bus client.
void _processData(Uint8List data) {
/// Reads incoming data from this D-Bus client.
void _readData() {
var data = _socket.read();
if (data == null) {
return;
}
_readBuffer.writeBytes(data);

// FIXME: Once supported the above should be replaced with:
//var message = _socket?.receiveMessage();
//if (message == null) {
// return;
//}
//_readBuffer.writeBytes(message.data);
//for (var message in message.controlMessages.where((message) => message is UnixFileDescriptorsControlMessage)) {
// readBuffer.addFileDescriptors(message.fileDescriptors);
//}

var complete = false;
while (!complete) {
if (!_authServer.isAuthenticated) {
Expand Down Expand Up @@ -177,7 +204,7 @@ class _DBusServerSocket {
final DBusServer server;

/// Socket being listened on.
final ServerSocket socket;
final RawServerSocket socket;

/// Id for this connection.
final int connectionId;
Expand Down Expand Up @@ -445,7 +472,7 @@ class DBusServer {

Future<_DBusServerSocket> _addServerSocket(
InternetAddress address, int port) async {
var socket = await ServerSocket.bind(address, port);
var socket = await RawServerSocket.bind(address, port);
var serverSocket = _DBusServerSocket(this, socket, _nextConnectionId);
_sockets.add(serverSocket);
_nextConnectionId++;
Expand Down
Loading

0 comments on commit 0af2e02

Please sign in to comment.