1 /*
2  *  Copyright (c) 2021, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "posix/platform/daemon.hpp"
30 
31 #if OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
32 #include <cutils/sockets.h>
33 #endif
34 #include <fcntl.h>
35 #include <signal.h>
36 #include <stdarg.h>
37 #include <string.h>
38 #include <sys/file.h>
39 #include <sys/socket.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/un.h>
43 #include <unistd.h>
44 
45 #include <openthread/cli.h>
46 
47 #include "cli/cli_config.h"
48 #include "common/code_utils.hpp"
49 #include "posix/platform/platform-posix.h"
50 
51 #if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
52 
53 #define OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".lock"
54 static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME) < sizeof(sockaddr_un::sun_path),
55               "OpenThread daemon socket name too long!");
56 
57 namespace ot {
58 namespace Posix {
59 
60 namespace {
61 
62 typedef char(Filename)[sizeof(sockaddr_un::sun_path)];
63 
GetFilename(Filename & aFilename,const char * aPattern)64 void GetFilename(Filename &aFilename, const char *aPattern)
65 {
66     int         rval;
67     const char *netIfName = strlen(gNetifName) > 0 ? gNetifName : OPENTHREAD_POSIX_CONFIG_THREAD_NETIF_DEFAULT_NAME;
68 
69     rval = snprintf(aFilename, sizeof(aFilename), aPattern, netIfName);
70     if (rval < 0 && static_cast<size_t>(rval) >= sizeof(aFilename))
71     {
72         DieNow(OT_EXIT_INVALID_ARGUMENTS);
73     }
74 }
75 
76 } // namespace
77 
78 const char Daemon::kLogModuleName[] = "Daemon";
79 
OutputFormat(const char * aFormat,...)80 int Daemon::OutputFormat(const char *aFormat, ...)
81 {
82     int     ret;
83     va_list ap;
84 
85     va_start(ap, aFormat);
86     ret = OutputFormatV(aFormat, ap);
87     va_end(ap);
88 
89     return ret;
90 }
91 
OutputFormatV(const char * aFormat,va_list aArguments)92 int Daemon::OutputFormatV(const char *aFormat, va_list aArguments)
93 {
94     static constexpr char truncatedMsg[] = "(truncated ...)";
95     char                  buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
96     int                   rval;
97 
98     static_assert(sizeof(truncatedMsg) < OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH,
99                   "OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH is too short!");
100 
101     rval = vsnprintf(buf, sizeof(buf), aFormat, aArguments);
102     VerifyOrExit(rval >= 0, LogWarn("Failed to format CLI output: %s", strerror(errno)));
103 
104     if (rval >= static_cast<int>(sizeof(buf)))
105     {
106         rval = static_cast<int>(sizeof(buf) - 1);
107         memcpy(buf + sizeof(buf) - sizeof(truncatedMsg), truncatedMsg, sizeof(truncatedMsg));
108     }
109 
110     VerifyOrExit(mSessionSocket != -1);
111 
112 #ifdef __linux__
113     // Don't die on SIGPIPE
114     rval = send(mSessionSocket, buf, static_cast<size_t>(rval), MSG_NOSIGNAL);
115 #else
116     rval = static_cast<int>(write(mSessionSocket, buf, static_cast<size_t>(rval)));
117 #endif
118 
119     if (rval < 0)
120     {
121         LogWarn("Failed to write CLI output: %s", strerror(errno));
122         close(mSessionSocket);
123         mSessionSocket = -1;
124     }
125 
126 exit:
127     return rval;
128 }
129 
InitializeSessionSocket(void)130 void Daemon::InitializeSessionSocket(void)
131 {
132     int newSessionSocket;
133     int rval;
134 
135     VerifyOrExit((newSessionSocket = accept(mListenSocket, nullptr, nullptr)) != -1, rval = -1);
136 
137     VerifyOrExit((rval = fcntl(newSessionSocket, F_GETFD, 0)) != -1);
138 
139     rval |= FD_CLOEXEC;
140 
141     VerifyOrExit((rval = fcntl(newSessionSocket, F_SETFD, rval)) != -1);
142 
143 #ifndef __linux__
144     // some platforms (macOS, Solaris) don't have MSG_NOSIGNAL
145     // SOME of those (macOS, but NOT Solaris) support SO_NOSIGPIPE
146     // if we have SO_NOSIGPIPE, then set it. Otherwise, we're going
147     // to simply ignore it.
148 #if defined(SO_NOSIGPIPE)
149     rval = setsockopt(newSessionSocket, SOL_SOCKET, SO_NOSIGPIPE, &rval, sizeof(rval));
150     VerifyOrExit(rval != -1);
151 #else
152 #warning "no support for MSG_NOSIGNAL or SO_NOSIGPIPE"
153 #endif
154 #endif // __linux__
155 
156     if (mSessionSocket != -1)
157     {
158         close(mSessionSocket);
159     }
160     mSessionSocket = newSessionSocket;
161 
162 exit:
163     if (rval == -1)
164     {
165         LogWarn("Failed to initialize session socket: %s", strerror(errno));
166         if (newSessionSocket != -1)
167         {
168             close(newSessionSocket);
169         }
170     }
171     else
172     {
173         LogInfo("Session socket is ready");
174     }
175 }
176 
177 #if OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
createListenSocketOrDie(void)178 void Daemon::createListenSocketOrDie(void)
179 {
180     Filename socketFile;
181 
182     // Don't use OPENTHREAD_POSIX_DAEMON_SOCKET_NAME because android_get_control_socket
183     // below already assumes parent /dev/socket dir
184     GetFilename(socketFile, "ot-daemon/%s.sock");
185 
186     // This returns the init-managed stream socket which is already bind to
187     // /dev/socket/ot-daemon/<interface-name>.sock
188     mListenSocket = android_get_control_socket(socketFile);
189 
190     if (mListenSocket == -1)
191     {
192         DieNowWithMessage("android_get_control_socket", OT_EXIT_ERROR_ERRNO);
193     }
194 }
195 #else
createListenSocketOrDie(void)196 void Daemon::createListenSocketOrDie(void)
197 {
198     struct sockaddr_un sockname;
199     int ret;
200 
201     class AllowAllGuard
202     {
203     public:
204         AllowAllGuard(void)
205         {
206             const char *allowAll = getenv("OT_DAEMON_ALLOW_ALL");
207             mAllowAll = (allowAll != nullptr && strcmp("1", allowAll) == 0);
208 
209             if (mAllowAll)
210             {
211                 mMode = umask(0);
212             }
213         }
214         ~AllowAllGuard(void)
215         {
216             if (mAllowAll)
217             {
218                 umask(mMode);
219             }
220         }
221 
222     private:
223         bool mAllowAll = false;
224         mode_t mMode = 0;
225     };
226 
227     mListenSocket = SocketWithCloseExec(AF_UNIX, SOCK_STREAM, 0, kSocketNonBlock);
228 
229     if (mListenSocket == -1)
230     {
231         DieNow(OT_EXIT_FAILURE);
232     }
233 
234     {
235         static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK) == sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME),
236                       "sock and lock file name pattern should have the same length!");
237         Filename lockfile;
238 
239         GetFilename(lockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK);
240 
241         mDaemonLock = open(lockfile, O_CREAT | O_RDONLY | O_CLOEXEC, 0600);
242     }
243 
244     if (mDaemonLock == -1)
245     {
246         DieNowWithMessage("open", OT_EXIT_ERROR_ERRNO);
247     }
248 
249     if (flock(mDaemonLock, LOCK_EX | LOCK_NB) == -1)
250     {
251         DieNowWithMessage("flock", OT_EXIT_ERROR_ERRNO);
252     }
253 
254     memset(&sockname, 0, sizeof(struct sockaddr_un));
255 
256     sockname.sun_family = AF_UNIX;
257     GetFilename(sockname.sun_path, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
258     (void)unlink(sockname.sun_path);
259 
260     {
261         AllowAllGuard allowAllGuard;
262 
263         ret = bind(mListenSocket, reinterpret_cast<const struct sockaddr *>(&sockname), sizeof(struct sockaddr_un));
264     }
265 
266     if (ret == -1)
267     {
268         DieNowWithMessage("bind", OT_EXIT_ERROR_ERRNO);
269     }
270 }
271 #endif // OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
272 
SetUp(void)273 void Daemon::SetUp(void)
274 {
275     int ret;
276 
277     // This allows implementing pseudo reset.
278     VerifyOrExit(mListenSocket == -1);
279     createListenSocketOrDie();
280 
281     //
282     // only accept 1 connection.
283     //
284     ret = listen(mListenSocket, 1);
285     if (ret == -1)
286     {
287         DieNowWithMessage("listen", OT_EXIT_ERROR_ERRNO);
288     }
289 
290 exit:
291 #if OPENTHREAD_POSIX_CONFIG_DAEMON_CLI_ENABLE
292     otSysCliInitUsingDaemon(gInstance);
293 #endif
294 
295     Mainloop::Manager::Get().Add(*this);
296 
297     return;
298 }
299 
TearDown(void)300 void Daemon::TearDown(void)
301 {
302     Mainloop::Manager::Get().Remove(*this);
303 
304     if (mSessionSocket != -1)
305     {
306         close(mSessionSocket);
307         mSessionSocket = -1;
308     }
309 
310 #if !OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
311     // The `mListenSocket` is managed by `init` on Android
312     if (mListenSocket != -1)
313     {
314         close(mListenSocket);
315         mListenSocket = -1;
316     }
317 
318     if (gPlatResetReason != OT_PLAT_RESET_REASON_SOFTWARE)
319     {
320         Filename sockfile;
321 
322         GetFilename(sockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
323         LogDebg("Removing daemon socket: %s", sockfile);
324         (void)unlink(sockfile);
325     }
326 
327     if (mDaemonLock != -1)
328     {
329         (void)flock(mDaemonLock, LOCK_UN);
330         close(mDaemonLock);
331         mDaemonLock = -1;
332     }
333 #endif
334 }
335 
Update(otSysMainloopContext & aContext)336 void Daemon::Update(otSysMainloopContext &aContext)
337 {
338     if (mListenSocket != -1)
339     {
340         FD_SET(mListenSocket, &aContext.mReadFdSet);
341         FD_SET(mListenSocket, &aContext.mErrorFdSet);
342 
343         if (aContext.mMaxFd < mListenSocket)
344         {
345             aContext.mMaxFd = mListenSocket;
346         }
347     }
348 
349     if (mSessionSocket != -1)
350     {
351         FD_SET(mSessionSocket, &aContext.mReadFdSet);
352         FD_SET(mSessionSocket, &aContext.mErrorFdSet);
353 
354         if (aContext.mMaxFd < mSessionSocket)
355         {
356             aContext.mMaxFd = mSessionSocket;
357         }
358     }
359 
360     return;
361 }
362 
Process(const otSysMainloopContext & aContext)363 void Daemon::Process(const otSysMainloopContext &aContext)
364 {
365     ssize_t rval;
366 
367     VerifyOrExit(mListenSocket != -1);
368 
369     if (FD_ISSET(mListenSocket, &aContext.mErrorFdSet))
370     {
371         DieNowWithMessage("daemon socket error", OT_EXIT_FAILURE);
372     }
373     else if (FD_ISSET(mListenSocket, &aContext.mReadFdSet))
374     {
375         InitializeSessionSocket();
376     }
377 
378     VerifyOrExit(mSessionSocket != -1);
379 
380     if (FD_ISSET(mSessionSocket, &aContext.mErrorFdSet))
381     {
382         close(mSessionSocket);
383         mSessionSocket = -1;
384     }
385     else if (FD_ISSET(mSessionSocket, &aContext.mReadFdSet))
386     {
387         uint8_t buffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
388 
389         // leave 1 byte for the null terminator
390         rval = read(mSessionSocket, buffer, sizeof(buffer) - 1);
391 
392         if (rval > 0)
393         {
394             buffer[rval] = '\0';
395 #if OPENTHREAD_POSIX_CONFIG_DAEMON_CLI_ENABLE
396             otCliInputLine(reinterpret_cast<char *>(buffer));
397 #else
398             OutputFormat("Error: CLI is disabled!\n");
399 #endif
400         }
401         else
402         {
403             if (rval < 0)
404             {
405                 LogWarn("Daemon read: %s", strerror(errno));
406             }
407             close(mSessionSocket);
408             mSessionSocket = -1;
409         }
410     }
411 
412 exit:
413     return;
414 }
415 
Get(void)416 Daemon &Daemon::Get(void)
417 {
418     static Daemon sInstance;
419 
420     return sInstance;
421 }
422 
423 } // namespace Posix
424 } // namespace ot
425 #endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
426