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
.OutputType
;
8 import org
.asamk
.signal
.ReceiveMessageHandler
;
9 import org
.asamk
.signal
.Shutdown
;
10 import org
.asamk
.signal
.commands
.exceptions
.CommandException
;
11 import org
.asamk
.signal
.commands
.exceptions
.IOErrorException
;
12 import org
.asamk
.signal
.dbus
.DbusHandler
;
13 import org
.asamk
.signal
.http
.HttpServerHandler
;
14 import org
.asamk
.signal
.json
.JsonReceiveMessageHandler
;
15 import org
.asamk
.signal
.jsonrpc
.SocketHandler
;
16 import org
.asamk
.signal
.manager
.Manager
;
17 import org
.asamk
.signal
.manager
.MultiAccountManager
;
18 import org
.asamk
.signal
.output
.JsonWriter
;
19 import org
.asamk
.signal
.output
.OutputWriter
;
20 import org
.asamk
.signal
.output
.PlainTextWriter
;
21 import org
.asamk
.signal
.util
.IOUtils
;
22 import org
.slf4j
.Logger
;
23 import org
.slf4j
.LoggerFactory
;
26 import java
.io
.IOException
;
27 import java
.net
.InetSocketAddress
;
28 import java
.net
.UnixDomainSocketAddress
;
29 import java
.nio
.channels
.Channel
;
30 import java
.nio
.channels
.ServerSocketChannel
;
31 import java
.util
.ArrayList
;
32 import java
.util
.List
;
34 import static org
.asamk
.signal
.util
.CommandUtil
.getReceiveConfig
;
36 public class DaemonCommand
implements MultiLocalCommand
, LocalCommand
{
38 private static final Logger logger
= LoggerFactory
.getLogger(DaemonCommand
.class);
41 public String
getName() {
46 public void attachToSubparser(final Subparser subparser
) {
47 final var defaultSocketPath
= new File(new File(IOUtils
.getRuntimeDir(), "signal-cli"), "socket");
48 subparser
.help("Run in daemon mode and provide a JSON-RPC or an experimental dbus interface.");
49 subparser
.addArgument("--dbus").action(Arguments
.storeTrue()).help("Expose a DBus interface on the user bus.");
50 subparser
.addArgument("--dbus-system", "--system")
51 .action(Arguments
.storeTrue())
52 .help("Expose a DBus interface on the system bus.");
53 subparser
.addArgument("--socket")
56 .setConst(defaultSocketPath
)
57 .help("Expose a JSON-RPC interface on a UNIX socket (default $XDG_RUNTIME_DIR/signal-cli/socket).");
58 subparser
.addArgument("--tcp")
60 .setConst("localhost:7583")
61 .help("Expose a JSON-RPC interface on a TCP socket (default localhost:7583).");
62 subparser
.addArgument("--http")
64 .setConst("localhost:8080")
65 .help("Expose a JSON-RPC interface as http endpoint (default localhost:8080).");
66 subparser
.addArgument("--no-receive-stdout")
67 .help("Don’t print received messages to stdout.")
68 .action(Arguments
.storeTrue());
69 subparser
.addArgument("--receive-mode")
70 .help("Specify when to start receiving messages.")
71 .type(Arguments
.enumStringType(ReceiveMode
.class))
72 .setDefault(ReceiveMode
.ON_START
);
73 subparser
.addArgument("--ignore-attachments")
74 .help("Don’t download attachments of received messages.")
75 .action(Arguments
.storeTrue());
76 subparser
.addArgument("--ignore-stories")
77 .help("Don’t receive story messages from the server.")
78 .action(Arguments
.storeTrue());
79 subparser
.addArgument("--send-read-receipts")
80 .help("Send read receipts for all incoming data messages (in addition to the default delivery receipts)")
81 .action(Arguments
.storeTrue());
85 public List
<OutputType
> getSupportedOutputTypes() {
86 return List
.of(OutputType
.PLAIN_TEXT
, OutputType
.JSON
);
90 public void handleCommand(
91 final Namespace ns
, final Manager m
, final OutputWriter outputWriter
92 ) throws CommandException
{
93 Shutdown
.installHandler();
94 logger
.info("Starting daemon in single-account mode for " + m
.getSelfNumber());
95 final var noReceiveStdOut
= Boolean
.TRUE
.equals(ns
.getBoolean("no-receive-stdout"));
96 final var receiveMode
= ns
.<ReceiveMode
>get("receive-mode");
97 final var receiveConfig
= getReceiveConfig(ns
);
99 m
.setReceiveConfig(receiveConfig
);
100 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
102 try (final var daemonHandler
= new SingleAccountDaemonHandler(m
, receiveMode
)) {
103 setup(ns
, daemonHandler
);
105 m
.addClosedListener(Shutdown
::triggerShutdown
);
108 Shutdown
.waitForShutdown();
109 } catch (InterruptedException ignored
) {
115 public void handleCommand(
116 final Namespace ns
, final MultiAccountManager c
, final OutputWriter outputWriter
117 ) throws CommandException
{
118 Shutdown
.installHandler();
119 logger
.info("Starting daemon in multi-account mode");
120 final var noReceiveStdOut
= Boolean
.TRUE
.equals(ns
.getBoolean("no-receive-stdout"));
121 final var receiveMode
= ns
.<ReceiveMode
>get("receive-mode");
122 final var receiveConfig
= getReceiveConfig(ns
);
123 c
.getManagers().forEach(m
-> {
124 m
.setReceiveConfig(receiveConfig
);
125 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
127 c
.addOnManagerAddedHandler(m
-> {
128 m
.setReceiveConfig(receiveConfig
);
129 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
132 try (final var daemonHandler
= new MultiAccountDaemonHandler(c
, receiveMode
)) {
133 setup(ns
, daemonHandler
);
135 synchronized (this) {
137 Shutdown
.waitForShutdown();
138 } catch (InterruptedException ignored
) {
144 private static void setup(final Namespace ns
, final DaemonHandler daemonHandler
) throws CommandException
{
145 final Channel inheritedChannel
;
147 if (System
.inheritedChannel() instanceof ServerSocketChannel serverChannel
) {
148 inheritedChannel
= serverChannel
;
149 logger
.info("Using inherited socket: " + serverChannel
.getLocalAddress());
150 daemonHandler
.runSocket(serverChannel
);
152 inheritedChannel
= null;
154 } catch (IOException e
) {
155 throw new IOErrorException("Failed to use inherited socket", e
);
158 final var socketFile
= ns
.<File
>get("socket");
159 if (socketFile
!= null) {
160 final var address
= UnixDomainSocketAddress
.of(socketFile
.toPath());
161 final var serverChannel
= IOUtils
.bindSocket(address
);
162 daemonHandler
.runSocket(serverChannel
);
165 final var tcpAddress
= ns
.getString("tcp");
166 if (tcpAddress
!= null) {
167 final var address
= IOUtils
.parseInetSocketAddress(tcpAddress
);
168 final var serverChannel
= IOUtils
.bindSocket(address
);
169 daemonHandler
.runSocket(serverChannel
);
172 final var httpAddress
= ns
.getString("http");
173 if (httpAddress
!= null) {
174 final var address
= IOUtils
.parseInetSocketAddress(httpAddress
);
175 daemonHandler
.runHttp(address
);
178 final var isDbusSystem
= Boolean
.TRUE
.equals(ns
.getBoolean("dbus-system"));
180 daemonHandler
.runDbus(true);
183 final var isDbusSession
= Boolean
.TRUE
.equals(ns
.getBoolean("dbus"));
184 if (isDbusSession
|| (
186 && socketFile
== null
187 && tcpAddress
== null
188 && httpAddress
== null
189 && inheritedChannel
== null
192 "Running daemon command without explicit mode is deprecated. Use --dbus to use the dbus interface.");
193 daemonHandler
.runDbus(false);
197 private void addDefaultReceiveHandler(Manager m
, OutputWriter outputWriter
, final boolean isWeakListener
) {
198 final var handler
= switch (outputWriter
) {
199 case PlainTextWriter writer
-> new ReceiveMessageHandler(m
, writer
);
200 case JsonWriter writer
-> new JsonReceiveMessageHandler(m
, writer
);
201 case null -> Manager
.ReceiveMessageHandler
.EMPTY
;
203 m
.addReceiveHandler(handler
, isWeakListener
);
206 private static abstract class DaemonHandler
implements AutoCloseable
{
208 protected final ReceiveMode receiveMode
;
209 protected final List
<AutoCloseable
> closeables
= new ArrayList
<>();
211 protected DaemonHandler(final ReceiveMode receiveMode
) {
212 this.receiveMode
= receiveMode
;
215 public abstract void runSocket(ServerSocketChannel serverChannel
) throws CommandException
;
217 public abstract void runDbus(boolean isDbusSystem
) throws CommandException
;
219 public abstract void runHttp(InetSocketAddress address
) throws CommandException
;
221 protected final void runSocket(final SocketHandler socketHandler
) {
222 socketHandler
.init();
223 this.closeables
.add(socketHandler
);
226 protected final void runDbus(
227 DbusHandler dbusHandler
228 ) throws CommandException
{
230 this.closeables
.add(dbusHandler
);
233 protected final void runHttp(final HttpServerHandler handler
) throws CommandException
{
236 } catch (IOException ex
) {
237 throw new IOErrorException("Failed to initialize HTTP Server", ex
);
239 this.closeables
.add(handler
);
243 public void close() {
244 for (final var closeable
: new ArrayList
<>(this.closeables
)) {
247 } catch (Exception e
) {
248 logger
.warn("Failed to close daemon handler", e
);
251 this.closeables
.clear();
255 private static final class SingleAccountDaemonHandler
extends DaemonHandler
{
257 private final Manager m
;
259 public SingleAccountDaemonHandler(final Manager m
, final ReceiveMode receiveMode
) {
265 public void runSocket(final ServerSocketChannel serverChannel
) {
266 runSocket(new SocketHandler(serverChannel
, m
, receiveMode
== ReceiveMode
.MANUAL
));
270 public void runDbus(final boolean isDbusSystem
) throws CommandException
{
271 runDbus(new DbusHandler(isDbusSystem
, m
, receiveMode
!= ReceiveMode
.ON_START
));
275 public void runHttp(InetSocketAddress address
) throws CommandException
{
276 runHttp(new HttpServerHandler(address
, m
));
280 private static final class MultiAccountDaemonHandler
extends DaemonHandler
{
282 private final MultiAccountManager c
;
284 public MultiAccountDaemonHandler(final MultiAccountManager c
, final ReceiveMode receiveMode
) {
290 public void runSocket(final ServerSocketChannel serverChannel
) {
291 runSocket(new SocketHandler(serverChannel
, c
, receiveMode
== ReceiveMode
.MANUAL
));
295 public void runDbus(final boolean isDbusSystem
) throws CommandException
{
296 runDbus(new DbusHandler(isDbusSystem
, c
, receiveMode
!= ReceiveMode
.ON_START
));
300 public void runHttp(final InetSocketAddress address
) throws CommandException
{
301 runHttp(new HttpServerHandler(address
, c
));