1 // Written in D programming language
2 /**
3 *    Describes server attached to tty console. Specified delegate 
4 *    is called when SIGHUP signal is caught (linux only).
5 *
6 *    See_Also: daemon
7 *
8 *    Copyright: © 2014 DSoftOut
9 *    License: Subject to the terms of the MIT license, as written in the included LICENSE file.
10 *    Authors: NCrashed <ncrashed@gmail.com>
11 *    
12 */
13 module terminal;
14 
15 import std.c.stdlib;
16 import std.stdio;
17 import std.conv;
18 import std.exception;
19 version (linux) 
20 {
21     import std.c.linux.linux;
22     import core.sys.linux.errno;
23 }
24 import dlogg.log;
25 import util;
26 
27 private 
28 {
29     void delegate() savedListener, savedTermListener;
30     void delegate() savedRotateListener;
31     
32     shared ILogger savedLogger;
33     
34     string savedPidFile;
35     string savedLockFile;
36     
37     extern (C) 
38     {
39         version (linux) 
40         {
41             // Signal trapping in Linux
42             alias void function(int) sighandler_t;
43             sighandler_t signal(int signum, sighandler_t handler);
44             int __libc_current_sigrtmin();
45             char* strerror(int errnum);
46             
47             void signal_handler_terminal(int sig)
48             {
49                 if(sig == SIGABRT || sig == SIGTERM || sig == SIGQUIT || sig == SIGINT || sig == SIGQUIT)
50                 {
51                     savedLogger.logInfo(text("Signal ", to!string(sig), " caught..."));
52                     savedTermListener();
53                 } else if(sig == SIGHUP)
54                 {
55                     savedLogger.logInfo(text("Signal ", to!string(sig), " caught..."));
56                     savedListener();
57                 } else if(sig == SIGROTATE)
58                 {
59                     savedLogger.logInfo(text("User signal ", sig, " is caught!"));
60                     savedRotateListener();
61                 }
62             }
63         }
64     }
65     version(linux)
66     {
67         private immutable int SIGROTATE;
68         static this()
69         {
70             SIGROTATE = __libc_current_sigrtmin + 10;
71         }
72         
73         void dropRootPrivileges(int groupid, int userid)
74         {
75             if (getuid() == 0) 
76             {
77                 if(groupid < 0 || userid < 0)
78                 {
79                     savedLogger.logWarning("Running as root, but doesn't specified groupid and/or userid for"
80                         " privileges lowing!");
81                     return;
82                 }
83                 
84                 savedLogger.logInfo("Running as root, dropping privileges...");
85                 // process is running as root, drop privileges 
86                 if (setgid(groupid) != 0)
87                 {
88                     savedLogger.logError(text("setgid: Unable to drop group privileges: ", strerror(errno).fromStringz));
89                     assert(false);
90                 }
91                 if (setuid(userid) != 0)
92                 {
93                     savedLogger.logError(text("setuid: Unable to drop user privileges: ", strerror(errno).fromStringz));
94                     assert(false);
95                 }
96             }
97         }
98     }
99 }
100 
101 /**
102 *    Run application as casual process (attached to tty) with $(B progMain) main function and passes $(B args) into it. 
103 *    If daemon catches SIGHUP signal, $(B listener) delegate is called (available on linux only).
104 *
105 *    If application receives some kind of terminating signal, the $(B termListener) is called. $(B termListener) should
106 *    end $(B progMain) to be able to clearly shutdown the application.
107 *
108 *    If application receives "real-time" signal $(B SIGROTATE) defined as SIGRTMIN+10, then $(B rotateListener) is called
109 *    to handle 'logrotate' utility.
110 *
111 *    Daemon writes log message into provided $(B logger).
112 *
113 *   $(B groupid) and $(B userid) are used to low privileges with run as root. 
114 */
115 int runTerminal(shared ILogger logger, int delegate(string[]) progMain, string[] args, void delegate() listener,
116     void delegate() termListener, void delegate() rotateListener, int groupid = -1, int userid = -1)
117 {
118     savedLogger = logger;
119     
120     // dropping root
121     dropRootPrivileges(groupid, userid);
122     
123     version (linux) 
124     {
125         void bindSignal(int sig, sighandler_t handler)
126         {
127             enforce(signal(sig, handler) != SIG_ERR, text("Cannot catch signal ", sig));
128         }
129         
130         savedTermListener = termListener;
131         bindSignal(SIGABRT, &signal_handler_terminal);
132         bindSignal(SIGTERM, &signal_handler_terminal);
133         bindSignal(SIGQUIT, &signal_handler_terminal);
134         bindSignal(SIGINT, &signal_handler_terminal);
135         bindSignal(SIGQUIT, &signal_handler_terminal);
136         
137         savedListener = listener;
138         bindSignal(SIGHUP, &signal_handler_terminal);
139         
140         savedRotateListener = rotateListener;
141         bindSignal(SIGROTATE, &signal_handler_terminal);
142     } 
143     else
144     {
145         logger.logError("This platform doesn't support signals. Updating json-sql table by signal is disabled!");
146     }
147 
148     logger.logInfo("Server is starting in terminal mode...");
149     return progMain(args);
150 }