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