fiche.c - fiche - A pastebin adjusted for gopher use
(HTM) git clone git://vernunftzentrum.de/fiche.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) LICENSE
---
fiche.c (19680B)
---
1 /*
2 Fiche - Command line pastebin for sharing terminal output.
3
4 -------------------------------------------------------------------------------
5
6 License: MIT (http://www.opensource.org/licenses/mit-license.php)
7 Repository: https://github.com/solusipse/fiche/
8 Live example: http://termbin.com
9
10 -------------------------------------------------------------------------------
11
12 usage: fiche [-DepbsdolBuw].
13 [-D] [-e] [-d domain] [-p port] [-s slug size]
14 [-o output directory] [-B buffer size] [-u user name]
15 [-l log file] [-b banlist] [-w whitelist]
16 -D option is for daemonizing fiche
17 -e option is for using an extended character set for the URL
18
19 Compile with Makefile or manually with -O2 and -pthread flags.
20
21 To install use `make install` command.
22
23 Use netcat to push text - example:
24 $ cat fiche.c | nc localhost 9999
25
26 -------------------------------------------------------------------------------
27 */
28
29 #include "fiche.h"
30
31 #include <stdio.h>
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include <pwd.h>
37 #include <time.h>
38 #include <unistd.h>
39 #include <pthread.h>
40
41 #include <fcntl.h>
42 #include <netdb.h>
43 #include <sys/time.h>
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <arpa/inet.h>
47 #include <sys/socket.h>
48 #include <netinet/in.h>
49 #include <netinet/in.h>
50
51
52 /******************************************************************************
53 * Various declarations
54 */
55 const char *Fiche_Symbols = "abcdefghijklmnopqrstuvwxyz0123456789";
56
57 /* File handle for the log file */
58 static FILE *logfile_handle = NULL;
59
60
61 /******************************************************************************
62 * Inner structs
63 */
64
65 struct fiche_connection {
66 int socket;
67 struct sockaddr_in address;
68
69 Fiche_Settings *settings;
70 };
71
72
73 /******************************************************************************
74 * Static function declarations
75 */
76
77 // Settings-related
78
79 /**
80 * @brief Sets domain name
81 * @warning settings.domain has to be freed after using this function!
82 */
83 static int set_domain_name(Fiche_Settings *settings);
84
85 /**
86 * @brief Changes user running this program to requested one
87 * @warning Application has to be run as root to use this function
88 */
89 static int perform_user_change(const Fiche_Settings *settings);
90
91
92 // Server-related
93
94 /**
95 * @brief Starts server with settings provided in Fiche_Settings struct
96 */
97 static int start_server(Fiche_Settings *settings);
98
99 /**
100 * @brief Dispatches incoming connections by spawning threads
101 */
102 static void dispatch_connection(int socket, Fiche_Settings *settings);
103
104 /**
105 * @brief Handles connections
106 * @remarks Is being run by dispatch_connection in separate threads
107 * @arg args Struct fiche_connection containing connection details
108 */
109 static void *handle_connection(void *args);
110
111 // Server-related utils
112
113
114 /**
115 * @brief Generates a slug that will be used for paste creation
116 * @warning output has to be freed after using!
117 *
118 * @arg output pointer to output string containing full path to directory
119 * @arg length default or user-requested length of a slug
120 * @arg extra_length additional length that was added to speed-up the
121 * generation process
122 *
123 * This function is used in connection with create_directory function
124 * It generates strings that are used to create a directory for
125 * user-provided data. If directory already exists, we ask this function
126 * to generate another slug with increased size.
127 */
128 static void generate_slug(char **output, uint8_t length, uint8_t extra_length);
129
130
131 /**
132 * @brief Creates a directory at requested path using requested slug
133 * @returns 0 if succeded, 1 if failed or dir already existed
134 *
135 * @arg output_dir root directory for all pastes
136 * @arg slug directory name for a particular paste
137 */
138 static int create_directory(char *output_dir, char *slug);
139
140
141 /**
142 * @brief Saves data to file at requested path
143 *
144 * @arg data Buffer with data received from the user
145 * @arg path Path at which file containing data from the buffer will be created
146 */
147 static int save_to_file(const Fiche_Settings *s, uint8_t *data, char *slug);
148
149
150 // Logging-related
151
152 /**
153 * @brief Displays error messages
154 */
155 static void print_error(const char *format, ...);
156
157
158 /**
159 * @brief Displays status messages
160 */
161 static void print_status(const char *format, ...);
162
163
164 /**
165 * @brief Displays horizontal line
166 */
167 static void print_separator();
168
169
170 /**
171 * @brief Saves connection entry to the logfile
172 */
173 static void log_entry(const Fiche_Settings *s, const char *ip,
174 const char *hostname, const char *slug);
175
176
177 /**
178 * @brief Returns string containing current date
179 * @warning Output has to be freed!
180 */
181 static void get_date(char *buf);
182
183
184 /******************************************************************************
185 * Public fiche functions
186 */
187
188 void fiche_init(Fiche_Settings *settings) {
189
190 // Initialize everything to default values
191 // or to NULL in case of pointers
192
193 struct Fiche_Settings def = {
194 // domain
195 "example.com",
196 // output dir
197 "code",
198 // port
199 9999,
200 // slug length
201 4,
202 // protocol prefix
203 "http",
204 // buffer length
205 32768,
206 // user name
207 NULL,
208 // path to log file
209 NULL,
210 // path to banlist
211 NULL,
212 // path to whitelist
213 NULL
214 };
215
216 // Copy default settings to provided instance
217 *settings = def;
218 }
219
220 int fiche_run(Fiche_Settings settings) {
221
222 // Check if log file is writable (if set)
223 if ( settings.log_file_path ) {
224
225 // Create log file if it doesn't exist
226 FILE *f = fopen(settings.log_file_path, "a+");
227 if (!f){
228 print_error("Unable to create log file!");
229 return -1;
230 }
231
232 // Then check if it's accessible
233 if ( access(settings.log_file_path, W_OK) != 0 ) {
234 print_error("Log file not writable!");
235 fclose(f);
236 return -1;
237 }
238
239 logfile_handle = f;
240
241 }
242
243 // Display welcome message
244 {
245 char date[64];
246 get_date(date);
247 print_status("Starting fiche on %s...", date);
248 }
249
250 // Try to set requested user
251 if ( perform_user_change(&settings) != 0) {
252 print_error("Was not able to change the user!");
253 return -1;
254 }
255
256 // Check if output directory is writable
257 // - First we try to create it
258 {
259 mkdir(
260 settings.output_dir_path,
261 S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP
262 );
263 // - Then we check if we can write there
264 if ( access(settings.output_dir_path, W_OK) != 0 ) {
265 print_error("Output directory not writable!");
266 return -1;
267 }
268 }
269
270 // Try to set domain name
271 if ( set_domain_name(&settings) != 0 ) {
272 print_error("Was not able to set domain name!");
273 if (logfile_handle) fclose(logfile_handle);
274 return -1;
275 }
276
277 pid_t pid = fork();
278 if (pid == -1){
279 char *err = strerror(0);
280 print_error("Unable to fork into background: %s", err);
281 if (logfile_handle) fclose(logfile_handle);
282 return -1;
283 }
284 if (pid > 0){
285 //parent
286 if (logfile_handle) fclose(logfile_handle);
287 return 0;
288 }
289
290 if (setsid() == -1){
291 char *err = strerror(0);
292 print_error("Creating new session id: %s", err);
293 if (logfile_handle) fclose(logfile_handle);
294 return -1;
295 }
296
297 // We are detached so close those to avoid noise
298 fclose(stdin);
299 fclose(stdout);
300 fclose(stderr);
301
302 // Main loop in this method
303 start_server(&settings);
304
305 // Perform final cleanup
306
307 // This is allways allocated on the heap
308 free(settings.domain);
309
310 if (logfile_handle) fclose(logfile_handle);
311
312 return 0;
313
314 }
315
316
317 /******************************************************************************
318 * Static functions below
319 */
320
321 static void print_error(const char *format, ...) {
322 va_list args;
323 FILE *fd = logfile_handle ? logfile_handle : stderr;
324
325 va_start(args, format);
326
327 fprintf(fd, "[Fiche][ERROR] ");
328 vfprintf(fd, format, args);
329 fprintf(fd, "\n");
330 fflush(fd);
331 va_end(args);
332 }
333
334
335 static void print_status(const char *format, ...) {
336 va_list args;
337 FILE *fd = logfile_handle ? logfile_handle : stderr;
338
339 va_start(args, format);
340
341 fprintf(fd, "[Fiche][STATUS] ");
342 vfprintf(fd, format, args);
343 fprintf(fd, "\n");
344 fflush(fd);
345 va_end(args);
346 }
347
348
349 static void print_separator() {
350 FILE *fd = logfile_handle ? logfile_handle : stderr;
351 fprintf(fd, "============================================================\n");
352 fflush(fd);
353 }
354
355
356 static void log_entry(const Fiche_Settings *s, const char *ip,
357 const char *hostname, const char *slug)
358 {
359 // Logging to file not enabled, finish here
360 if (!s->log_file_path) {
361 return;
362 }
363
364 if (!logfile_handle) {
365 print_status("Was not able to save entry to the log!");
366 return;
367 }
368
369 char date[64];
370 get_date(date);
371
372 // Write entry to file
373 fprintf(logfile_handle, "%s -- %s -- %s (%s)\n", slug, date, ip, hostname);
374 }
375
376
377 static void get_date(char *buf) {
378 struct tm curtime;
379 time_t ltime;
380
381 ltime=time(<ime);
382 localtime_r(<ime, &curtime);
383
384 // Save data to provided buffer
385 if (asctime_r(&curtime, buf) == 0) {
386 // Couldn't get date, setting first byte of the
387 // buffer to zero so it won't be displayed
388 buf[0] = 0;
389 return;
390 }
391
392 // Remove newline char
393 buf[strlen(buf)-1] = 0;
394 }
395
396
397 static int set_domain_name(Fiche_Settings *settings) {
398
399 const int len = strlen(settings->domain) + strlen(settings->prefix) + 4;
400
401 char *b = malloc(len);
402 if (!b) {
403 return -1;
404 }
405
406 strlcpy(b, settings->prefix, len);
407 strlcat(b, "://", len);
408 strlcat(b, settings->domain, len);
409
410 settings->domain = b;
411
412 print_status("Domain set to: %s.", settings->domain);
413
414 return 0;
415 }
416
417
418 static int perform_user_change(const Fiche_Settings *settings) {
419
420 // User change wasn't requested, finish here
421 if (settings->user_name == NULL) {
422 return 0;
423 }
424
425 // Check if root, if not - finish here
426 if (getuid() != 0) {
427 print_error("Run as root if you want to change the user!");
428 return -1;
429 }
430
431 // Get user details
432 const struct passwd *userdata = getpwnam(settings->user_name);
433
434 const int uid = userdata->pw_uid;
435 const int gid = userdata->pw_gid;
436
437 if (uid == -1 || gid == -1) {
438 print_error("Could find requested user: %s!", settings->user_name);
439 return -1;
440 }
441
442 if (setgid(gid) != 0) {
443 print_error("Couldn't switch to requested user: %s!", settings->user_name);
444 }
445
446 if (setuid(uid) != 0) {
447 print_error("Couldn't switch to requested user: %s!", settings->user_name);
448 }
449
450 print_status("User changed to: %s.", settings->user_name);
451
452 return 0;
453 }
454
455
456 static int start_server(Fiche_Settings *settings) {
457
458 // Perform socket creation
459 int s = socket(AF_INET, SOCK_STREAM, 0);
460 if (s < 0) {
461 print_error("Couldn't create a socket!");
462 return -1;
463 }
464
465 // Set socket settings
466 if ( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 } , sizeof(int)) != 0 ) {
467 print_error("Couldn't prepare the socket!");
468 return -1;
469 }
470
471 // Prepare address and port handler
472 struct sockaddr_in address;
473 address.sin_family = AF_INET;
474 address.sin_addr.s_addr = INADDR_ANY;
475 address.sin_port = htons(settings->port);
476
477 // Bind to port
478 if ( bind(s, (struct sockaddr *) &address, sizeof(address)) != 0) {
479 print_error("Couldn't bind to the port: %d!", settings->port);
480 return -1;
481 }
482
483 // Start listening
484 if ( listen(s, 128) != 0 ) {
485 print_error("Couldn't start listening on the socket!");
486 return -1;
487 }
488
489 print_status("Server started listening on port: %d.", settings->port);
490 print_separator();
491
492 // Run dispatching loop
493 while (1) {
494 dispatch_connection(s, settings);
495 }
496
497 // Give some time for all threads to finish
498 // NOTE: this code is reached only in testing environment
499 // There is currently no way to kill the main thread from any thread
500 // Something like this can be done for testing purpouses:
501 // int i = 0;
502 // while (i < 3) {
503 // dispatch_connection(s, settings);
504 // i++;
505 // }
506
507 sleep(5);
508
509 return 0;
510 }
511
512
513 static void dispatch_connection(int socket, Fiche_Settings *settings) {
514
515 // Create address structs for this socket
516 struct sockaddr_in address;
517 socklen_t addlen = sizeof(address);
518
519 // Accept a connection and get a new socket id
520 const int s = accept(socket, (struct sockaddr *) &address, &addlen);
521 if (s < 0 ) {
522 print_error("Error on accepting connection!");
523 return;
524 }
525
526 // Set timeout for accepted socket
527 const struct timeval timeout = { 5, 0 };
528
529 if ( setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ) {
530 print_error("Couldn't set a timeout!");
531 }
532
533 if ( setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0 ) {
534 print_error("Couldn't set a timeout!");
535 }
536
537 // Create an argument for the thread function
538 struct fiche_connection *c = malloc(sizeof(*c));
539 if (!c) {
540 print_error("Couldn't allocate memory!");
541 return;
542 }
543 c->socket = s;
544 c->address = address;
545 c->settings = settings;
546
547 // Spawn a new thread to handle this connection
548 pthread_t id;
549
550 if ( pthread_create(&id, NULL, &handle_connection, c) != 0 ) {
551 print_error("Couldn't spawn a thread!");
552 return;
553 }
554
555 // Detach thread if created succesfully
556 // TODO: consider using pthread_tryjoin_np
557 pthread_detach(id);
558
559 }
560
561
562 static void *handle_connection(void *args) {
563
564 // Cast args to it's previous type
565 struct fiche_connection *c = (struct fiche_connection *) args;
566
567 // Get client's IP
568 const char *ip = inet_ntoa(c->address.sin_addr);
569
570 // Get client's hostname
571 char hostname[1024];
572
573 if (getnameinfo((struct sockaddr *)&c->address, sizeof(c->address),
574 hostname, sizeof(hostname), NULL, 0, 0) != 0 ) {
575
576 // Couldn't resolve a hostname
577 strlcpy(hostname, "n/a", 1024);
578 }
579
580 // Print status on this connection
581 {
582 char date[64];
583 get_date(date);
584 print_status("%s", date);
585
586 print_status("Incoming connection from: %s (%s).", ip, hostname);
587 }
588
589 // Create a buffer
590 uint8_t buffer[c->settings->buffer_len];
591 memset(buffer, 0, c->settings->buffer_len);
592
593 const int r = recv(c->socket, buffer, sizeof(buffer), MSG_WAITALL);
594 if (r <= 0) {
595 print_error("No data received from the client!");
596 print_separator();
597
598 // Close the socket
599 close(c->socket);
600
601 // Cleanup
602 free(c);
603 pthread_exit(NULL);
604
605 return 0;
606 }
607
608 // - Check if request was performed with a known protocol
609 // TODO
610
611 // - Check if on whitelist
612 // TODO
613
614 // - Check if on banlist
615 // TODO
616
617 // Generate slug and use it to create an url
618 char *slug;
619 uint8_t extra = 0;
620
621 do {
622
623 // Generate slugs until it's possible to create a directory
624 // with generated slug on disk
625 generate_slug(&slug, c->settings->slug_len, extra);
626
627 // Something went wrong in slug generation, break here
628 if (!slug) {
629 break;
630 }
631
632 // Increment counter for additional letters needed
633 ++extra;
634
635 // If i was incremented more than 128 times, something
636 // for sure went wrong. We are closing connection and
637 // killing this thread in such case
638 if (extra > 128) {
639 print_error("Couldn't generate a valid slug!");
640 print_separator();
641
642 // Cleanup
643 free(c);
644 free(slug);
645 close(c->socket);
646 pthread_exit(NULL);
647 return NULL;
648 }
649
650 }
651 while(create_directory(c->settings->output_dir_path, slug) != 0);
652
653
654 // Slug generation failed, we have to finish here
655 if (!slug) {
656 print_error("Couldn't generate a slug!");
657 print_separator();
658
659 close(c->socket);
660
661 // Cleanup
662 free(c);
663 pthread_exit(NULL);
664 return NULL;
665 }
666
667
668 // Save to file failed, we have to finish here
669 if ( save_to_file(c->settings, buffer, slug) != 0 ) {
670 print_error("Couldn't save a file!");
671 print_separator();
672
673 close(c->socket);
674
675 // Cleanup
676 free(c);
677 free(slug);
678 pthread_exit(NULL);
679 return NULL;
680 }
681
682 // Write a response to the user
683 {
684 // Create an url (additional byte for slash and one for new line)
685 const size_t len = strlen(c->settings->domain) + strlen(slug) + 3;
686
687 char url[len];
688 snprintf(url, len, "%s%s%s%s", c->settings->domain, "/", slug, "\n");
689
690 // Send the response
691 write(c->socket, url, len);
692 }
693
694 print_status("Received %d bytes, saved to: %s.", r, slug);
695 print_separator();
696
697 // Log connection
698 // TODO: log unsuccessful and rejected connections
699 log_entry(c->settings, ip, hostname, slug);
700
701 // Close the connection
702 close(c->socket);
703
704 // Perform cleanup of values used in this thread
705 free(slug);
706 free(c);
707
708 pthread_exit(NULL);
709
710 return NULL;
711 }
712
713
714 static void generate_slug(char **output, uint8_t length, uint8_t extra_length) {
715
716 // Realloc buffer for slug when we want it to be bigger
717 // This happens in case when directory with this name already
718 // exists. To save time, we don't generate new slugs until
719 // we spot an available one. We add another letter instead.
720
721 if (extra_length > 0) {
722 free(*output);
723 }
724
725 // Create a buffer for slug with extra_length if any
726 *output = calloc(length + 1 + extra_length, sizeof(char));
727
728 if (*output == NULL) {
729 return;
730 }
731
732 // Take n-th symbol from symbol table and use it for slug generation
733 for (int i = 0; i < length + extra_length; i++) {
734 int n = arc4random() % strlen(Fiche_Symbols);
735 *(output[0] + sizeof(char) * i) = Fiche_Symbols[n];
736 }
737
738 }
739
740
741 static int create_directory(char *output_dir, char *slug) {
742 if (!slug) {
743 return -1;
744 }
745
746 // Additional byte is for the slash
747 size_t len = strlen(output_dir) + strlen(slug) + 2;
748
749 // Generate a path
750 char *path = malloc(len);
751 if (!path) {
752 return -1;
753 }
754 snprintf(path, len, "%s%s%s", output_dir, "/", slug);
755
756 // Create output directory, just in case
757 mkdir(output_dir, S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP);
758
759 // Create slug directory
760 const int r = mkdir(
761 path,
762 S_IRWXU | S_IRGRP | S_IROTH | S_IXOTH | S_IXGRP
763 );
764
765 free(path);
766
767 return r;
768 }
769
770
771 static int save_to_file(const Fiche_Settings *s, uint8_t *data, char *slug) {
772 char *file_name = "paste.txt";
773
774 // Additional 2 bytes are for 2 slashes
775 size_t len = strlen(s->output_dir_path) + strlen(slug) + strlen(file_name) + 3;
776
777 // Generate a path
778 char *path = malloc(len);
779 if (!path) {
780 return -1;
781 }
782
783 snprintf(path, len, "%s%s%s%s%s", s->output_dir_path, "/", slug, "/", file_name);
784
785 // Attempt file saving
786 FILE *f = fopen(path, "w");
787 if (!f) {
788 free(path);
789 return -1;
790 }
791
792 // Null-terminate buffer if not null terminated already
793 data[s->buffer_len - 1] = 0;
794
795 if ( fprintf(f, "%s", data) < 0 ) {
796 fclose(f);
797 free(path);
798 return -1;
799 }
800
801 fclose(f);
802 free(path);
803
804 return 0;
805 }