Add to git.
[pthrlib.git] / src / pthr_server.c
1 /* Generic server process.
2  * - by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: pthr_server.c,v 1.10 2003/02/05 22:13:33 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif
29
30 #ifdef HAVE_FCNTL_H
31 #include <fcntl.h>
32 #endif
33
34 #ifdef HAVE_SYSLOG_H
35 #include <syslog.h>
36 #endif
37
38 #ifdef HAVE_SYS_TYPES_H
39 #include <sys/types.h>
40 #endif
41
42 #ifdef HAVE_SYS_SOCKET_H
43 #include <sys/socket.h>
44 #endif
45
46 #ifdef HAVE_NETINET_IN_H
47 #include <netinet/in.h>
48 #endif
49
50 #ifdef HAVE_ARPA_INET_H
51 #include <arpa/inet.h>
52 #endif
53
54 #ifdef HAVE_PWD_H
55 #include <pwd.h>
56 #endif
57
58 #ifdef HAVE_GRP_H
59 #include <grp.h>
60 #endif
61
62 #ifdef HAVE_STRING_H
63 #include <string.h>
64 #endif
65
66 #ifdef HAVE_EXECINFO_H
67 #include <execinfo.h>
68 #endif
69
70 #ifdef HAVE_SIGNAL_H
71 #include <signal.h>
72 #endif
73
74 #include "pthr_listener.h"
75 #include "pthr_server.h"
76
77 static int default_port = 80;
78 static char port_option_name = 'p';
79 static in_addr_t default_address = INADDR_ANY;
80 static char address_option_name = 'a';
81 static int disable_syslog = 0;
82 static const char *package_name = PACKAGE " " VERSION;
83 static int disable_fork = 0;
84 static int disable_chdir = 0;
85 static int disable_close = 0;
86 static const char *root = 0;
87 static const char *username = 0;
88 static const char *stderr_file = 0;
89 static void (*startup_fn)(int argc, char *argv[]) = 0;
90 static int enable_stack_trace_on_segv = 0;
91
92 #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
93 #define CAN_CATCH_SIGSEGV
94 #endif
95
96 #ifdef CAN_CATCH_SIGSEGV
97 static void catch_sigsegv (int);
98 #endif
99
100 extern char *optarg;
101 extern int optind;
102 extern int opterr;
103
104 #ifdef __OpenBSD__
105 extern int optreset;
106 #endif
107
108 #ifndef INADDR_NONE
109 #define INADDR_NONE ((in_addr_t) 0xffffffff)
110 #endif
111
112 void
113 pthr_server_main_loop (int argc, char *argv[],
114                        void (*processor_fn) (int sock, void *))
115 {
116   struct sockaddr_in addr;
117   int port = default_port;
118   in_addr_t address = default_address;
119   int sock, one = 1;
120   int c;
121   char getopt_scr[10];
122
123   /* Reset the getopt library. */
124   optind = 1;
125   opterr = 0;
126
127 #ifdef __OpenBSD__
128   optreset = 1;
129 #endif
130
131   /* Handle command-line arguments. Get the correct port and address to
132    * listen on.
133    */
134   snprintf (getopt_scr, sizeof getopt_scr,
135             "%c:%c:", port_option_name, address_option_name);
136
137   while ((c = getopt (argc, argv, getopt_scr)) != -1)
138     {
139       if (port_option_name == c)
140         {
141           if (sscanf (optarg, "%d", &port) != 1)
142             {
143               fprintf (stderr, "invalid port option: %s\n", optarg);
144               exit (1);
145             }
146         }
147       else if (address_option_name == c)
148         {
149           /* Should really use inet_aton() (or even inet_pton())
150            * but inet_addr() is more widely supported.
151            */
152           address = inet_addr (optarg);
153           if (INADDR_NONE == address)
154             {
155               fprintf (stderr, "invalid address: %s\n", optarg);
156               exit(1);
157             }
158         }
159     }
160
161   /* Bind a socket to the appropriate port. */
162   sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
163   if (sock < 0) abort ();
164
165   setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof (one));
166
167   memset(&addr, 0, sizeof(addr));
168   addr.sin_family = AF_INET;
169   addr.sin_addr.s_addr = address;
170   addr.sin_port = htons (port);
171   if (bind (sock, (struct sockaddr *) &addr, sizeof addr) < 0)
172     {
173       /* Generally this means that the port is already bound. */
174       perror ("bind");
175       exit (1);
176     }
177
178   /* Put the socket into listen mode. */
179   if (listen (sock, 10) < 0) abort ();
180
181   /* Set the new socket to non-blocking. */
182   if (fcntl (sock, F_SETFL, O_NONBLOCK) < 0) abort ();
183
184   /* If running as root, and asked to chroot, then chroot. */
185   if (root && geteuid () == 0)
186     {
187       if (chroot (root) == -1)
188         {
189           perror (root);
190           exit (1);
191         }
192     }
193
194   /* If running as root, and asked to change user, do so now. */
195   if (username && geteuid () == 0)
196     {
197       struct passwd *pw = getpwnam (username);
198
199       if (pw == 0)
200         {
201           fprintf (stderr, "username not found: %s", username);
202           exit (1);
203         }
204
205       if (initgroups (username, pw->pw_gid) == -1 ||
206           setgid (pw->pw_gid) == -1 ||
207           setuid (pw->pw_uid) == -1)
208         {
209           perror ("setuid");
210           exit (1);
211         }
212     }
213
214   if (!disable_chdir)
215     chdir ("/");
216
217   if (!disable_close)
218     {
219       /* Close connections to the terminal. */
220       close (0);
221       close (1);
222       close (2);
223       open ("/dev/null", O_RDWR);
224       dup2 (0, 1);
225       dup2 (0, 2);
226       setsid ();
227     }
228
229   if (stderr_file)
230     {
231       /* Reopen stderr on a log file. */
232       close (2);
233       if (open (stderr_file, O_WRONLY|O_CREAT|O_APPEND, 0644) == -1)
234         {
235           /* Hard to output an error message at this point ... */
236           abort ();
237         }
238     }
239
240   if (!disable_fork)
241     {
242       /* Fork into background. */
243       int pid = fork ();
244
245       if (pid < 0) { perror ("fork"); exit (1); }
246       else if (pid > 0)
247         {
248           /* Parent process: exit normally. */
249           exit (0);
250         }
251     }
252
253   if (!disable_syslog)
254     {
255       /* Open connection to syslog. */
256       openlog (package_name, LOG_PID|LOG_NDELAY, LOG_USER);
257       syslog (LOG_INFO,
258               "%s starting up on port %d", package_name, port);
259     }
260
261   if (enable_stack_trace_on_segv)
262     {
263 #ifdef CAN_CATCH_SIGSEGV
264       struct sigaction sa;
265
266       /* Print a stack trace to stderr if we get SIGSEGV. */
267       memset (&sa, 0, sizeof sa);
268       sa.sa_handler = catch_sigsegv;
269       sa.sa_flags = 0;
270       sigaction (SIGSEGV, &sa, 0);
271 #endif
272     }
273
274   /* Run the startup function, if any. */
275   if (startup_fn)
276     startup_fn (argc, argv);
277
278   /* Start the listener thread. */
279   (void) new_listener (sock, processor_fn, 0);
280
281   /* Run the reactor. */
282   while (pseudothread_count_threads () > 0)
283     reactor_invoke ();
284 }
285
286 #ifdef CAN_CATCH_SIGSEGV
287 static void
288 catch_sigsegv (int sig)
289 {
290 #define MAX_ADDRS 50
291   const char msg[] = "** Segmentation fault **\n\nStack trace:\n\n";
292   void *addr[MAX_ADDRS];
293   int n;
294
295   write (2, msg, sizeof (msg) - 1);
296
297   /* Write the stack trace to stderr. */
298   n = backtrace (addr, MAX_ADDRS);
299   backtrace_symbols_fd (addr, n, 2);
300
301   /* Really abort. */
302   abort ();
303 #undef MAX_ADDRS
304 }
305 #endif
306
307 void
308 pthr_server_default_port (int _default_port)
309 {
310   default_port = _default_port;
311 }
312
313 void
314 pthr_server_port_option_name (char _port_option_name)
315 {
316   port_option_name = _port_option_name;
317 }
318
319 void
320 pthr_server_default_address (in_addr_t _default_address)
321 {
322   default_address = _default_address;
323 }
324
325 void
326 pthr_server_address_option_name (char _address_option_name)
327 {
328   address_option_name = _address_option_name;
329 }
330
331 void
332 pthr_server_disable_syslog (void)
333 {
334   disable_syslog = 1;
335 }
336
337 void
338 pthr_server_package_name (const char *_package_name)
339 {
340   package_name = _package_name;
341 }
342
343 void
344 pthr_server_disable_fork (void)
345 {
346   disable_fork = 1;
347 }
348
349 void
350 pthr_server_disable_chdir (void)
351 {
352   disable_chdir = 1;
353 }
354
355 void
356 pthr_server_disable_close (void)
357 {
358   disable_close = 1;
359 }
360
361 void
362 pthr_server_chroot (const char *_root)
363 {
364   root = _root;
365 }
366
367 void
368 pthr_server_username (const char *_username)
369 {
370   username = _username;
371 }
372
373 void
374 pthr_server_stderr_file (const char *_pathname)
375 {
376   stderr_file = _pathname;
377 }
378
379 void
380 pthr_server_startup_fn (void (*_startup_fn) (int argc, char *argv[]))
381 {
382   startup_fn = _startup_fn;
383 }
384
385 void
386 pthr_server_enable_stack_trace_on_segv (void)
387 {
388   enable_stack_trace_on_segv = 1;
389 }