hermes

HTTP GET/HEAD-only web server for static content.
git clone https://git.francescosaccone.com/hermes
Log | Files | Refs | README | LICENSE

main.c (7481B)


      1 #include <grp.h>
      2 #include <pwd.h>
      3 #include <signal.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <sys/stat.h>
      8 #include <unistd.h>
      9 
     10 #include "file.h"
     11 #include "http.h"
     12 #include "socket.h"
     13 #include "utils.h"
     14 
     15 #define VERSION "0.1.0"
     16 
     17 #define REQUEST_BUFFER_MAX_LENGTH 104857600 * sizeof(char) /* 100 MiB */
     18 
     19 #define DIRECTORY_MAX_LENGTH 1024
     20 #define DIRECTORY_INDEX_MAX_LENGTH 32
     21 #define USER_NAME_MAX_LENGTH 32
     22 #define GROUP_NAME_MAX_LENGTH 32
     23 
     24 void
     25 print_usage(char *program_name) {
     26 	print_error("usage: %s -d directory [-p port] [-i file] [-u user] "
     27 	            "[-g group]",
     28 	            program_name);
     29 }
     30 
     31 void
     32 print_program_info() {
     33 	printf("Hermes v%s (c) Francesco Saccone\n", VERSION);
     34 }
     35 
     36 void handle_signal(int signal) {
     37 	printf("\nTerminating Hermes...\n");
     38 
     39 	switch (signal) {
     40 	case SIGINT:
     41 	case SIGTERM:
     42 		exit(0);
     43 		break;
     44 	default:
     45 		exit(1);
     46 	}
     47 }
     48 
     49 int
     50 main(int argc, char *argv[]) {
     51 	char *program_name = argv[0],
     52 	     directory[DIRECTORY_MAX_LENGTH],
     53 	     user_name[USER_NAME_MAX_LENGTH] = "nobody",
     54 	     group_name[GROUP_NAME_MAX_LENGTH] = "nogroup",
     55 	     directory_index[DIRECTORY_INDEX_MAX_LENGTH] = "index.html";
     56 	int i, port = 80,
     57 	    server_socket_fd,
     58 	    is_directory_set = 0;
     59 	struct passwd *user;
     60 	struct group *group;
     61 
     62 	signal(SIGINT, handle_signal);
     63 	signal(SIGTERM, handle_signal);
     64 	signal(SIGSEGV, handle_signal);
     65 	signal(SIGABRT, handle_signal);
     66 	signal(SIGFPE, handle_signal);
     67 
     68 	for (i = 1; i < argc; i++) {
     69 		char *argument = argv[i];
     70 
     71 		if (argument[0] != '-' ||
     72 		    argument[1] == '\0' ||
     73 		    argument[2] != '\0' ||
     74 		    i + 1 >= argc /* Argument value exists */) {
     75 			print_usage(program_name);
     76 			return 1;
     77 		}
     78 
     79 		switch (argument[1]) {
     80 		case 'd':
     81 			if (strlen(argv[i + 1]) >= DIRECTORY_MAX_LENGTH) {
     82 				print_error("error: directory length must be "
     83 				            "less than %u characters",
     84 				            DIRECTORY_MAX_LENGTH);
     85 			}
     86 			snprintf(directory,
     87 			         sizeof(directory),
     88 			         "%s",
     89 			         argv[i + 1]);
     90 			directory[sizeof(directory) - 1] = '\0';
     91 			is_directory_set = 1;
     92 			i++;
     93 			break;
     94 		case 'p':
     95 			port = atoi(argv[i + 1]);
     96 			if (port < 1 || port > 65535) {
     97 				print_error("error: port must be between 1 "
     98 				            "and 65535.");
     99 				return 1;
    100 			};
    101 			i++;
    102 			break;
    103 		case 'i':
    104 			if (strlen(argv[i + 1])
    105 			    >= DIRECTORY_INDEX_MAX_LENGTH) {
    106 				print_error("error: directory index must be "
    107 				            "less than %u characters",
    108 				            DIRECTORY_INDEX_MAX_LENGTH);
    109 			}
    110 			snprintf(directory_index,
    111 			         sizeof(directory_index),
    112 			         "%s",
    113 			         argv[i + 1]);
    114 			directory_index[sizeof(directory_index) - 1] = '\0';
    115 			i++;
    116 			break;
    117 		case 'u':
    118 			if (strlen(argv[i + 1]) >= USER_NAME_MAX_LENGTH) {
    119 				print_error("error: the user name must be "
    120 				            "less than %u characters",
    121 				            USER_NAME_MAX_LENGTH);
    122 			}
    123 			strncpy(user_name,
    124 			        argv[i + 1],
    125 			        sizeof(user_name) - 1);
    126 			user_name[sizeof(user_name) - 1] = '\0';
    127 			i++;
    128 			break;
    129 		case 'g':
    130 			if (strlen(argv[i + 1]) >= GROUP_NAME_MAX_LENGTH) {
    131 				print_error("error: the group name must be "
    132 				            "less than %u characters",
    133 				            GROUP_NAME_MAX_LENGTH);
    134 			}
    135 			strncpy(group_name,
    136 			        argv[i + 1],
    137 			        sizeof(group_name) - 1);
    138 			group_name[sizeof(group_name) - 1] = '\0';
    139 			i++;
    140 			break;
    141 		default:
    142 			print_usage(program_name);
    143 			return 1;
    144 		}
    145 	}
    146 
    147 	if (!is_directory_set) {
    148 		print_usage(program_name);
    149 		return 1;
    150 	}
    151 
    152 	user = getpwnam(user_name);
    153 	group = getgrnam(group_name);
    154 
    155 	if (user == NULL) {
    156 		print_error("error: user %s does not exist.", user_name);
    157 		return 1;
    158 	}
    159 
    160 	if (group == NULL) {
    161 		print_error("error: group %s does not exist.", group_name);
    162 		return 1;
    163 	}
    164 
    165 	server_socket_fd = create_socket(port);
    166 
    167 	if (access(directory, R_OK) == -1) {
    168 		print_error("error: directory is nonexistent or "
    169 		            "inaccessible");
    170 		return 1;
    171 	}
    172 
    173 	if (chroot(directory) == -1) {
    174 		print_error("error: could not chroot to directory");
    175 		return 1;
    176 	}
    177 
    178 	if (chdir("/") == -1) {
    179 		print_error("error: could not change directory after "
    180 		            "chrooting");
    181 		return 1;
    182 	}
    183 
    184 	if (setgid(group->gr_gid) == -1) {
    185 		print_error("error: could not drop privileges to given "
    186 		            "group");
    187 		return 1;
    188 	}
    189 
    190 	if (setuid(user->pw_uid) == -1) {
    191 		print_error("error: could not drop privileges to given "
    192 		            "user");
    193 		return 1;
    194 	}
    195 
    196 	print_program_info();
    197 
    198 	while (1) {
    199 		int client_socket_fd,
    200 		    file_readable;
    201 		char *request_buffer = malloc(REQUEST_BUFFER_MAX_LENGTH),
    202 		     *normalised_path,
    203 		     *file_extension,
    204 		     *mime_type;
    205 		struct http_request *request;
    206 		struct http_response response;
    207 
    208 		client_socket_fd = accept_client(server_socket_fd);
    209 
    210 		if (client_socket_fd == -1) {
    211 			free(request_buffer);
    212 			close_socket(client_socket_fd);
    213 			continue;
    214 		}
    215 
    216 		if (read_client_request(client_socket_fd,
    217 		                        request_buffer,
    218 		                        REQUEST_BUFFER_MAX_LENGTH) == -1) {
    219 			free(request_buffer);
    220 			close_socket(client_socket_fd);
    221 			continue;
    222 		}
    223 
    224 		request = parse_http_request(request_buffer);
    225 		normalised_path = get_normalised_path(request->path);
    226 		file_extension = get_file_extension(normalised_path);
    227 		mime_type = get_mime_type_from_extension(file_extension);
    228 		file_readable = is_file_readable(normalised_path);
    229 
    230 		if (!file_readable) {
    231 			char *index_path = malloc(strlen(normalised_path) +
    232 			                          sizeof(char) + /* "/" */
    233 			                          strlen(directory_index) +
    234 			                          1),
    235 			     *normalised_index_path,
    236 			     *index_file_extension,
    237 			     *index_mime_type;
    238 			int index_file_readable;
    239 
    240 			index_path[0] = '\0';
    241 			strcat(index_path, normalised_path);
    242 			if (strcmp(normalised_path, "/") != 0) {
    243 				strcat(index_path, "/");
    244 			}
    245 			strcat(index_path, directory_index);
    246 
    247 			normalised_index_path = get_normalised_path(
    248 			                        index_path);
    249 			index_file_extension = get_file_extension(
    250 			                       normalised_index_path);
    251 			index_mime_type = get_mime_type_from_extension(
    252 			                  index_file_extension);
    253 			index_file_readable = is_file_readable(
    254 			                      normalised_index_path);
    255 
    256 			free(index_path);
    257 
    258 			if (!index_file_readable) {
    259 				response.status = NOT_FOUND;
    260 				response.content_type = "text/plain";
    261 				response.body = "404 NOT FOUND\n";
    262 			} else {
    263 				struct file_content file
    264 				 = get_file_content(normalised_index_path);
    265 
    266 				response.status = OK;
    267 				response.content_type = index_mime_type;
    268 				response.body = file.content;
    269 			}
    270 		} else {
    271 			struct file_content file = get_file_content(
    272 			                           normalised_path);
    273 
    274 			response.status = OK;
    275 			response.content_type = mime_type;
    276 			response.body = file.content;
    277 		}
    278 
    279 		switch (request->method) {
    280 		case GET:
    281 			send_to_socket(client_socket_fd,
    282 			               compose_http_response_full(response));
    283 			break;
    284 		case HEAD:
    285 			send_to_socket(client_socket_fd,
    286 			               compose_http_response_head(response));
    287 			break;
    288 		default:
    289 			response.status = METHOD_NOT_ALLOWED;
    290 			response.content_type = "text/plain";
    291 			response.body = "405 METHOD NOT ALLOWED\n";
    292 
    293 			send_to_socket(client_socket_fd,
    294 			               compose_http_response_full(response));
    295 		}
    296 
    297 		free(request_buffer);
    298 		close_socket(client_socket_fd);
    299 	}
    300 
    301 	close_socket(server_socket_fd);
    302 
    303 	return 0;
    304 }