Introduction
Introduction Statistics Contact Development Disclaimer Help
fiche.c - fiche - A pastebin adjusted for gopher use
git clone git://vernunftzentrum.de/fiche.git
Log
Files
Refs
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_l…
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 …
146 */
147 static int save_to_file(const Fiche_Settings *s, uint8_t *data, char *sl…
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, "=======================================================…
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, hos…
374 }
375
376
377 static void get_date(char *buf) {
378 struct tm curtime;
379 time_t ltime;
380
381 ltime=time(&ltime);
382 localtime_r(&ltime, &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) …
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_nam…
439 return -1;
440 }
441
442 if (setgid(gid) != 0) {
443 print_error("Couldn't switch to requested user: %s!", settings->…
444 }
445
446 if (setuid(uid) != 0) {
447 print_error("Couldn't switch to requested user: %s!", settings->…
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(in…
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…
530 print_error("Couldn't set a timeout!");
531 }
532
533 if ( setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout…
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) + …
686
687 char url[len];
688 snprintf(url, len, "%s%s%s%s", c->settings->domain, "/", slug, "…
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_l…
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 *sl…
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…
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, "/"…
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 }
You are viewing proxied material from vernunftzentrum.de. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.