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(
95 final Namespace ns
, final Manager m
, final OutputWriter outputWriter
96 ) throws CommandException
{
97 Shutdown
.installHandler();
98 logger
.info("Starting daemon in single-account mode for " + m
.getSelfNumber());
99 final var noReceiveStdOut
= Boolean
.TRUE
.equals(ns
.getBoolean("no-receive-stdout"));
100 final var receiveMode
= ns
.<ReceiveMode
>get("receive-mode");
101 final var receiveConfig
= getReceiveConfig(ns
);
103 m
.setReceiveConfig(receiveConfig
);
104 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
106 try (final var daemonHandler
= new SingleAccountDaemonHandler(m
, receiveMode
)) {
107 setup(ns
, daemonHandler
);
109 m
.addClosedListener(Shutdown
::triggerShutdown
);
112 Shutdown
.waitForShutdown();
113 } catch (InterruptedException ignored
) {
119 public void handleCommand(
120 final Namespace ns
, final MultiAccountManager c
, final OutputWriter outputWriter
121 ) throws CommandException
{
122 Shutdown
.installHandler();
123 logger
.info("Starting daemon in multi-account mode");
124 final var noReceiveStdOut
= Boolean
.TRUE
.equals(ns
.getBoolean("no-receive-stdout"));
125 final var receiveMode
= ns
.<ReceiveMode
>get("receive-mode");
126 final var receiveConfig
= getReceiveConfig(ns
);
127 c
.getManagers().forEach(m
-> {
128 m
.setReceiveConfig(receiveConfig
);
129 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
131 c
.addOnManagerAddedHandler(m
-> {
132 m
.setReceiveConfig(receiveConfig
);
133 addDefaultReceiveHandler(m
, noReceiveStdOut ?
null : outputWriter
, receiveMode
!= ReceiveMode
.ON_START
);
136 try (final var daemonHandler
= new MultiAccountDaemonHandler(c
, receiveMode
)) {
137 setup(ns
, daemonHandler
);
139 synchronized (this) {
141 Shutdown
.waitForShutdown();
142 } catch (InterruptedException ignored
) {
148 private static void setup(final Namespace ns
, final DaemonHandler daemonHandler
) throws CommandException
{
149 final Channel inheritedChannel
;
151 if (System
.inheritedChannel() instanceof ServerSocketChannel serverChannel
) {
152 inheritedChannel
= serverChannel
;
153 logger
.info("Using inherited socket: " + serverChannel
.getLocalAddress());
154 daemonHandler
.runSocket(serverChannel
);
156 inheritedChannel
= null;
158 } catch (IOException e
) {
159 throw new IOErrorException("Failed to use inherited socket", e
);
162 final var socketFile
= ns
.<File
>get("socket");
163 if (socketFile
!= null) {
164 final var address
= UnixDomainSocketAddress
.of(socketFile
.toPath());
165 final var serverChannel
= IOUtils
.bindSocket(address
);
166 daemonHandler
.runSocket(serverChannel
);
169 final var tcpAddress
= ns
.getString("tcp");
170 if (tcpAddress
!= null) {
171 final var address
= IOUtils
.parseInetSocketAddress(tcpAddress
);
172 final var serverChannel
= IOUtils
.bindSocket(address
);
173 daemonHandler
.runSocket(serverChannel
);
176 final var httpAddress
= ns
.getString("http");
177 if (httpAddress
!= null) {
178 final var address
= IOUtils
.parseInetSocketAddress(httpAddress
);
179 daemonHandler
.runHttp(address
);
182 final var isDbusSystem
= Boolean
.TRUE
.equals(ns
.getBoolean("dbus-system"));
184 final var busName
= ns
.getString("bus-name");
185 daemonHandler
.runDbus(true, busName
);
188 final var isDbusSession
= Boolean
.TRUE
.equals(ns
.getBoolean("dbus"));
190 final var busName
= ns
.getString("bus-name");
191 daemonHandler
.runDbus(false, busName
);
196 && socketFile
== null
197 && tcpAddress
== null
198 && httpAddress
== null
199 && inheritedChannel
== null) {
201 "Running daemon command without explicit mode is deprecated. Use --dbus to use the dbus interface.");
202 daemonHandler
.runDbus(false, DbusConfig
.getBusname());
206 private void addDefaultReceiveHandler(Manager m
, OutputWriter outputWriter
, final boolean isWeakListener
) {
207 final var handler
= switch (outputWriter
) {
208 case PlainTextWriter writer
-> new ReceiveMessageHandler(m
, writer
);
209 case JsonWriter writer
-> new JsonReceiveMessageHandler(m
, writer
);
210 case null -> Manager
.ReceiveMessageHandler
.EMPTY
;
212 m
.addReceiveHandler(handler
, isWeakListener
);
215 private static abstract class DaemonHandler
implements AutoCloseable
{
217 protected final ReceiveMode receiveMode
;
218 protected final List
<AutoCloseable
> closeables
= new ArrayList
<>();
220 protected DaemonHandler(final ReceiveMode receiveMode
) {
221 this.receiveMode
= receiveMode
;
224 public abstract void runSocket(ServerSocketChannel serverChannel
) throws CommandException
;
226 public abstract void runDbus(boolean isDbusSystem
, final String busname
) throws CommandException
;
228 public abstract void runHttp(InetSocketAddress address
) throws CommandException
;
230 protected final void runSocket(final SocketHandler socketHandler
) {
231 socketHandler
.init();
232 this.closeables
.add(socketHandler
);
235 protected final void runDbus(
236 DbusHandler dbusHandler
237 ) throws CommandException
{
239 this.closeables
.add(dbusHandler
);
242 protected final void runHttp(final HttpServerHandler handler
) throws CommandException
{
245 } catch (IOException ex
) {
246 throw new IOErrorException("Failed to initialize HTTP Server", ex
);
248 this.closeables
.add(handler
);
252 public void close() {
253 for (final var closeable
: new ArrayList
<>(this.closeables
)) {
256 } catch (Exception e
) {
257 logger
.warn("Failed to close daemon handler", e
);
260 this.closeables
.clear();
264 private static final class SingleAccountDaemonHandler
extends DaemonHandler
{
266 private final Manager m
;
268 public SingleAccountDaemonHandler(final Manager m
, final ReceiveMode receiveMode
) {
274 public void runSocket(final ServerSocketChannel serverChannel
) {
275 runSocket(new SocketHandler(serverChannel
, m
, receiveMode
== ReceiveMode
.MANUAL
));
279 public void runDbus(final boolean isDbusSystem
, final String busname
) throws CommandException
{
280 runDbus(new DbusHandler(isDbusSystem
, busname
, m
, receiveMode
!= ReceiveMode
.ON_START
));
284 public void runHttp(InetSocketAddress address
) throws CommandException
{
285 runHttp(new HttpServerHandler(address
, m
));
289 private static final class MultiAccountDaemonHandler
extends DaemonHandler
{
291 private final MultiAccountManager c
;
293 public MultiAccountDaemonHandler(final MultiAccountManager c
, final ReceiveMode receiveMode
) {
299 public void runSocket(final ServerSocketChannel serverChannel
) {
300 runSocket(new SocketHandler(serverChannel
, c
, receiveMode
== ReceiveMode
.MANUAL
));
304 public void runDbus(final boolean isDbusSystem
, final String busname
) throws CommandException
{
305 runDbus(new DbusHandler(isDbusSystem
, busname
, c
, receiveMode
!= ReceiveMode
.ON_START
));
309 public void runHttp(final InetSocketAddress address
) throws CommandException
{
310 runHttp(new HttpServerHandler(address
, c
));