Put the site name and room name in the top header bar
[citadel] / webcit-ng / webserver.c
1 //
2 // webserver.c
3 //
4 // This module handles the task of setting up a listening socket, accepting
5 // connections, and dispatching active connections onto a pool of worker
6 // threads.
7 //
8 // Copyright (c) 1996-2018 by the citadel.org team
9 //
10 // This program is open source software.  It runs great on the
11 // Linux operating system (and probably elsewhere).  You can use,
12 // copy, and run it under the terms of the GNU General Public
13 // License version 3.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19
20 #include "webcit.h"                     // All other headers are included from this header.
21
22 int num_threads_executing = 1;          // Number of worker threads currently bound to a connected client
23 int num_threads_existing = 1;           // Total number of worker threads that exist right now
24 int is_https = 0;                       // Set to nonzero if we are running as an HTTPS server today.
25 static void *original_brk = NULL;       // Remember the original program break so we can test for leaks
26
27
28 // Spawn an additional worker thread into the pool.
29 void spawn_another_worker_thread(int *pointer_to_master_socket) {
30         pthread_t th;           // Thread descriptor
31         pthread_attr_t attr;    // Thread attributes
32
33         // set attributes for the new thread
34         pthread_attr_init(&attr);
35         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
36         pthread_attr_setstacksize(&attr, 1048576);      // Large stacks to prevent MIME parser crash on FreeBSD
37
38         // now create the thread
39         if (pthread_create(&th, &attr, (void *(*)(void *)) worker_entry, (void *) pointer_to_master_socket) != 0) {
40                 syslog(LOG_WARNING, "Can't create thread: %s", strerror(errno));
41         }
42         else {
43                 ++num_threads_existing;
44                 ++num_threads_executing;
45         }
46
47         // free up the attributes
48         pthread_attr_destroy(&attr);
49 }
50
51
52 // Entry point for worker threads
53 void worker_entry(int *pointer_to_master_socket) {
54         int master_socket = *pointer_to_master_socket;
55         int i = 0;
56         int fail_this_transaction = 0;
57         struct client_handle ch;
58
59         while (1) {
60                 // Each worker thread blocks on accept() while waiting for something to do.
61                 memset(&ch, 0, sizeof ch);
62                 ch.sock = -1;
63                 errno = EAGAIN;
64                 do {
65                         --num_threads_executing;
66                         syslog(LOG_DEBUG, "Additional memory allocated since startup: %d bytes", (int) (sbrk(0) - original_brk));
67                         syslog(LOG_DEBUG, "Thread 0x%x calling accept() on master socket %d", (unsigned int) pthread_self(),
68                                master_socket);
69                         ch.sock = accept(master_socket, NULL, 0);
70                         if (ch.sock < 0) {
71                                 syslog(LOG_DEBUG, "accept() : %s", strerror(errno));
72                         }
73                         ++num_threads_executing;
74                         syslog(LOG_DEBUG, "socket %d is awake , threads executing: %d , threads total: %d", ch.sock,
75                                num_threads_executing, num_threads_existing);
76                 } while ((master_socket > 0) && (ch.sock < 0));
77
78                 // If all threads are executing, spawn more, up to the maximum
79                 if ((num_threads_executing >= num_threads_existing) && (num_threads_existing <= MAX_WORKER_THREADS)) {
80                         spawn_another_worker_thread(pointer_to_master_socket);
81                 }
82
83                 // We have a client.  Do some work.
84
85                 // Set the SO_REUSEADDR socket option
86                 i = 1;
87                 setsockopt(ch.sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
88
89                 // If we are an HTTPS server, go crypto now.
90                 if (is_https) {
91                         starttls(&ch);
92                         if (ch.ssl_handle == NULL) {
93                                 fail_this_transaction = 1;
94                         }
95                 }
96                 else {
97                         int fdflags;
98                         fdflags = fcntl(ch.sock, F_GETFL);
99                         if (fdflags < 0) {
100                                 syslog(LOG_WARNING, "unable to get server socket flags! %s", strerror(errno));
101                         }
102                 }
103
104                 // Perform an HTTP transaction...
105                 if (fail_this_transaction == 0) {
106                         perform_one_http_transaction(&ch);
107                 }
108
109                 // Shut down SSL/TLS if required...
110                 if (is_https) {
111                         endtls(&ch);
112                 }
113                 // ...and close the socket.
114                 //syslog(LOG_DEBUG, "Closing socket %d...", ch.sock);
115                 //lingering_close(ch.sock);
116                 close(ch.sock);
117                 syslog(LOG_DEBUG, "Closed socket %d.", ch.sock);
118         }
119 }
120
121
122 // Start up a TCP HTTP[S] server on the requested port
123 int webserver(char *webserver_interface, int webserver_port, int webserver_protocol) {
124         int master_socket = (-1);
125         original_brk = sbrk(0);
126
127         switch (webserver_protocol) {
128         case WEBSERVER_HTTP:
129                 syslog(LOG_DEBUG, "Starting HTTP server on %s:%d", webserver_interface, webserver_port);
130                 master_socket = webcit_tcp_server(webserver_interface, webserver_port, 10);
131                 break;
132         case WEBSERVER_HTTPS:
133                 syslog(LOG_DEBUG, "Starting HTTPS server on %s:%d", webserver_interface, webserver_port);
134                 master_socket = webcit_tcp_server(webserver_interface, webserver_port, 10);
135                 init_ssl();
136                 is_https = 1;
137                 break;
138         default:
139                 syslog(LOG_ERR, "unknown protocol");
140                 ;;
141         }
142
143         if (master_socket < 1) {
144                 syslog(LOG_ERR, "Unable to bind the web server listening socket");
145                 return (1);
146         }
147
148         syslog(LOG_INFO, "Listening on socket %d", master_socket);
149         signal(SIGPIPE, SIG_IGN);
150
151         worker_entry(&master_socket);   // this thread becomes a worker
152         return (0);
153 }