1 package org
.asamk
.signal
.commands
;
3 import net
.sourceforge
.argparse4j
.impl
.Arguments
;
4 import net
.sourceforge
.argparse4j
.inf
.Namespace
;
5 import net
.sourceforge
.argparse4j
.inf
.Subparser
;
7 import org
.asamk
.signal
.DbusConfig
;
8 import org
.asamk
.signal
.OutputType
;
9 import org
.asamk
.signal
.ReceiveMessageHandler
;
10 import org
.asamk
.signal
.Shutdown
;
11 import org
.asamk
.signal
.commands
.exceptions
.CommandException
;
12 import org
.asamk
.signal
.commands
.exceptions
.IOErrorException
;
13 import org
.asamk
.signal
.dbus
.DbusHandler
;
14 import org
.asamk
.signal
.http
.HttpServerHandler
;
15 import org
.asamk
.signal
.json
.JsonReceiveMessageHandler
;
16 import org
.asamk
.signal
.jsonrpc
.SocketHandler
;
17 import org
.asamk
.signal
.manager
.Manager
;
18 import org
.asamk
.signal
.manager
.MultiAccountManager
;
19 import org
.asamk
.signal
.output
.JsonWriter
;
20 import org
.asamk
.signal
.output
.OutputWriter
;
21 import org
.asamk
.signal
.output
.PlainTextWriter
;
22 import org
.asamk
.signal
.util
.IOUtils
;
23 import org
.slf4j
.Logger
;
24 import org
.slf4j
.LoggerFactory
;
27 import java
.io
.IOException
;
28 import java
.net
.InetSocketAddress
;
29 import java
.net
.UnixDomainSocketAddress
;
30 import java
.nio
.channels
.Channel
;
31 import java
.nio
.channels
.ServerSocketChannel
;
32 import java
.util
.ArrayList
;
33 import java
.util
.List
;
35 import static org
.asamk
.signal
.util
.CommandUtil
.getReceiveConfig
;
37 public class DaemonCommand
implements MultiLocalCommand
, LocalCommand
{
39 private static final Logger logger
= LoggerFactory
.getLogger(DaemonCommand
.class);
42 public String
getName() {
47 public void attachToSubparser(final Subparser subparser
) {
48 final var defaultSocketPath
= new File(new File(IOUtils
.getRuntimeDir(), "signal-cli"), "socket");
49 subparser
.help("Run in daemon mode and provide a JSON-RPC or an experimental dbus interface.");
50 subparser
.addArgument("--dbus").action(Arguments
.storeTrue()).help("Expose a DBus interface on the user bus.");
51 subparser
.addArgument("--dbus-system", "--system")
52 .action(Arguments
.storeTrue())
53 .help("Expose a DBus interface on the system bus.");
54 subparser
.addArgument("--bus-name")
55 .setDefault(DbusConfig
.getBusname())
56 .help("Specify the D-Bus bus name to connect to.");
57 subparser
.addArgument("--socket")
60 .setConst(defaultSocketPath
)
61 .help("Expose a JSON-RPC interface on a UNIX socket (default $XDG_RUNTIME_DIR/signal-cli/socket).");
62 subparser
.addArgument("--tcp")
64 .setConst("localhost:7583")
65 .help("Expose a JSON-RPC interface on a TCP socket (default localhost:7583).");
66 subparser
.addArgument("--http")
68 .setConst("localhost:8080")
69 .help("Expose a JSON-RPC interface as http endpoint (default localhost:8080).");
70 subparser
.addArgument("--no-receive-stdout")
71 .help("Don’t print received messages to stdout.")
72 .action(Arguments
.storeTrue());
73 subparser
.addArgument("--receive-mode")
74 .help("Specify when to start receiving messages.")
75 .type(Arguments
.enumStringType(ReceiveMode
.class))
76 .setDefault(ReceiveMode
.ON_START
);
77 subparser
.addArgument("--ignore-attachments")
78 .help("Don’t download attachments of received messages.")
79 .action(Arguments
.storeTrue());
80 subparser
.addArgument("--ignore-stories")
81 .help("Don’t receive story messages from the server.")
82 .action(Arguments
.storeTrue());
83 subparser
.addArgument("--send-read-receipts")
84 .help("Send read receipts for all incoming data messages (in addition to the default delivery receipts)")
85 .action(Arguments
.storeTrue());
89 public List
<OutputType
> getSupportedOutputTypes() {
90 return List
.of(OutputType
.PLAIN_TEXT
, OutputType
.JSON
);
94 public void handleCommand(
97 final OutputWriter outputWriter
98 ) throws CommandException
{
99 Shutdown
.installHandler();
100 logger
.info("Starting daemon in single-account mode for " + m
.getSelfNumber());
101 final var noReceiveStdOut
= Boolean
.TRUE
.equals(ns
.getBoolean("no-receive-stdout"));
102 final var receiveMode
= ns
.<ReceiveMode
>get("receive-mode");
103 final var receiveConfig
= getReceiveConfig(ns
);
105 m
.setReceiveConfig(receiveConfig
);
106 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
108 try (final var daemonHandler
= new SingleAccountDaemonHandler(m
, receiveMode
)) {
109 setup(ns
, daemonHandler
);
111 m
.addClosedListener(Shutdown
::triggerShutdown
);
114 Shutdown
.waitForShutdown();
115 } catch (InterruptedException ignored
) {
121 public void handleCommand(
123 final MultiAccountManager c
,
124 final OutputWriter outputWriter
125 ) throws CommandException
{
126 Shutdown
.installHandler();
127 logger
.info("Starting daemon in multi-account mode");
128 final var noReceiveStdOut
= Boolean
.TRUE
.equals(ns
.getBoolean("no-receive-stdout"));
129 final var receiveMode
= ns
.<ReceiveMode
>get("receive-mode");
130 final var receiveConfig
= getReceiveConfig(ns
);
131 c
.getManagers().forEach(m
-> {
132 m
.setReceiveConfig(receiveConfig
);
133 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
135 c
.addOnManagerAddedHandler(m
-> {
136 m
.setReceiveConfig(receiveConfig
);
137 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
140 try (final var daemonHandler
= new MultiAccountDaemonHandler(c
, receiveMode
)) {
141 setup(ns
, daemonHandler
);
143 synchronized (this) {
145 Shutdown
.waitForShutdown();
146 } catch (InterruptedException ignored
) {
152 private static void setup(final Namespace ns
, final DaemonHandler daemonHandler
) throws CommandException
{
153 final Channel inheritedChannel
;
155 if (System
.inheritedChannel() instanceof ServerSocketChannel serverChannel
) {
156 inheritedChannel
= serverChannel
;
157 logger
.info("Using inherited socket: " + serverChannel
.getLocalAddress());
158 daemonHandler
.runSocket(serverChannel
);
160 inheritedChannel
= null;
162 } catch (IOException e
) {
163 throw new IOErrorException("Failed to use inherited socket", e
);
166 final var socketFile
= ns
.<File
>get("socket");
167 if (socketFile
!= null) {
168 final var address
= UnixDomainSocketAddress
.of(socketFile
.toPath());
169 final var serverChannel
= IOUtils
.bindSocket(address
);
170 daemonHandler
.runSocket(serverChannel
);
173 final var tcpAddress
= ns
.getString("tcp");
174 if (tcpAddress
!= null) {
175 final var address
= IOUtils
.parseInetSocketAddress(tcpAddress
);
176 final var serverChannel
= IOUtils
.bindSocket(address
);
177 daemonHandler
.runSocket(serverChannel
);
180 final var httpAddress
= ns
.getString("http");
181 if (httpAddress
!= null) {
182 final var address
= IOUtils
.parseInetSocketAddress(httpAddress
);
183 daemonHandler
.runHttp(address
);
186 final var isDbusSystem
= Boolean
.TRUE
.equals(ns
.getBoolean("dbus-system"));
188 final var busName
= ns
.getString("bus-name");
189 daemonHandler
.runDbus(true, busName
);
192 final var isDbusSession
= Boolean
.TRUE
.equals(ns
.getBoolean("dbus"));
194 final var busName
= ns
.getString("bus-name");
195 daemonHandler
.runDbus(false, busName
);
200 && socketFile
== null
201 && tcpAddress
== null
202 && httpAddress
== null
203 && inheritedChannel
== null) {
205 "Running daemon command without explicit mode is deprecated. Use 'daemon --dbus' to use the dbus interface.");
206 daemonHandler
.runDbus(false, DbusConfig
.getBusname());
210 private void addDefaultReceiveHandler(Manager m
, OutputWriter outputWriter
, final boolean isWeakListener
) {
211 final var handler
= switch (outputWriter
) {
212 case PlainTextWriter writer
-> new ReceiveMessageHandler(m
, writer
);
213 case JsonWriter writer
-> new JsonReceiveMessageHandler(m
, writer
);
214 case null -> Manager
.ReceiveMessageHandler
.EMPTY
;
216 m
.addReceiveHandler(handler
, isWeakListener
);
219 private static abstract class DaemonHandler
implements AutoCloseable
{
221 protected final ReceiveMode receiveMode
;
222 protected final List
<AutoCloseable
> closeables
= new ArrayList
<>();
224 protected DaemonHandler(final ReceiveMode receiveMode
) {
225 this.receiveMode
= receiveMode
;
228 public abstract void runSocket(ServerSocketChannel serverChannel
) throws CommandException
;
230 public abstract void runDbus(boolean isDbusSystem
, final String busname
) throws CommandException
;
232 public abstract void runHttp(InetSocketAddress address
) throws CommandException
;
234 protected final void runSocket(final SocketHandler socketHandler
) {
235 socketHandler
.init();
236 this.closeables
.add(socketHandler
);
239 protected final void runDbus(
240 DbusHandler dbusHandler
241 ) throws CommandException
{
243 this.closeables
.add(dbusHandler
);
246 protected final void runHttp(final HttpServerHandler handler
) throws CommandException
{
249 } catch (IOException ex
) {
250 throw new IOErrorException("Failed to initialize HTTP Server", ex
);
252 this.closeables
.add(handler
);
256 public void close() {
257 for (final var closeable
: new ArrayList
<>(this.closeables
)) {
260 } catch (Exception e
) {
261 logger
.warn("Failed to close daemon handler", e
);
264 this.closeables
.clear();
268 private static final class SingleAccountDaemonHandler
extends DaemonHandler
{
270 private final Manager m
;
272 public SingleAccountDaemonHandler(final Manager m
, final ReceiveMode receiveMode
) {
278 public void runSocket(final ServerSocketChannel serverChannel
) {
279 runSocket(new SocketHandler(serverChannel
, m
, receiveMode
== ReceiveMode
.MANUAL
));
283 public void runDbus(final boolean isDbusSystem
, final String busname
) throws CommandException
{
284 runDbus(new DbusHandler(isDbusSystem
, busname
, m
, receiveMode
!= ReceiveMode
.ON_START
));
288 public void runHttp(InetSocketAddress address
) throws CommandException
{
289 runHttp(new HttpServerHandler(address
, m
));
293 private static final class MultiAccountDaemonHandler
extends DaemonHandler
{
295 private final MultiAccountManager c
;
297 public MultiAccountDaemonHandler(final MultiAccountManager c
, final ReceiveMode receiveMode
) {
303 public void runSocket(final ServerSocketChannel serverChannel
) {
304 runSocket(new SocketHandler(serverChannel
, c
, receiveMode
== ReceiveMode
.MANUAL
));
308 public void runDbus(final boolean isDbusSystem
, final String busname
) throws CommandException
{
309 runDbus(new DbusHandler(isDbusSystem
, busname
, c
, receiveMode
!= ReceiveMode
.ON_START
));
313 public void runHttp(final InetSocketAddress address
) throws CommandException
{
314 runHttp(new HttpServerHandler(address
, c
));