From 9ff5f7702bfc7256accca0ef24d59ac7ca31b10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Kie=C3=9F?= Date: Mon, 5 Jun 2017 18:53:41 +0200 Subject: [PATCH] Add support for Unix FD passing During compilation, this requires Mono.Posix from mono with commit 2225b6a260e1f0d469e37ec0df68a89e832688cd (i.e. mono 4.6 or later). During runtime it will fall back to an implementation which does not support Unix FD passing when the Mono.Posix version is too old. An example showing how to pass file descriptors can be found in examples/UnixFD*.cs --- examples/UnixFDClient.cs | 142 +++++++++++++++ examples/UnixFDInterface.cs | 61 +++++++ examples/UnixFDService.cs | 137 ++++++++++++++ src/Authentication.cs | 10 ++ src/BusObject.cs | 17 +- src/Connection.cs | 121 ++++++++----- src/DisposableList.cs | 37 ++++ src/ExportObject.cs | 8 +- src/Introspection.cs | 4 + src/Makefile.am | 5 + src/Mapper.cs | 2 + src/Protocol/DType.cs | 1 + src/Protocol/FieldCode.cs | 1 + src/Protocol/Message.cs | 28 ++- src/Protocol/MessageReader.cs | 15 ++ src/Protocol/MessageWriter.cs | 19 +- src/Protocol/PendingCall.cs | 12 +- src/Protocol/ProtocolInformation.cs | 2 + src/Protocol/Signature.cs | 13 ++ src/Protocol/Transport.cs | 32 +++- src/Protocol/UnixFDArray.cs | 44 +++++ src/Transports/UnixNativeTransport.cs | 90 +--------- src/Transports/UnixSendmsgTransport.cs | 173 ++++++++++++++++++ src/Transports/UnixTransport.cs | 92 ++++++++++ src/TypeImplementer.cs | 34 +++- src/Unix/UnixMonoStream.cs | 237 +++++++++++++++++++++++++ src/UnixFD.cs | 79 +++++++++ src/UnixMonoTransport.cs | 66 ------- src/dbus-sharp.csproj | 7 +- 29 files changed, 1266 insertions(+), 223 deletions(-) create mode 100644 examples/UnixFDClient.cs create mode 100644 examples/UnixFDInterface.cs create mode 100644 examples/UnixFDService.cs create mode 100644 src/DisposableList.cs create mode 100644 src/Protocol/UnixFDArray.cs create mode 100644 src/Transports/UnixSendmsgTransport.cs create mode 100644 src/Unix/UnixMonoStream.cs create mode 100644 src/UnixFD.cs delete mode 100644 src/UnixMonoTransport.cs diff --git a/examples/UnixFDClient.cs b/examples/UnixFDClient.cs new file mode 100644 index 0000000..d4ff500 --- /dev/null +++ b/examples/UnixFDClient.cs @@ -0,0 +1,142 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; + +using DBus; +using org.freedesktop.DBus; + +using Mono.Unix; +using Mono.Unix.Native; + +class SignalsImpl : Signals { + public event Action GotFD; + + public void CallGotFD (UnixFD fd) { + var handler = GotFD; + if (handler != null) + handler (fd); + } +} + +public class ManagedDBusTest +{ + public static void Main (string[] args) + { + Bus conn; + + if (args.Length == 0) + conn = Bus.Session; + else { + if (args[0] == "--session") + conn = Bus.Session; + else if (args[0] == "--system") + conn = Bus.System; + else + conn = Bus.Open (args[0]); + } + + IBus bus = conn.GetObject ("org.freedesktop.DBus", new ObjectPath ("/org/freedesktop/DBus")); + Console.WriteLine (bus.ListNames ().Length); + + var obj = conn.GetObject (Constants.BusName, Constants.ObjectPath); + var obj2 = conn.GetObject (Constants.BusName, Constants.ObjectPath); + var objIntr = conn.GetObject (Constants.BusName, Constants.ObjectPath); + obj.Ping (); + Console.WriteLine (obj.GetBytes (3).Length); + + Console.WriteLine ("conn.UnixFDSupported = " + conn.UnixFDSupported); + if (!conn.UnixFDSupported) + return; + + using (var disposableList = new DisposableList ()) { + var res = obj.GetFD (disposableList, false); + Console.WriteLine ("Got FD:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + res.Handle); + } + using (var disposableList = new DisposableList ()) { + var res = obj.GetFDList (disposableList, false); + Console.WriteLine ("Got FDs:"); + foreach (var fd in res) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + using (var disposableList = new DisposableList ()) { + var res = (UnixFD[]) obj.GetFDListVariant (disposableList, false); + Console.WriteLine ("Got FDs as variant:"); + foreach (var fd in res) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + using (var disposableList = new DisposableList ()) { + try { + obj.GetFD (disposableList, true); + throw new Exception ("Expected an exception"); + } catch (Exception e) { + if (!e.Message.Contains ("Throwing an exception after creating a UnixFD object")) + throw; + } + } + using (var disposableList = new DisposableList ()) { + try { + obj.GetFDList (disposableList, true); + throw new Exception ("Expected an exception"); + } catch (Exception e) { + if (!e.Message.Contains ("Throwing an exception after creating a UnixFD object")) + throw; + } + } + using (var disposableList = new DisposableList ()) { + try { + obj.GetFDListVariant (disposableList, true); + throw new Exception ("Expected an exception"); + } catch (Exception e) { + if (!e.Message.Contains ("Throwing an exception after creating a UnixFD object")) + throw; + } + } + + // Check whether this leaks an FD + obj.GetFD (null, false); + obj.GetFDList (null, false); + obj.GetFDListVariant (null, false); + try { obj.GetFD (null, true); } catch {} + try { obj.GetFDList (null, true); } catch {} + try { obj.GetFDListVariant (null, true); } catch {} + obj2.GetFD (false); + obj2.GetFDList (false); + obj2.GetFDListVariant (false); + try { obj2.GetFD (true); } catch {} + try { obj2.GetFDList (true); } catch {} + try { obj2.GetFDListVariant (true); } catch {} + + var fd_ = Syscall.open ("/dev/null", OpenFlags.O_RDWR, 0); + if (fd_ < 0) + UnixMarshal.ThrowExceptionForLastError (); + using (var fd = new UnixFD (fd_)) { + obj.SendFD (fd); + obj.SendFD (fd); + obj.SendFDList (new UnixFD[] { fd, fd }); + obj.SendFDListVariant (new UnixFD[] { fd, fd }); + + var impl = new SignalsImpl (); + var spath = new ObjectPath ("/mono_dbus_sharp_test/Signals"); + conn.Register (spath, impl); + obj.RegisterSignalInterface (conn.UniqueName, spath); + impl.CallGotFD (fd); + } + + Console.WriteLine (objIntr.Introspect ().Length); + + obj.ListOpenFDs (); + Console.WriteLine ("Open FDs:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/"); + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/examples/UnixFDInterface.cs b/examples/UnixFDInterface.cs new file mode 100644 index 0000000..68ac4ae --- /dev/null +++ b/examples/UnixFDInterface.cs @@ -0,0 +1,61 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using DBus; + +public static class Constants { + public const string BusName = "mono_dbus_sharp_test.UnixFDService"; + public static readonly ObjectPath ObjectPath = new ObjectPath ("/mono_dbus_sharp_test/UnixFDService"); +} + +[DBus.Interface ("mono_dbus_sharp_test.UnixFDService")] +public interface Interface { + void Ping (); + + void ListOpenFDs (); + + byte[] GetBytes (int len); + + UnixFD GetFD (DisposableList disposableList, bool throwError); + UnixFD[] GetFDList (DisposableList disposableList, bool throwError); + object GetFDListVariant (DisposableList disposableList, bool throwError); + + void SendFD (UnixFD fd); + void SendFDList (UnixFD[] fds); + void SendFDListVariant (object fds); + + void RegisterSignalInterface (string busName, ObjectPath path); +} + +[DBus.Interface ("mono_dbus_sharp_test.UnixFDService")] +public interface Interface2 { + void Ping (); + + void ListOpenFDs (); + + byte[] GetBytes (int len); + + UnixFD GetFD (bool throwError); + UnixFD[] GetFDList (bool throwError); + object GetFDListVariant (bool throwError); + + void SendFD (UnixFD fd); + void SendFDList (UnixFD[] fds); + void SendFDListVariant (object fds); + + void RegisterSignalInterface (string busName, ObjectPath path); +} + +[DBus.Interface ("mono_dbus_sharp_test.UnixFDSignals")] +public interface Signals { + event Action GotFD; +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/examples/UnixFDService.cs b/examples/UnixFDService.cs new file mode 100644 index 0000000..4596197 --- /dev/null +++ b/examples/UnixFDService.cs @@ -0,0 +1,137 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +/* +PATH="$HOME/mono-master/bin:$PATH" ./autogen.sh && make -j8 +make && ( cd examples && mcs -g UnixFDService.cs UnixFDInterface.cs -r ../src/dbus-sharp.dll -r Mono.Posix ) && MONO_PATH=src $HOME/mono-master/bin/mono --debug examples/UnixFDService.exe +make && ( cd examples && mcs -g UnixFDClient.cs UnixFDInterface.cs -r ../src/dbus-sharp.dll -r Mono.Posix ) && MONO_PATH=src $HOME/mono-master/bin/mono --debug examples/UnixFDClient.exe + +python3 +import dbus; dbus.SessionBus().get_object('mono_dbus_sharp_test.UnixFDService', '/mono_dbus_sharp_test/UnixFDService').GetBytes(3) +import dbus; f = dbus.SessionBus().get_object('mono_dbus_sharp_test.UnixFDService', '/mono_dbus_sharp_test/UnixFDService').GetFD () +*/ + +using System; + +using Mono.Unix; +using Mono.Unix.Native; + +using DBus; +using org.freedesktop.DBus; + +public class Impl : Interface { + Connection conn; + + public Impl (Connection conn) + { + this.conn = conn; + } + + public void Ping () + { + } + + public void ListOpenFDs () + { + Console.WriteLine ("Open FDs:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/"); + } + + public UnixFD GetFD (DisposableList disposableList, bool throwError) + { + var fd_ = Syscall.open ("/dev/null", OpenFlags.O_RDWR, 0); + if (fd_ < 0) + UnixMarshal.ThrowExceptionForLastError (); + var fd = new UnixFD (fd_); + disposableList.Add (fd); + + if (throwError) + throw new Exception ("Throwing an exception after creating a UnixFD object"); + + return fd; + } + + public UnixFD[] GetFDList (DisposableList disposableList, bool throwError) + { + return new UnixFD[] { + GetFD (disposableList, false), GetFD (disposableList, throwError), GetFD (disposableList, false) + }; + } + + public object GetFDListVariant (DisposableList disposableList, bool throwError) + { + return GetFDList (disposableList, throwError); + } + + public byte[] GetBytes (int len) + { + return new byte[len]; + } + + public void SendFD (UnixFD fd) + { + Console.WriteLine ("Got FD as parameter:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + public void SendFDList (UnixFD[] fds) + { + Console.WriteLine ("Got FDs as parameter:"); + foreach (var fd in fds) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + public void SendFDListVariant (object fds) + { + Console.WriteLine ("Got FDs as variant parameter:"); + foreach (var fd in (UnixFD[]) fds) + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + } + + public void RegisterSignalInterface (string busName, ObjectPath path) + { + Console.WriteLine ("Register for GotFD event at {0} / {1}", busName, path); + conn.GetObject (busName, path).GotFD += fd => { + Console.WriteLine ("Got FD from signal:"); + Mono.Unix.Native.Stdlib.system ("ls -l /proc/$PPID/fd/" + fd.Handle); + }; + } +} + +public class UnixFDService +{ + public static void Main (string[] args) + { + Bus conn; + + if (args.Length == 0) + conn = Bus.Session; + else { + if (args[0] == "--session") + conn = Bus.Session; + else if (args[0] == "--system") + conn = Bus.System; + else + conn = Bus.Open (args[0]); + } + + conn.Register (Constants.ObjectPath, new Impl (conn)); + + if (conn.RequestName (Constants.BusName) != org.freedesktop.DBus.RequestNameReply.PrimaryOwner) + throw new Exception ("Could not request name"); + + Console.WriteLine ("Waiting for requests..."); + + while (conn.IsConnected) { + conn.Iterate (); + } + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/Authentication.cs b/src/Authentication.cs index cd23945..a2152a6 100644 --- a/src/Authentication.cs +++ b/src/Authentication.cs @@ -324,6 +324,8 @@ public virtual bool Run (IEnumerable commands) class SaslClient : SaslPeer { public string Identity = String.Empty; + public bool TransportSupportsUnixFD = false; + public bool UnixFDSupported = false; //static Regex rejectedRegex = new Regex (@"^REJECTED(\s+(\w+))*$"); @@ -387,6 +389,14 @@ public override IEnumerator GetEnumerator () else ActualId = UUID.Parse (reply[1]); + if (TransportSupportsUnixFD) { + yield return new AuthCommand ("NEGOTIATE_UNIX_FD"); + if (!replies.MoveNext ()) + yield break; + reply = replies.Current; + UnixFDSupported = reply[0] == "AGREE_UNIX_FD"; + } + yield return new AuthCommand ("BEGIN"); yield break; } diff --git a/src/BusObject.cs b/src/BusObject.cs index f8509b5..865c52a 100644 --- a/src/BusObject.cs +++ b/src/BusObject.cs @@ -80,6 +80,10 @@ public void ToggleSignal (string iface, string member, Delegate dlg, bool adding } public void SendSignal (string iface, string member, string inSigStr, MessageWriter writer, Type retType, out Exception exception) + { + SendSignal (iface, member, inSigStr, writer, retType, null, out exception); + } + internal void SendSignal (string iface, string member, string inSigStr, MessageWriter writer, Type retType, DisposableList disposableList, out Exception exception) { exception = null; @@ -100,6 +104,10 @@ public void SendSignal (string iface, string member, string inSigStr, MessageWri } public MessageReader SendMethodCall (string iface, string member, string inSigStr, MessageWriter writer, Type retType, out Exception exception) + { + return SendMethodCall (iface, member, inSigStr, writer, retType, null, out exception); + } + public MessageReader SendMethodCall (string iface, string member, string inSigStr, MessageWriter writer, Type retType, DisposableList disposableList, out Exception exception) { if (string.IsNullOrEmpty (bus_name)) throw new ArgumentNullException ("bus_name"); @@ -137,7 +145,10 @@ public MessageReader SendMethodCall (string iface, string member, string inSigSt } #endif - Message retMsg = conn.SendWithReplyAndBlock (callMsg); + Message retMsg = conn.SendWithReplyAndBlock (callMsg, disposableList != null); + if (disposableList != null && retMsg.UnixFDArray != null) + foreach (var fd in retMsg.UnixFDArray.FDs) + disposableList.Add (fd); MessageReader retVal = null; @@ -163,6 +174,10 @@ public MessageReader SendMethodCall (string iface, string member, string inSigSt } public void Invoke (MethodBase methodBase, string methodName, object[] inArgs, out object[] outArgs, out object retVal, out Exception exception) + { + Invoke (methodBase, methodName, inArgs, null, out outArgs, out retVal, out exception); + } + public void Invoke (MethodBase methodBase, string methodName, object[] inArgs, DisposableList disposableList, out object[] outArgs, out object retVal, out Exception exception) { outArgs = new object[0]; retVal = null; diff --git a/src/Connection.cs b/src/Connection.cs index 4a18033..0284e6c 100644 --- a/src/Connection.cs +++ b/src/Connection.cs @@ -29,6 +29,7 @@ public class Connection bool isShared = false; UUID Id = UUID.Zero; bool isAuthenticated = false; + bool unixFDSupported = false; int serial = 0; // STRONG TODO: GET RID OF THAT SHIT @@ -73,6 +74,12 @@ internal bool IsAuthenticated { } } + public bool UnixFDSupported { + get { + return unixFDSupported; + } + } + internal Transport Transport { get { return transport; @@ -145,6 +152,8 @@ void Authenticate () transport.WriteCred (); SaslClient auth = new SaslClient (); + if (transport != null) + auth.TransportSupportsUnixFD = transport.TransportSupportsUnixFD; auth.Identity = transport.AuthString (); auth.stream = transport.Stream; auth.Peer = new SaslPeer (); @@ -162,6 +171,7 @@ void Authenticate () Id = auth.ActualId; isAuthenticated = true; + unixFDSupported = auth.UnixFDSupported; } // Interlocked.Increment() handles the overflow condition for uint correctly, @@ -171,13 +181,13 @@ internal uint GenerateSerial () return (uint)Interlocked.Increment (ref serial); } - internal Message SendWithReplyAndBlock (Message msg) + internal Message SendWithReplyAndBlock (Message msg, bool keepFDs) { - PendingCall pending = SendWithReply (msg); + PendingCall pending = SendWithReply (msg, keepFDs); return pending.Reply; } - internal PendingCall SendWithReply (Message msg) + internal PendingCall SendWithReply (Message msg, bool keepFDs) { msg.ReplyExpected = true; @@ -186,7 +196,7 @@ internal PendingCall SendWithReply (Message msg) // Should we throttle the maximum number of concurrent PendingCalls? // Should we support timeouts? - PendingCall pending = new PendingCall (this); + PendingCall pending = new PendingCall (this, keepFDs); lock (pendingCalls) pendingCalls[msg.Header.Serial] = pending; @@ -211,7 +221,11 @@ internal void DispatchSignals () lock (inbound) { while (inbound.Count != 0) { Message msg = inbound.Dequeue (); - HandleSignal (msg); + try { + HandleSignal (msg); + } finally { + msg.Dispose (); + } } } } @@ -233,54 +247,66 @@ internal virtual void HandleMessage (Message msg) if (msg == null) throw new ArgumentNullException ("msg", "Cannot handle a null message; maybe the bus was disconnected"); - //TODO: Restrict messages to Local ObjectPath? + bool cleanupFDs = true; + try { - { - object field_value = msg.Header[FieldCode.ReplySerial]; - if (field_value != null) { - uint reply_serial = (uint)field_value; - PendingCall pending; + //TODO: Restrict messages to Local ObjectPath? - lock (pendingCalls) { - if (pendingCalls.TryGetValue (reply_serial, out pending)) { - if (pendingCalls.Remove (reply_serial)) - pending.Reply = msg; + { + object field_value = msg.Header[FieldCode.ReplySerial]; + if (field_value != null) { + uint reply_serial = (uint)field_value; + PendingCall pending; - return; + lock (pendingCalls) { + if (pendingCalls.TryGetValue (reply_serial, out pending)) { + if (pendingCalls.Remove (reply_serial)) { + pending.Reply = msg; + if (pending.KeepFDs) + cleanupFDs = false; // caller is responsible for closing FDs + } + + return; + } } - } - //we discard reply messages with no corresponding PendingCall - if (ProtocolInformation.Verbose) - Console.Error.WriteLine ("Unexpected reply message received: MessageType='" + msg.Header.MessageType + "', ReplySerial=" + reply_serial); + //we discard reply messages with no corresponding PendingCall + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Unexpected reply message received: MessageType='" + msg.Header.MessageType + "', ReplySerial=" + reply_serial); - return; + return; + } } - } - switch (msg.Header.MessageType) { - case MessageType.MethodCall: - MessageContainer method_call = MessageContainer.FromMessage (msg); - HandleMethodCall (method_call); - break; - case MessageType.Signal: - //HandleSignal (msg); - lock (inbound) - inbound.Enqueue (msg); - break; - case MessageType.Error: - //TODO: better exception handling - MessageContainer error = MessageContainer.FromMessage (msg); - string errMsg = String.Empty; - if (msg.Signature.Value.StartsWith ("s")) { - MessageReader reader = new MessageReader (msg); - errMsg = reader.ReadString (); - } - Console.Error.WriteLine ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg); - break; - case MessageType.Invalid: - default: - throw new Exception ("Invalid message received: MessageType='" + msg.Header.MessageType + "'"); + switch (msg.Header.MessageType) { + case MessageType.MethodCall: + MessageContainer method_call = MessageContainer.FromMessage (msg); + HandleMethodCall (method_call); + break; + case MessageType.Signal: + //HandleSignal (msg); + lock (inbound) + inbound.Enqueue (msg); + cleanupFDs = false; // FDs are closed after signal is handled + break; + case MessageType.Error: + //TODO: better exception handling + MessageContainer error = MessageContainer.FromMessage (msg); + string errMsg = String.Empty; + if (msg.Signature.Value.StartsWith ("s")) { + MessageReader reader = new MessageReader (msg); + errMsg = reader.ReadString (); + } + Console.Error.WriteLine ("Remote Error: Signature='" + msg.Signature.Value + "' " + error.ErrorName + ": " + errMsg); + break; + case MessageType.Invalid: + default: + throw new Exception ("Invalid message received: MessageType='" + msg.Header.MessageType + "'"); + } + + } finally { + if (cleanupFDs) + msg.Dispose (); } } @@ -303,9 +329,10 @@ internal void HandleSignal (Message msg) bool compatible = false; Signature inSig, outSig; + bool hasDisposableList; - if (TypeImplementer.SigsForMethod(mi, out inSig, out outSig)) - if (outSig == Signature.Empty && inSig == msg.Signature) + if (TypeImplementer.SigsForMethod(mi, out inSig, out outSig, out hasDisposableList)) + if (outSig == Signature.Empty && inSig == msg.Signature && !hasDisposableList) compatible = true; if (!compatible) { diff --git a/src/DisposableList.cs b/src/DisposableList.cs new file mode 100644 index 0000000..0c4589d --- /dev/null +++ b/src/DisposableList.cs @@ -0,0 +1,37 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.Collections.Generic; +using System.IO; + +namespace DBus +{ + public class DisposableList : IDisposable + { + List list = new List (); + + public void Dispose () + { + lock (list) { + foreach (var obj in list) + obj.Dispose (); + } + } + + public void Add (IDisposable obj) + { + if (obj == null) + throw new ArgumentNullException ("obj"); + + list.Add (obj); + } + } +} + +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/ExportObject.cs b/src/ExportObject.cs index df9c195..14c2eb3 100644 --- a/src/ExportObject.cs +++ b/src/ExportObject.cs @@ -97,15 +97,18 @@ public virtual void HandleMethodCall (MessageContainer method_call) } Signature inSig, outSig; - TypeImplementer.SigsForMethod (mi, out inSig, out outSig); + bool hasDisposableList; + TypeImplementer.SigsForMethod (mi, out inSig, out outSig, out hasDisposableList); Message msg = method_call.Message; MessageReader msgReader = new MessageReader (msg); MessageWriter retWriter = new MessageWriter (); + using (var disposableList = new DisposableList ()) { + Exception raisedException = null; try { - mCaller (Object, msgReader, msg, retWriter); + mCaller (Object, msgReader, msg, retWriter, disposableList); } catch (Exception e) { raisedException = e; } @@ -142,6 +145,7 @@ public virtual void HandleMethodCall (MessageContainer method_call) replyMsg.Header[FieldCode.Destination] = method_call.Sender; conn.Send (replyMsg); + } } public object Object { diff --git a/src/Introspection.cs b/src/Introspection.cs index d4ddeca..8a62372 100644 --- a/src/Introspection.cs +++ b/src/Introspection.cs @@ -121,11 +121,15 @@ public void WriteType (Type target_type) public void WriteArg (ParameterInfo pi) { + if (pi.Position == 0 && pi.ParameterType == typeof (DisposableList)) + return; WriteArg (pi.ParameterType, Mapper.GetArgumentName (pi), pi.IsOut, false); } public void WriteArgReverse (ParameterInfo pi) { + if (pi.Position == 0 && pi.ParameterType == typeof (DisposableList)) + return; WriteArg (pi.ParameterType, Mapper.GetArgumentName (pi), pi.IsOut, true); } diff --git a/src/Makefile.am b/src/Makefile.am index 5e5db4e..e7c4e99 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ CSFILES = Address.cs \ BusObject.cs \ Connection.cs \ DBus.cs \ + DisposableList.cs \ DProxy.cs \ ExportObject.cs \ Introspection.cs \ @@ -19,6 +20,7 @@ CSFILES = Address.cs \ OSHelpers.cs \ BusException.cs \ AddressEntry.cs \ + UnixFD.cs \ Protocol/DBusStruct.cs \ Protocol/DType.cs \ Protocol/DValue.cs \ @@ -39,9 +41,12 @@ CSFILES = Address.cs \ Protocol/Signature.cs \ Protocol/SocketTransport.cs \ Protocol/Transport.cs \ + Protocol/UnixFDArray.cs \ Transports/UnixNativeTransport.cs \ + Transports/UnixSendmsgTransport.cs \ Transports/UnixTransport.cs \ Unix/UnixStream.cs \ + Unix/UnixMonoStream.cs \ Unix/UnixError.cs \ Unix/UnixSocket.cs \ AssemblyInfo.cs diff --git a/src/Mapper.cs b/src/Mapper.cs index dcfe170..61ed342 100644 --- a/src/Mapper.cs +++ b/src/Mapper.cs @@ -164,6 +164,8 @@ public static Type[] GetTypes (ArgDirection dir, ParameterInfo[] parms) //TODO: consider InOut/Ref for (int i = 0 ; i != parms.Length ; i++) { + if (i == 0 && parms[i].ParameterType == typeof (DisposableList)) + continue; switch (dir) { case ArgDirection.In: //docs say IsIn isn't reliable, and this is indeed true diff --git a/src/Protocol/DType.cs b/src/Protocol/DType.cs index e14c99f..41578ef 100644 --- a/src/Protocol/DType.cs +++ b/src/Protocol/DType.cs @@ -24,6 +24,7 @@ public enum DType : byte String = (byte)'s', ObjectPath = (byte)'o', Signature = (byte)'g', + UnixFD = (byte)'h', Array = (byte)'a', Variant = (byte)'v', diff --git a/src/Protocol/FieldCode.cs b/src/Protocol/FieldCode.cs index a524df2..0956753 100644 --- a/src/Protocol/FieldCode.cs +++ b/src/Protocol/FieldCode.cs @@ -18,6 +18,7 @@ public enum FieldCode : byte Destination, Sender, Signature, + UnixFDs, #if PROTO_REPLY_SIGNATURE ReplySignature, //note: not supported in dbus #endif diff --git a/src/Protocol/Message.cs b/src/Protocol/Message.cs index cbaeb65..48842d6 100644 --- a/src/Protocol/Message.cs +++ b/src/Protocol/Message.cs @@ -8,11 +8,12 @@ namespace DBus.Protocol { - public class Message + public class Message : IDisposable { Header header = new Header (); Connection connection; byte[] body; + UnixFDArray fdArray; public Message () { @@ -23,10 +24,16 @@ public Message () } public static Message FromReceivedBytes (Connection connection, byte[] header, byte[] body) + { + return FromReceivedBytes (connection, header, body, null); + } + + public static Message FromReceivedBytes (Connection connection, byte[] header, byte[] body, UnixFDArray fdArray) { Message message = new Message (); message.connection = connection; message.body = body; + message.fdArray = fdArray; message.SetHeaderData (header); return message; @@ -38,6 +45,12 @@ public byte[] Body { } } + public UnixFDArray UnixFDArray { + get { + return fdArray; + } + } + public Header Header { get { return header; @@ -82,6 +95,13 @@ public void AttachBodyTo (MessageWriter writer) { body = writer.ToArray (); header.Length = (uint)body.Length; + if (writer.fdArray.FDs.Count != 0) { + header[FieldCode.UnixFDs] = (uint) writer.fdArray.FDs.Count; + if (fdArray == null) + fdArray = new UnixFDArray (); + foreach (var fd in writer.fdArray.FDs) + fdArray.FDs.Add (fd); + } } public void HandleHeader (Header headerIn) @@ -100,5 +120,11 @@ public byte[] GetHeaderData () header.WriteHeaderToMessage (writer); return writer.ToArray (); } + + public void Dispose () + { + if (UnixFDArray != null) + UnixFDArray.Dispose (); + } } } diff --git a/src/Protocol/MessageReader.cs b/src/Protocol/MessageReader.cs index 1670df5..25b91c2 100644 --- a/src/Protocol/MessageReader.cs +++ b/src/Protocol/MessageReader.cs @@ -96,6 +96,9 @@ public object ReadValue (Type type) } else if (type == typeof (string)) { readValueCache[type] = () => ReadString (); return ReadString (); + } else if (type == typeof (UnixFD)) { + readValueCache[type] = () => ReadUnixFD (); + return ReadUnixFD (); } else if (type.IsGenericType && (type.GetGenericTypeDefinition () == typeof (Dictionary<,>) || type.GetGenericTypeDefinition() == typeof(IDictionary<,>))) { Type[] genArgs = type.GetGenericArguments (); readValueCache[type] = () => ReadDictionary (genArgs[0], genArgs[1]); @@ -358,6 +361,18 @@ public string ReadString () return val; } + public UnixFD ReadUnixFD () + { + uint index = ReadUInt32 (); + + if (message.UnixFDArray == null) + throw new Exception ("Trying to read a UnixFD value but unix fd transfer not supported"); + if (index >= message.UnixFDArray.FDs.Count) + throw new Exception ("UnixFD index " + index + " out of range (got " + message.UnixFDArray.FDs.Count + " fds)"); + + return message.UnixFDArray.FDs[(int)index]; + } + public ObjectPath ReadObjectPath () { //exactly the same as string diff --git a/src/Protocol/MessageWriter.cs b/src/Protocol/MessageWriter.cs index 11f5329..29801e4 100644 --- a/src/Protocol/MessageWriter.cs +++ b/src/Protocol/MessageWriter.cs @@ -11,10 +11,12 @@ namespace DBus.Protocol { - public sealed class MessageWriter + // Disposing a MessageWriter will close all UnixFDs written to it + public sealed class MessageWriter : IDisposable { EndianFlag endianness; MemoryStream stream; + internal readonly UnixFDArray fdArray; Connection connection; static readonly MethodInfo arrayWriter = typeof (MessageWriter).GetMethod ("WriteArray"); @@ -35,6 +37,13 @@ public MessageWriter (EndianFlag endianness) { this.endianness = endianness; stream = new MemoryStream (); + fdArray = new UnixFDArray (); + } + + public void Dispose () + { + if (fdArray != null) + fdArray.Dispose (); } public Connection Connection { @@ -174,6 +183,13 @@ public void Write (string val) WriteNull (); } + public void Write (UnixFD val) + { + int index = fdArray.FDs.Count; + fdArray.FDs.Add (val); + Write ((uint) index); + } + public void Write (ObjectPath val) { Write (val.Value); @@ -497,6 +513,7 @@ internal void WriteHeaderFields (Dictionary val) Write ((ObjectPath)entry.Value); break; case FieldCode.ReplySerial: + case FieldCode.UnixFDs: Write (Signature.UInt32Sig); Write ((uint)entry.Value); break; diff --git a/src/Protocol/PendingCall.cs b/src/Protocol/PendingCall.cs index 47e92da..f8813a4 100644 --- a/src/Protocol/PendingCall.cs +++ b/src/Protocol/PendingCall.cs @@ -13,12 +13,22 @@ public class PendingCall : IAsyncResult Message reply; ManualResetEvent waitHandle; bool completedSync; + bool keepFDs; public event Action Completed; - public PendingCall (Connection conn) + public PendingCall (Connection conn) : this (conn, false) {} + public PendingCall (Connection conn, bool keepFDs) { this.conn = conn; + this.keepFDs = keepFDs; + } + + internal bool KeepFDs + { + get { + return keepFDs; + } } public Message Reply { diff --git a/src/Protocol/ProtocolInformation.cs b/src/Protocol/ProtocolInformation.cs index 2747516..0e654d5 100644 --- a/src/Protocol/ProtocolInformation.cs +++ b/src/Protocol/ProtocolInformation.cs @@ -64,6 +64,8 @@ public static int GetAlignment (DType dtype) return 8; case DType.String: return 4; + case DType.UnixFD: + return 4; case DType.ObjectPath: return 4; case DType.Signature: diff --git a/src/Protocol/Signature.cs b/src/Protocol/Signature.cs index 66336be..2c56af2 100644 --- a/src/Protocol/Signature.cs +++ b/src/Protocol/Signature.cs @@ -25,6 +25,7 @@ public struct Signature public static readonly Signature UInt16Sig = Allocate (DType.UInt16); public static readonly Signature UInt32Sig = Allocate (DType.UInt32); public static readonly Signature StringSig = Allocate (DType.String); + public static readonly Signature UnixFDSig = Allocate (DType.UnixFD); public static readonly Signature StructBegin = Allocate (DType.StructBegin); public static readonly Signature StructEnd = Allocate (DType.StructEnd); public static readonly Signature ObjectPathSig = Allocate (DType.ObjectPath); @@ -314,6 +315,7 @@ static int GetSize (DType dtype) return 2; case DType.Int32: case DType.UInt32: + case DType.UnixFD: return 4; case DType.Int64: case DType.UInt64: @@ -617,6 +619,9 @@ internal static DType TypeToDType (Type type) if (type == typeof (string)) return DType.String; + if (type == typeof (UnixFD)) + return DType.UnixFD; + if (type == typeof (ObjectPath)) return DType.ObjectPath; @@ -678,6 +683,8 @@ public static DType TypeToDType (Type type) return DType.Double; else if (type == typeof (string)) return DType.String; + else if (type == typeof (UnixFD)) + return DType.UnixFD; else if (type == typeof (ObjectPath)) return DType.ObjectPath; else if (type == typeof (Signature)) @@ -793,6 +800,8 @@ public Type ToType (ref int pos) return typeof (double); case DType.String: return typeof (string); + case DType.UnixFD: + return typeof (UnixFD); case DType.ObjectPath: return typeof (ObjectPath); case DType.Signature: @@ -862,6 +871,9 @@ public static Signature GetSig (Type type) if (type == typeof (string)) return Signature.StringSig; + if (type == typeof (UnixFD)) + return Signature.UnixFDSig; + if (type == typeof (object)) return Signature.VariantSig; @@ -926,6 +938,7 @@ bool SingleType () case DType.Single: case DType.Double: case DType.String: + case DType.UnixFD: case DType.ObjectPath: case DType.Signature: case DType.Variant: diff --git a/src/Protocol/Transport.cs b/src/Protocol/Transport.cs index 2ae8815..910e652 100644 --- a/src/Protocol/Transport.cs +++ b/src/Protocol/Transport.cs @@ -40,7 +40,14 @@ public static Transport Create (AddressEntry entry) case "unix": { if (OSHelpers.PlatformIsUnixoid) { - Transport transport = new UnixNativeTransport (); + Transport transport; + if (UnixSendmsgTransport.Available ()) { + transport = new UnixSendmsgTransport (); + } else { + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Warning: Syscall.sendmsg() not available, transfering unix FDs will not work"); + transport = new UnixNativeTransport (); + } transport.Open (entry); return transport; } @@ -153,6 +160,13 @@ internal Message ReadMessage () try { msg = ReadMessageReal (); + if (msg == null) { + if (connection.IsConnected) { + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Warning: The dbus daemon closed the connection"); + } + connection.IsConnected = false; + } } catch (IOException e) { if (ProtocolInformation.Verbose) Console.Error.WriteLine (e.Message); @@ -166,7 +180,7 @@ internal Message ReadMessage () return msg; } - int Read (byte[] buffer, int offset, int count) + internal virtual int Read (byte[] buffer, int offset, int count, UnixFDArray fdArray) { int read = 0; while (read < count) { @@ -186,6 +200,7 @@ Message ReadMessageReal () { byte[] header = null; byte[] body = null; + UnixFDArray fdArray = Connection.UnixFDSupported ? new UnixFDArray () : null; int read; @@ -194,7 +209,7 @@ Message ReadMessageReal () readBuffer = new byte[16]; byte[] hbuf = readBuffer; - read = Read (hbuf, 0, 16); + read = Read (hbuf, 0, 16, fdArray); if (read == 0) return null; @@ -235,7 +250,7 @@ Message ReadMessageReal () header = new byte[16 + toRead]; Array.Copy (hbuf, header, 16); - read = Read (header, 16, toRead); + read = Read (header, 16, toRead, fdArray); if (read != toRead) throw new Exception ("Message header length mismatch: " + read + " of expected " + toRead); @@ -244,13 +259,13 @@ Message ReadMessageReal () if (bodyLen != 0) { body = new byte[bodyLen]; - read = Read (body, 0, bodyLen); + read = Read (body, 0, bodyLen, fdArray); if (read != bodyLen) throw new Exception ("Message body length mismatch: " + read + " of expected " + bodyLen); } - Message msg = Message.FromReceivedBytes (Connection, header, body); + Message msg = Message.FromReceivedBytes (Connection, header, body, fdArray); return msg; } @@ -264,5 +279,10 @@ internal virtual void WriteMessage (Message msg) stream.Flush (); } } + + // Returns true if then transport supports unix FDs, even when the + // actual connection doesn't (e.g. because the daemon doesn't support + // it) + internal virtual bool TransportSupportsUnixFD { get { return false; } } } } diff --git a/src/Protocol/UnixFDArray.cs b/src/Protocol/UnixFDArray.cs new file mode 100644 index 0000000..8748a0f --- /dev/null +++ b/src/Protocol/UnixFDArray.cs @@ -0,0 +1,44 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.Collections.Generic; +using System.IO; + +namespace DBus.Protocol +{ + public class UnixFDArray : IDisposable + { + public static readonly int MaxFDs = 16; + + List fds = new List (); + + public IList FDs + { + get { + return fds; + } + } + + public void Dispose () + { + for (int i = 0; i < fds.Count; i++) { + fds[i].Dispose (); + } + } + + internal UnixFDArray Clone () { + var res = new UnixFDArray (); + foreach (var fd in FDs) + res.FDs.Add (fd.Clone ()); + return res; + } + } +} + +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/Transports/UnixNativeTransport.cs b/src/Transports/UnixNativeTransport.cs index 8d6410f..9f697ad 100644 --- a/src/Transports/UnixNativeTransport.cs +++ b/src/Transports/UnixNativeTransport.cs @@ -2,15 +2,11 @@ // This software is made available under the MIT License // See COPYING for details -//We send BSD-style credentials on all platforms -//Doesn't seem to break Linux (but is redundant there) -//This may turn out to be a bad idea -#define HAVE_CMSGCRED - using System; using System.IO; using System.Text; using System.Runtime.InteropServices; + using DBus.Unix; using DBus.Protocol; @@ -42,52 +38,6 @@ public override void Open (string path, bool @abstract) Stream = new UnixStream (socket); } - //send peer credentials null byte - //different platforms do this in different ways -#if HAVE_CMSGCRED - unsafe void WriteBsdCred () - { - //null credentials byte - byte buf = 0; - - IOVector iov = new IOVector (); - //iov.Base = (IntPtr)(&buf); - iov.Base = &buf; - iov.Length = 1; - - msghdr msg = new msghdr (); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - cmsg cm = new cmsg (); - msg.msg_control = (IntPtr)(&cm); - msg.msg_controllen = (uint)sizeof (cmsg); - cm.hdr.cmsg_len = (uint)sizeof (cmsg); - cm.hdr.cmsg_level = 0xffff; //SOL_SOCKET - cm.hdr.cmsg_type = 0x03; //SCM_CREDS - - int written = socket.SendMsg (&msg, 0); - if (written != 1) - throw new Exception ("Failed to write credentials"); - } -#endif - - public override void WriteCred () - { -#if HAVE_CMSGCRED - try { - WriteBsdCred (); - return; - } catch { - if (ProtocolInformation.Verbose) - Console.Error.WriteLine ("Warning: WriteBsdCred() failed; falling back to ordinary WriteCred()"); - } -#endif - //null credentials byte - byte buf = 0; - Stream.WriteByte (buf); - } - public static byte[] GetSockAddr (string path) { byte[] p = Encoding.Default.GetBytes (path); @@ -140,42 +90,4 @@ internal UnixSocket OpenAbstractUnix (string path) return client; } } - -#if HAVE_CMSGCRED - unsafe struct msghdr - { - public IntPtr msg_name; //optional address - public uint msg_namelen; //size of address - public IOVector *msg_iov; //scatter/gather array - public int msg_iovlen; //# elements in msg_iov - public IntPtr msg_control; //ancillary data, see below - public uint msg_controllen; //ancillary data buffer len - public int msg_flags; //flags on received message - } - - struct cmsghdr - { - public uint cmsg_len; //data byte count, including header - public int cmsg_level; //originating protocol - public int cmsg_type; //protocol-specific type - } - - unsafe struct cmsgcred - { - const int CMGROUP_MAX = 16; - - public int cmcred_pid; //PID of sending process - public uint cmcred_uid; //real UID of sending process - public uint cmcred_euid; //effective UID of sending process - public uint cmcred_gid; //real GID of sending process - public short cmcred_ngroups; //number or groups - public fixed uint cmcred_groups[CMGROUP_MAX]; //groups - } - - struct cmsg - { - public cmsghdr hdr; - public cmsgcred cred; - } -#endif } diff --git a/src/Transports/UnixSendmsgTransport.cs b/src/Transports/UnixSendmsgTransport.cs new file mode 100644 index 0000000..e23aa32 --- /dev/null +++ b/src/Transports/UnixSendmsgTransport.cs @@ -0,0 +1,173 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.IO; +using System.Text; +using System.Runtime.InteropServices; + +using DBus.Protocol; + +using Mono.Unix; +using Mono.Unix.Native; + +namespace DBus.Transports +{ + class UnixSendmsgTransport : UnixTransport + { + static bool CheckAvailable () + { + var msghdr = new Msghdr { + msg_iov = new Iovec[] {}, + msg_iovlen = 0, + }; + // sendmsg() should return EBADFD because fd == -1 + // If sendmsg() is not available (e.g. because Mono.Posix is too + // old or this is on a system without sendmsg()), Syscall.sendmsg() + // will throw an exception + Syscall.sendmsg (-1, msghdr, 0); + return true; + } + + public static bool Available () + { + try { + return CheckAvailable (); + } catch { + return false; + } + } + + public UnixSendmsgTransport () + { + SocketHandle = -1; + } + + internal override bool TransportSupportsUnixFD { get { return true; } } + + public override string AuthString () + { + long uid = Mono.Unix.Native.Syscall.geteuid (); + return uid.ToString (); + } + + public override void Open (string path, bool @abstract) + { + if (String.IsNullOrEmpty (path)) + throw new ArgumentException ("path"); + + var addr = new SockaddrUn (path, linuxAbstractNamespace:@abstract); + + SocketHandle = Syscall.socket (UnixAddressFamily.AF_UNIX, UnixSocketType.SOCK_STREAM, 0); + if (SocketHandle == -1) + UnixMarshal.ThrowExceptionForLastError (); + bool success = false; + try { + if (Syscall.connect ((int) SocketHandle, addr) < 0) + UnixMarshal.ThrowExceptionForLastError (); + Stream = new DBus.Unix.UnixMonoStream ((int) SocketHandle); + success = true; + } finally { + if (!success) { + int ret = Syscall.close ((int) SocketHandle); + SocketHandle = -1; + if (ret == -1) + UnixMarshal.ThrowExceptionForLastError (); + } + } + } + + readonly object writeLock = new object (); + + byte[] cmsgBuffer = new byte[Syscall.CMSG_LEN ((ulong)(sizeof (int) * UnixFDArray.MaxFDs))]; + // Might return short reads + unsafe int ReadShort (byte[] buffer, int offset, int length, UnixFDArray fdArray) + { + if (length < 0 || offset < 0 || length + offset < length || length + offset > buffer.Length) + throw new ArgumentException (); + + fixed (byte* ptr = buffer, cmsgPtr = cmsgBuffer) { + var iovecs = new Iovec[] { + new Iovec { + iov_base = (IntPtr) (ptr + offset), + iov_len = (ulong) length, + }, + }; + + var msghdr = new Msghdr { + msg_iov = iovecs, + msg_iovlen = 1, + msg_control = cmsgBuffer, + msg_controllen = cmsgBuffer.Length, + }; + + long r; + do { + r = Syscall.recvmsg ((int) SocketHandle, msghdr, 0); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + + for (long cOffset = Syscall.CMSG_FIRSTHDR (msghdr); cOffset != -1; cOffset = Syscall.CMSG_NXTHDR (msghdr, cOffset)) { + var recvHdr = Cmsghdr.ReadFromBuffer (msghdr, cOffset); + if (recvHdr.cmsg_level != UnixSocketProtocol.SOL_SOCKET) + continue; + if (recvHdr.cmsg_type != UnixSocketControlMessage.SCM_RIGHTS) + continue; + var recvDataOffset = Syscall.CMSG_DATA (msghdr, cOffset); + var bytes = recvHdr.cmsg_len - (recvDataOffset - cOffset); + var fdCount = bytes / sizeof (int); + for (int i = 0; i < fdCount; i++) + fdArray.FDs.Add (new UnixFD (((int*) (cmsgPtr + recvDataOffset))[i])); + } + + if ((msghdr.msg_flags & MessageFlags.MSG_CTRUNC) != 0) + throw new Exception ("Control message truncated (probably file descriptors lost)"); + + return (int) r; + } + } + + internal override int Read (byte[] buffer, int offset, int length, UnixFDArray fdArray) + { + if (!Connection.UnixFDSupported || fdArray == null) + return base.Read (buffer, offset, length, fdArray); + + int read = 0; + while (read < length) { + int nread = ReadShort (buffer, offset + read, length - read, fdArray); + if (nread == 0) + break; + read += nread; + } + + if (read > length) + throw new Exception (); + + return read; + } + + internal override unsafe void WriteMessage (Message msg) + { + if (msg.UnixFDArray == null || msg.UnixFDArray.FDs.Count == 0) { + base.WriteMessage (msg); + return; + } + if (!Connection.UnixFDSupported) + throw new Exception ("Attempting to write Unix FDs to a connection which does not support them"); + + lock (writeLock) { + var ms = new MemoryStream (); + msg.Header.GetHeaderDataToStream (ms); + var header = ms.ToArray (); + ((DBus.Unix.UnixMonoStream) Stream).Sendmsg (header, 0, header.Length, msg.Body, 0, msg.Body == null ? 0 : msg.Body.Length, msg.UnixFDArray); + } + } + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/Transports/UnixTransport.cs b/src/Transports/UnixTransport.cs index 048f86a..28f40f5 100644 --- a/src/Transports/UnixTransport.cs +++ b/src/Transports/UnixTransport.cs @@ -2,9 +2,17 @@ // This software is made available under the MIT License // See COPYING for details +//We send BSD-style credentials on all platforms +//Doesn't seem to break Linux (but is redundant there) +//This may turn out to be a bad idea +#define HAVE_CMSGCRED + using System; using System.IO; +using DBus.Protocol; +using DBus.Unix; + namespace DBus.Transports { abstract class UnixTransport : Transport @@ -25,5 +33,89 @@ public override void Open (AddressEntry entry) } public abstract void Open (string path, bool @abstract); + + //send peer credentials null byte + //different platforms do this in different ways +#if HAVE_CMSGCRED + unsafe void WriteBsdCred () + { + //null credentials byte + byte buf = 0; + + IOVector iov = new IOVector (); + //iov.Base = (IntPtr)(&buf); + iov.Base = &buf; + iov.Length = 1; + + msghdr msg = new msghdr (); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + cmsg cm = new cmsg (); + msg.msg_control = (IntPtr)(&cm); + msg.msg_controllen = (uint)sizeof (cmsg); + cm.hdr.cmsg_len = (uint)sizeof (cmsg); + cm.hdr.cmsg_level = 0xffff; //SOL_SOCKET + cm.hdr.cmsg_type = 0x03; //SCM_CREDS + + int written = new UnixSocket ((int) SocketHandle, false).SendMsg (&msg, 0); + if (written != 1) + throw new Exception ("Failed to write credentials"); + } +#endif + + public override void WriteCred () + { +#if HAVE_CMSGCRED + try { + WriteBsdCred (); + return; + } catch { + if (ProtocolInformation.Verbose) + Console.Error.WriteLine ("Warning: WriteBsdCred() failed; falling back to ordinary WriteCred()"); + } +#endif + //null credentials byte + byte buf = 0; + Stream.WriteByte (buf); + } + } + +#if HAVE_CMSGCRED + unsafe struct msghdr + { + public IntPtr msg_name; //optional address + public uint msg_namelen; //size of address + public IOVector *msg_iov; //scatter/gather array + public int msg_iovlen; //# elements in msg_iov + public IntPtr msg_control; //ancillary data, see below + public uint msg_controllen; //ancillary data buffer len + public int msg_flags; //flags on received message + } + + struct cmsghdr + { + public uint cmsg_len; //data byte count, including header + public int cmsg_level; //originating protocol + public int cmsg_type; //protocol-specific type + } + + unsafe struct cmsgcred + { + const int CMGROUP_MAX = 16; + + public int cmcred_pid; //PID of sending process + public uint cmcred_uid; //real UID of sending process + public uint cmcred_euid; //effective UID of sending process + public uint cmcred_gid; //real GID of sending process + public short cmcred_ngroups; //number or groups + public fixed uint cmcred_groups[CMGROUP_MAX]; //groups + } + + struct cmsg + { + public cmsghdr hdr; + public cmsgcred cred; } +#endif } diff --git a/src/TypeImplementer.cs b/src/TypeImplementer.cs index 5e6839f..a4ab9e3 100644 --- a/src/TypeImplementer.cs +++ b/src/TypeImplementer.cs @@ -34,8 +34,8 @@ class TypeImplementer static Dictionary writeMethods = new Dictionary (); static Dictionary typeWriters = new Dictionary (); - static MethodInfo sendMethodCallMethod = typeof (BusObject).GetMethod ("SendMethodCall"); - static MethodInfo sendSignalMethod = typeof (BusObject).GetMethod ("SendSignal"); + static MethodInfo sendMethodCallMethod = typeof (BusObject).GetMethod ("SendMethodCall", new Type[] { typeof (string), typeof (string), typeof (string), typeof (MessageWriter), typeof (Type), typeof (DisposableList), typeof (Exception).MakeByRefType () }); + static MethodInfo sendSignalMethod = typeof (BusObject).GetMethod ("SendSignal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof (string), typeof (string), typeof (string), typeof (MessageWriter), typeof (Type), typeof (DisposableList), typeof (Exception).MakeByRefType () }, null); static MethodInfo toggleSignalMethod = typeof (BusObject).GetMethod ("ToggleSignal"); static Dictionary hookup_methods = new Dictionary (); @@ -290,7 +290,8 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth //signature Signature inSig; Signature outSig; - SigsForMethod (declMethod, out inSig, out outSig); + bool hasDisposableList; + SigsForMethod (declMethod, out inSig, out outSig, out hasDisposableList); ilg.Emit (OpCodes.Ldstr, inSig.Value); @@ -300,6 +301,8 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth foreach (ParameterInfo parm in parms) { + if (hasDisposableList && parm.Position == 0) + continue; if (parm.IsOut) continue; @@ -338,6 +341,12 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth //the expected return Type GenTypeOf (ilg, retType); + // The DisposableList object to which returned unix FDs will be added + if (hasDisposableList) + ilg.Emit (OpCodes.Ldarg_1); + else + ilg.Emit (OpCodes.Ldnull); + LocalBuilder exc = ilg.DeclareLocal (typeof (Exception)); ilg.Emit (OpCodes.Ldloca_S, exc); @@ -402,16 +411,21 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth } - public static bool SigsForMethod (MethodInfo mi, out Signature inSig, out Signature outSig) + public static bool SigsForMethod (MethodInfo mi, out Signature inSig, out Signature outSig, out bool hasDisposableList) { inSig = Signature.Empty; outSig = Signature.Empty; + hasDisposableList = false; + bool first = true; foreach (ParameterInfo parm in mi.GetParameters ()) { - if (parm.IsOut) + if (first && !parm.IsOut && parm.ParameterType == typeof (DisposableList)) + hasDisposableList = true; + else if (parm.IsOut) outSig += Signature.GetSig (parm.ParameterType.GetElementType ()); else inSig += Signature.GetSig (parm.ParameterType); + first = false; } outSig += Signature.GetSig (mi.ReturnType); @@ -454,7 +468,7 @@ internal static MethodCaller GenCaller (MethodInfo target) internal static DynamicMethod GenReadMethod (MethodInfo target) { - Type[] parms = new Type[] { typeof (object), typeof (MessageReader), typeof (Message), typeof (MessageWriter) }; + Type[] parms = new Type[] { typeof (object), typeof (MessageReader), typeof (Message), typeof (MessageWriter), typeof (DisposableList) }; DynamicMethod hookupMethod = new DynamicMethod ("Caller", typeof (void), parms, typeof (MessageReader)); Gen (hookupMethod, target); return hookupMethod; @@ -473,9 +487,13 @@ static void Gen (DynamicMethod hookupMethod, MethodInfo declMethod) Dictionary locals = new Dictionary (); foreach (ParameterInfo parm in parms) { - Type parmType = parm.ParameterType; + if (parm.Position == 0 && !parm.IsOut && parmType == typeof (DisposableList)) { + ilg.Emit (OpCodes.Ldarg, 4); // disposableList + continue; + } + if (parm.IsOut) { LocalBuilder parmLocal = ilg.DeclareLocal (parmType.GetElementType ()); locals[parm] = parmLocal; @@ -567,5 +585,5 @@ static void GenTypeOf (ILGenerator ilg, Type t) internal delegate void TypeWriter (MessageWriter writer, T value); - internal delegate void MethodCaller (object instance, MessageReader rdr, Message msg, MessageWriter ret); + internal delegate void MethodCaller (object instance, MessageReader rdr, Message msg, MessageWriter ret, DisposableList disposableList); } diff --git a/src/Unix/UnixMonoStream.cs b/src/Unix/UnixMonoStream.cs new file mode 100644 index 0000000..1dc1f17 --- /dev/null +++ b/src/Unix/UnixMonoStream.cs @@ -0,0 +1,237 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.IO; +using System.Runtime.InteropServices; + +using Mono.Unix; +using Mono.Unix.Native; + +namespace DBus.Unix +{ + sealed class UnixMonoStream : Stream + { + int fd; + + public UnixMonoStream (int fd) + { + this.fd = fd; + } + + public override bool CanRead + { + get { + return true; + } + } + + public override bool CanSeek + { + get { + return false; + } + } + + public override bool CanWrite + { + get { + return true; + } + } + + public override long Length + { + get { + throw new NotImplementedException ("Seeking is not implemented"); + } + } + + public override long Position + { + get { + throw new NotImplementedException ("Seeking is not implemented"); + } set { + throw new NotImplementedException ("Seeking is not implemented"); + } + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotImplementedException ("Seeking is not implemented"); + } + + public override void SetLength (long value) + { + throw new NotImplementedException ("Not implemented"); + } + + public override void Flush () + { + } + + private void AssertValidBuffer (byte[] buffer, long offset, long length) + { + if (buffer == null) + throw new ArgumentNullException ("buffer"); + if (offset < 0) + throw new ArgumentOutOfRangeException ("offset", "< 0"); + if (length < 0) + throw new ArgumentOutOfRangeException ("length", "< 0"); + if (offset > buffer.LongLength) + throw new ArgumentException ("destination offset is beyond array size"); + if (offset > (buffer.LongLength - length)) + throw new ArgumentException ("would overrun buffer"); + } + + public override unsafe int Read (byte[] buffer, int offset, int length) + { + AssertValidBuffer (buffer, offset, length); + + long r = 0; + fixed (byte* buf = buffer) { + do { + r = Syscall.read (fd, buf + offset, (ulong) length); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + return (int) r; + } + } + + public override unsafe void Write (byte[] buffer, int offset, int length) + { + AssertValidBuffer (buffer, offset, length); + + int pos = 0; + long r = 0; + fixed (byte* buf = buffer) { + while (pos < length) { + do { + r = Syscall.write (fd, buf + offset + pos, (ulong) length); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + pos += (int) r; + } + } + } + + public override void Close () + { + if (fd != -1) { + int ret = Syscall.close (fd); + fd = -1; + if (ret == -1) + UnixMarshal.ThrowExceptionForLastError (); + } + base.Close (); + } + + // Send the two buffers and the FDs using sendmsg(), don't handle short writes + // length1 + length2 must not be 0 + public unsafe long SendmsgShort (byte[] buffer1, long offset1, long length1, + byte[] buffer2, long offset2, long length2, + DBus.Protocol.UnixFDArray fds) + { + //Console.WriteLine ("SendmsgShort (X, {0}, {1}, {2}, {3}, {4}, {5})", offset1, length1, buffer2 == null ? "-" : "Y", offset2, length2, fds == null ? "-" : "" + fds.FDs.Count); + AssertValidBuffer (buffer1, offset1, length1); + if (buffer2 == null) { + if (length2 != 0) + throw new ArgumentOutOfRangeException ("length2", "!= 0 while buffer2 == null"); + offset2 = 0; + } else { + AssertValidBuffer (buffer2, offset2, length2); + } + + fixed (byte* ptr1 = buffer1, ptr2 = buffer2) { + var iovecs = new Iovec[] { + new Iovec { + iov_base = (IntPtr) (ptr1 + offset1), + iov_len = (ulong) length1, + }, + new Iovec { + iov_base = (IntPtr) (ptr2 + offset2), + iov_len = (ulong) length2, + }, + }; + /* Simulate short writes + if (iovecs[0].iov_len == 0) { + iovecs[1].iov_len = Math.Min (iovecs[1].iov_len, 5); + } else { + iovecs[0].iov_len = Math.Min (iovecs[0].iov_len, 5); + iovecs[1].iov_len = 0; + } + */ + byte[] cmsg = null; + + // Create copy of FDs to prevent the user from Dispose()ing the + // FDs in another thread between writing the FDs into the cmsg + // buffer and calling sendmsg() + using (var fds2 = fds == null ? null : fds.Clone ()) { + int fdCount = fds2 == null ? 0 : fds2.FDs.Count; + if (fdCount != 0) { + // Create one SCM_RIGHTS control message + cmsg = new byte[Syscall.CMSG_SPACE ((uint) fdCount * sizeof (int))]; + } + var msghdr = new Msghdr { + msg_iov = iovecs, + msg_iovlen = length2 == 0 ? 1 : 2, + msg_control = cmsg, + msg_controllen = cmsg == null ? 0 : cmsg.Length, + }; + if (fdCount != 0) { + var hdr = new Cmsghdr { + cmsg_len = (long) Syscall.CMSG_LEN ((uint) fdCount * sizeof (int)), + cmsg_level = UnixSocketProtocol.SOL_SOCKET, + cmsg_type = UnixSocketControlMessage.SCM_RIGHTS, + }; + hdr.WriteToBuffer (msghdr, 0); + var dataOffset = Syscall.CMSG_DATA (msghdr, 0); + fixed (byte* ptr = cmsg) { + for (int i = 0; i < fdCount; i++) + ((int*) (ptr + dataOffset))[i] = fds2.FDs[i].Handle; + } + } + long r; + do { + r = Syscall.sendmsg (fd, msghdr, MessageFlags.MSG_NOSIGNAL); + } while (UnixMarshal.ShouldRetrySyscall ((int) r)); + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + if (r == 0) + throw new Exception ("sendmsg() returned 0"); + return r; + } + } + } + + // Send the two buffers and the FDs using sendmsg(), handle short writes + public unsafe void Sendmsg (byte[] buffer1, long offset1, long length1, + byte[] buffer2, long offset2, long length2, + DBus.Protocol.UnixFDArray fds) + { + //SendmsgShort (buffer1, offset1, length1, buffer2, offset2, length2, fds); return; + long bytes_overall = (long) length1 + length2; + long written = 0; + while (written < bytes_overall) { + if (written >= length1) { + long written2 = written - length1; + written += SendmsgShort (buffer2, offset2 + written2, length2 - written2, null, 0, 0, written == 0 ? fds : null); + } else { + written += SendmsgShort (buffer1, offset1 + written, length1 - written, buffer2, offset2, length2, written == 0 ? fds : null); + } + } + if (written != bytes_overall) + throw new Exception ("written != bytes_overall"); + } + } +} + +// vim: noexpandtab +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/UnixFD.cs b/src/UnixFD.cs new file mode 100644 index 0000000..2c2cf5d --- /dev/null +++ b/src/UnixFD.cs @@ -0,0 +1,79 @@ +// Copyright 2017 Steffen Kiess +// This software is made available under the MIT License +// See COPYING for details + +using System; +using System.Collections.Generic; +using System.IO; + +using Mono.Unix; +using Mono.Unix.Native; + +namespace DBus +{ + public class UnixFD : IDisposable + { + object lck = new object (); + int fd = -1; + + public UnixFD (int fd) + { + this.fd = fd; + } + + public void Dispose () + { + lock (lck) { + if (fd != -1) { + int r; + // Don't retry close() on EINTR, on a lot of systems (e.g. Linux) the FD will be already closed when EINTR is returned, see https://lwn.net/Articles/576478/ + r = DBus.Unix.UnixSocket.close (fd); + fd = -1; + + if (r < 0) + UnixMarshal.ThrowExceptionForLastError (); + } + } + } + + public override string ToString () + { + lock (lck) { + if (fd == -1) + return "UnixFD (disposed)"; + return "UnixFD (" + fd + ")"; + } + } + + // The caller has to make sure that the FD does not get closed between + // calling UnixFD.Handle and using the handle + public int Handle { + get { + lock (lck) { + if (fd == -1) + throw new ObjectDisposedException (GetType ().FullName); + return fd; + } + } + } + + // Return a new UnixFD instance which will be usable after this UnixFD + // has been closed + public UnixFD Clone () { + lock (lck) { + if (fd == -1) + throw new ObjectDisposedException (GetType ().FullName); + int newFd = Syscall.dup (fd); + if (newFd < 0) + UnixMarshal.ThrowExceptionForLastError (); + return new UnixFD (newFd); + } + } + } +} + +// Local Variables: +// tab-width: 4 +// c-basic-offset: 4 +// indent-tabs-mode: t +// End: diff --git a/src/UnixMonoTransport.cs b/src/UnixMonoTransport.cs deleted file mode 100644 index 47326a7..0000000 --- a/src/UnixMonoTransport.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2006 Alp Toker -// This software is made available under the MIT License -// See COPYING for details - -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; - -using Mono.Unix; -using Mono.Unix.Native; - -namespace DBus.Transports -{ - class UnixMonoTransport : UnixTransport - { - protected Socket socket; - - public override void Open (string path, bool @abstract) - { - if (@abstract) - socket = OpenAbstractUnix (path); - else - socket = OpenUnix (path); - - socket.Blocking = true; - SocketHandle = (long)socket.Handle; - //Stream = new UnixStream ((int)socket.Handle); - Stream = new NetworkStream (socket); - } - - //send peer credentials null byte. note that this might not be portable - //there are also selinux, BSD etc. considerations - public override void WriteCred () - { - Stream.WriteByte (0); - } - - public override string AuthString () - { - long uid = UnixUserInfo.GetRealUserId (); - - return uid.ToString (); - } - - protected Socket OpenAbstractUnix (string path) - { - AbstractUnixEndPoint ep = new AbstractUnixEndPoint (path); - - Socket client = new Socket (AddressFamily.Unix, SocketType.Stream, 0); - client.Connect (ep); - - return client; - } - - public Socket OpenUnix (string path) - { - UnixEndPoint remoteEndPoint = new UnixEndPoint (path); - - Socket client = new Socket (AddressFamily.Unix, SocketType.Stream, 0); - client.Connect (remoteEndPoint); - - return client; - } - } -} diff --git a/src/dbus-sharp.csproj b/src/dbus-sharp.csproj index 28d264e..45ee10d 100644 --- a/src/dbus-sharp.csproj +++ b/src/dbus-sharp.csproj @@ -13,7 +13,7 @@ DBus true ..\dbus-sharp.snk - v3.5 + v4.5 True @@ -47,6 +47,7 @@ + @@ -57,6 +58,7 @@ + @@ -77,9 +79,12 @@ + + +