Improve logging.
[apps/pfixtools.git] / common / common.c
1 /******************************************************************************/
2 /*          pfixtools: a collection of postfix related tools                  */
3 /*          ~~~~~~~~~                                                         */
4 /*  ________________________________________________________________________  */
5 /*                                                                            */
6 /*  Redistribution and use in source and binary forms, with or without        */
7 /*  modification, are permitted provided that the following conditions        */
8 /*  are met:                                                                  */
9 /*                                                                            */
10 /*  1. Redistributions of source code must retain the above copyright         */
11 /*     notice, this list of conditions and the following disclaimer.          */
12 /*  2. Redistributions in binary form must reproduce the above copyright      */
13 /*     notice, this list of conditions and the following disclaimer in the    */
14 /*     documentation and/or other materials provided with the distribution.   */
15 /*  3. The names of its contributors may not be used to endorse or promote    */
16 /*     products derived from this software without specific prior written     */
17 /*     permission.                                                            */
18 /*                                                                            */
19 /*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
20 /*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
21 /*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
22 /*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
23 /*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
24 /*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
25 /*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
26 /*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
27 /*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
28 /*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
29 /*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
30 /******************************************************************************/
31
32 /*
33  * Copyright © 2007 Pierre Habouzit
34  * Copyright © 2008 Florent Bruneau
35  */
36
37 #include <fcntl.h>
38 #include <grp.h>
39 #include <pwd.h>
40 #include <sys/un.h>
41
42 #include "common.h"
43
44 bool daemon_process   = true;
45 int  log_level        = LOG_INFO;
46 bool log_syslog       = false;
47 const char *log_state = "";
48
49 static FILE *pidfile = NULL;
50
51 void common_sighandler(int sig)
52 {
53     switch (sig) {
54       default:
55         err("Killed (got signal %d)...", sig);
56         exit(-1);
57     }
58 }
59
60 int setnonblock(int sock)
61 {
62     int res = fcntl(sock, F_GETFL);
63
64     if (res < 0) {
65         UNIXERR("fcntl");
66         return -1;
67     }
68
69     if (fcntl(sock, F_SETFL, res | O_NONBLOCK) < 0) {
70         UNIXERR("fcntl");
71         return -1;
72     }
73
74     return 0;
75 }
76
77 int tcp_bind(const struct sockaddr *addr, socklen_t len)
78 {
79     int sock;
80
81     switch (addr->sa_family) {
82       case AF_UNIX:
83         unlink(((struct sockaddr_un *)addr)->sun_path);
84         sock = socket(PF_UNIX, SOCK_STREAM, 0);
85         break;
86       case AF_INET:
87         sock = socket(PF_INET, SOCK_STREAM, 0);
88         break;
89       case AF_INET6:
90         sock = socket(PF_INET6, SOCK_STREAM, 0);
91         break;
92       default:
93         errno = EINVAL;
94         return -1;
95     }
96
97     if (sock < 0) {
98         UNIXERR("socket");
99         return -1;
100     }
101
102     if (addr->sa_family != AF_UNIX) {
103         int v = 1;
104         if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) < 0) {
105             UNIXERR("setsockopt(SO_REUSEADDR)");
106             close(sock);
107             return -1;
108         }
109     }
110
111     if (bind(sock, addr, len) < 0) {
112         UNIXERR("bind");
113         close(sock);
114         return -1;
115     }
116
117     return sock;
118 }
119
120 int tcp_listen(const struct sockaddr *addr, socklen_t len)
121 {
122     int sock = tcp_bind(addr, len);
123     if (listen(sock, 0) < 0) {
124         UNIXERR("bind");
125         close(sock);
126         return -1;
127     }
128     return sock;
129 }
130
131 int tcp_listen_nonblock(const struct sockaddr *addr, socklen_t len)
132 {
133     int sock = tcp_bind(addr, len);
134     if (setnonblock(sock)) {
135         close(sock);
136         return -1;
137     }
138     if (listen(sock, 0) < 0) {
139         UNIXERR("bind");
140         close(sock);
141         return -1;
142     }
143     return sock;
144 }
145
146 int accept_nonblock(int fd)
147 {
148     int sock = accept(fd, NULL, 0);
149
150     if (sock < 0) {
151         UNIXERR("accept");
152         return -1;
153     }
154
155     if (setnonblock(sock)) {
156         close(sock);
157         return -1;
158     }
159
160     return sock;
161 }
162
163 int xwrite(int fd, const char *s, size_t l)
164 {
165     while (l > 0) {
166         int nb = write(fd, s, l);
167         if (nb < 0) {
168             if (errno == EINTR || errno == EAGAIN)
169                 continue;
170             return -1;
171         }
172         l -= nb;
173     }
174     return 0;
175 }
176
177 int daemon_detach(void)
178 {
179     pid_t pid;
180
181     close(STDIN_FILENO);
182     close(STDOUT_FILENO);
183     close(STDERR_FILENO);
184
185     open("/dev/null", O_RDWR);
186     open("/dev/null", O_RDWR);
187     open("/dev/null", O_RDWR);
188
189     pid = fork();
190     if (pid < 0) {
191         return -1;
192     }
193     if (pid) {
194         daemon_process = false;
195         exit(0);
196     }
197
198     setsid();
199     return 0;
200 }
201
202 int drop_privileges(const char *user, const char *group)
203 {
204     if (!geteuid()) {
205         struct passwd *pw;
206         struct group *gr;
207
208         if (group) {
209             gr = getgrnam(group);
210             if (!gr)
211                 return -1;
212             setgid(gr->gr_gid);
213         }
214
215         pw = getpwnam(user);
216         if (!pw)
217             return -1;
218         if (!group) {
219             setgid(pw->pw_gid);
220         }
221         setuid(pw->pw_uid);
222     }
223
224     return 0;
225 }
226
227 int pidfile_open(const char *name)
228 {
229     if (name) {
230         pidfile = fopen(name, "w");
231         if (!pidfile)
232             return -1;
233         fprintf(pidfile, "%d\n", getpid());
234         return fflush(pidfile);
235     }
236     return 0;
237 }
238
239 int pidfile_refresh(void)
240 {
241     if (pidfile) {
242         rewind(pidfile);
243         ftruncate(fileno(pidfile), 0);
244         fprintf(pidfile, "%d\n", getpid());
245         return fflush(pidfile);
246     }
247     return 0;
248 }
249
250 static void pidfile_close(void)
251 {
252     if (pidfile) {
253         if (daemon_process) {
254             rewind(pidfile);
255             ftruncate(fileno(pidfile), 0);
256         }
257         fclose(pidfile);
258         pidfile = NULL;
259     }
260 }
261
262 int common_setup(const char* pidfilename, bool unsafe, const char* runas_user,
263                  const char* runas_group, bool daemonize)
264 {
265     if (pidfile_open(pidfilename) < 0) {
266         crit("unable to write pidfile %s", pidfilename);
267         return EXIT_FAILURE;
268     }
269
270     if (!unsafe && drop_privileges(runas_user, runas_group) < 0) {
271         crit("unable to drop privileges");
272         return EXIT_FAILURE;
273     }
274
275     if (daemonize && daemon_detach() < 0) {
276         crit("unable to fork");
277         return EXIT_FAILURE;
278     }
279
280     pidfile_refresh();
281     return EXIT_SUCCESS;
282 }
283
284 #include "array.h"
285
286 ARRAY(exitcall_t)
287
288 static A(exitcall_t) __exit = ARRAY_INIT;
289
290 void common_register_exit(exitcall_t exitcall)
291 {
292     array_add(__exit, exitcall);
293 }
294
295 static void common_shutdown(void)
296 {
297     log_state = "stopping ";
298     if (daemon_process && log_syslog) {
299         info("");
300     }
301     pidfile_close();
302     for (int i = array_len(__exit) - 1 ; i >= 0 ; --i) {
303         array_elt(__exit, i)();
304     }
305     array_wipe(__exit);
306 }
307
308 void common_init(void)
309 {
310     static bool __ran = false;
311     if (__ran) {
312         return;
313     }
314     log_state = "starting ";
315     if (atexit(common_shutdown)) {
316         fputs("Cannot hook my atexit function, quitting !\n", stderr);
317         abort();
318     }
319     __ran = true;
320 }
321