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 }