1 /* XQF - Quake server browser and launcher
2  * Copyright (C) 1998-2000 Roman Pozlevich <roma@botik.ru>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
17  */
18 
19 #include "gnuconfig.h"
20 
21 #include <sys/types.h>  /* getpwnam, readdir, dirent */
22 #include <stdio.h>      /* FILE, putc */
23 #include <string.h>     /* strlen, strncpy, strcmp, strspn, strcspn, strchr */
24 #include <ctype.h>      /* isspace */
25 #include <pwd.h>        /* getpwnam */
26 #include <stdlib.h>     /* strtol */
27 #include <dirent.h>     /* readdir, dirent */
28 #include <signal.h>     /* sigaction, sigemptyset */
29 #include <ctype.h>      /* tolower */
30 #include <sys/stat.h>   /* stat */
31 #include <unistd.h>     /* access */
32 #include <fcntl.h>      /* fcntl */
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 
37 
38 #include <gtk/gtk.h>
39 
40 #include "utils.h"
41 #include "debug.h"
42 #include "i18n.h"
43 
44 static char* _find_file_in_path(const char* files, gboolean relative);
45 static char* _find_file_in_path_list(char** binaries, gboolean relative);
46 
strtosh(const char * str)47 short strtosh (const char *str) {
48 	long tmp;
49 
50 	tmp = strtol (str, NULL, 10);
51 	return CLAMP (tmp, G_MINSHORT, G_MAXSHORT);
52 }
53 
54 
strtoush(const char * str)55 unsigned short strtoush (const char *str) {
56 	long tmp;
57 
58 	tmp = strtol (str, NULL, 10);
59 	return CLAMP (tmp + G_MINSHORT, G_MINSHORT, G_MAXSHORT) - G_MINSHORT;
60 }
61 
62 
strdup_strip(const char * str)63 char *strdup_strip (const char *str) {
64 	const char *start;
65 	const char *end;
66 	char *res;
67 
68 	if (!str) {
69 		return NULL;
70 	}
71 
72 	for (start = str; *start && isspace (*start); start++);
73 
74 	if (!strlen(str)) {
75 		return NULL;
76 	}
77 
78 	for (end = str + strlen (str) - 1; end >= start && isspace (*end); end--);
79 
80 	if (start > end) {
81 		return NULL;
82 	}
83 
84 	res = g_malloc (end - start + 1 + 1);
85 	strncpy (res, start, end - start + 1);
86 	res[end - start + 1] = '\0';
87 
88 	return res;
89 }
90 
91 
92 // concatenate dir and file, insert slash if necessary
93 // returned string must be freed manually
file_in_dir(const char * dir,const char * file)94 char *file_in_dir (const char *dir, const char *file) {
95 	char *res, *tmp;
96 	int need_slash = 0;
97 
98 	if (!dir || dir[0] == '\0') /* dir "" is current dir */
99 		return (file)? g_strdup (file) : NULL;
100 
101 	if (!file) {
102 		return g_strdup (dir);
103 	}
104 
105 	if (dir[strlen (dir) - 1] != G_DIR_SEPARATOR) {
106 		need_slash = 1;
107 	}
108 
109 	tmp = res = g_malloc0 (strlen (dir) + strlen (file) + need_slash + 1);
110 
111 	strcpy (tmp, dir);
112 	tmp += strlen (dir);
113 	if (need_slash) {
114 		*tmp++ = G_DIR_SEPARATOR;
115 	}
116 
117 	strcpy (tmp, file);
118 
119 	return res;
120 }
121 
122 
str_isempty(const char * str)123 int str_isempty (const char *str) {
124 	if (!str) {
125 		return TRUE;
126 	}
127 
128 	for (; *str; str++) {
129 		if (!isspace (*str)) {
130 			return FALSE;
131 		}
132 	}
133 
134 	return TRUE;
135 }
136 
137 
138 #define MAXNAMELEN	256
139 
expand_tilde(const char * path)140 char *expand_tilde (const char *path) {
141 	char *res = NULL;
142 	char *slash;
143 	char name[MAXNAMELEN];
144 	int namelen;
145 	struct passwd *pwd;
146 
147 	if (path) {
148 		if (path[0] == '~') {
149 			if (path[1] == '\0') {
150 				res = g_strdup (g_get_home_dir ());
151 			}
152 			else if (path[1] == G_DIR_SEPARATOR) {
153 				res = file_in_dir (g_get_home_dir (), path + 2);
154 			}
155 			else {
156 				slash = strchr (&path[1], G_DIR_SEPARATOR);
157 				if (slash) {
158 					namelen = slash - &path[1];
159 				}
160 				else {
161 					namelen = strlen (&path[1]);
162 				}
163 
164 				if (namelen > MAXNAMELEN) {
165 					namelen = MAXNAMELEN - 1;
166 				}
167 
168 				strncpy (name, &path[1], namelen);
169 				name[namelen] = '\0';
170 
171 				pwd = getpwnam (name);
172 				if (pwd) {
173 					res = file_in_dir (pwd->pw_dir, path + 2 + namelen);
174 				}
175 				else {
176 					res = g_strdup (path);
177 				}
178 			}
179 		}
180 		else {
181 			res = g_strdup (path);
182 		}
183 	}
184 
185 #ifdef DEBUG
186 	fprintf (stderr, "Tilde expansion: %s -> %s\n", path, res);
187 #endif
188 
189 	return res;
190 }
191 
192 
193 /*
194  * Directory Scaning
195  */
196 
197 
dir_to_list(const char * dirname,char * (* filter)(const char *,const char *))198 GList *dir_to_list (const char *dirname,
199 		char * (*filter) (const char *, const char *)) {
200 	DIR *directory;
201 	struct dirent *dirent_ptr;
202 	GList *list = NULL;
203 	char *str;
204 
205 	if (!dirname) {
206 		return NULL;
207 	}
208 
209 	directory = opendir (dirname);
210 	if (directory == NULL) {
211 		return NULL;
212 	}
213 
214 	while ((dirent_ptr = readdir (directory)) != NULL) {
215 		if (filter) {
216 			str = filter (dirname, dirent_ptr->d_name);
217 		}
218 		else {
219 			str = g_strdup (dirent_ptr->d_name);
220 		}
221 
222 		if (str) {
223 			list = g_list_prepend (list, str);
224 		}
225 	}
226 
227 	closedir (directory);
228 
229 	return g_list_sort (list, (GCompareFunc) strcmp);
230 }
231 
232 
233 /*
234  * Lists
235  */
236 
237 
merge_sorted_string_lists(GList * list1,GList * list2)238 GList *merge_sorted_string_lists (GList *list1, GList *list2) {
239 	GList *res = NULL;
240 	GList *ptr1;
241 	GList *ptr2;
242 	int cmpres;
243 
244 	ptr1 = list1;
245 	ptr2 = list2;
246 
247 	while (ptr1 && ptr2) {
248 		cmpres = strcmp ((char *) ptr1->data, (char *) ptr2->data);
249 
250 		if (cmpres == 0) {
251 			res = g_list_prepend (res, ptr1->data);
252 			g_free (ptr2->data);
253 			ptr1 = ptr1->next;
254 			ptr2 = ptr2->next;
255 		} else {
256 			if (cmpres < 0) {
257 				res = g_list_prepend (res, ptr1->data);
258 				ptr1 = ptr1->next;
259 			}
260 			else {
261 				res = g_list_prepend (res, ptr2->data);
262 				ptr2 = ptr2->next;
263 			}
264 		}
265 	}
266 
267 	while (ptr1) {
268 		res = g_list_prepend (res, ptr1->data);
269 		ptr1 = ptr1->next;
270 	}
271 
272 	while (ptr2) {
273 		res = g_list_prepend (res, ptr2->data);
274 		ptr2 = ptr2->next;
275 	}
276 
277 	res = g_list_reverse (res);
278 	g_list_free (list1);
279 	g_list_free (list2);
280 	return res;
281 }
282 
283 
unique_strings(GSList * strings)284 GSList *unique_strings (GSList *strings) {
285 	GSList *result = NULL;
286 	GSList *tmp;
287 
288 	while (strings) {
289 		for (tmp = result; tmp; tmp = tmp->next) {
290 			if (strcmp ((char *) tmp->data, (char *) strings->data)) {
291 				result = g_slist_prepend (result, strings->data);
292 			}
293 		}
294 		strings = strings->next;
295 	}
296 
297 	return result;
298 }
299 
300 // build GList from array of char*
createGListfromchar(char * strings[])301 GList* createGListfromchar(char* strings[]) {
302 	GList *list = NULL;
303 	char** ptr = NULL;
304 	for (ptr=strings;ptr&&*ptr;ptr++) {
305 		list = g_list_append (list, *ptr);
306 	}
307 
308 	return list;
309 }
310 
311 
312 /*
313  * Signals
314  */
315 
316 
on_sig(int signum,void (* func)(int signum))317 void on_sig (int signum, void (*func) (int signum)) {
318 	struct sigaction action;
319 
320 	action.sa_handler = func;
321 	sigemptyset (&action.sa_mask);
322 	action.sa_flags = (signum == SIGCHLD)?
323 		SA_RESTART | SA_NOCLDSTOP : SA_RESTART;
324 	sigaction (signum, &action, NULL);
325 }
326 
327 
ignore_sigpipe(void)328 void ignore_sigpipe (void) {
329 	on_sig (SIGPIPE, SIG_IGN);
330 }
331 
332 
333 /*
334  * String Output
335  */
336 
print_dq_string(FILE * f,const char * ptr)337 void print_dq_string (FILE *f, const char *ptr) {
338 
339 	if (!f) {
340 		return;
341 	}
342 
343 	putc ('\"', f);
344 
345 	while (ptr && *ptr) {
346 		switch (*ptr) {
347 
348 			case '\n':
349 				putc ('\\', f); putc ('n', f);
350 				break;
351 
352 			case '\t':
353 				putc ('\\', f); putc ('t', f);
354 				break;
355 
356 			case '\r':
357 				putc ('\\', f); putc ('r', f);
358 				break;
359 
360 			case '\b':
361 				putc ('\\', f); putc ('b', f);
362 				break;
363 
364 			case '\f':
365 				putc ('\\', f); putc ('f', f);
366 				break;
367 
368 			case '\\':
369 			case '\'':
370 			case '\"':
371 				putc ('\\', f); putc (*ptr, f);
372 				break;
373 
374 			default:
375 				if (*ptr >= 0x20 && *ptr != 0x7F) {
376 					putc (*ptr, f);
377 				}
378 				else {
379 					fprintf (f, "\\%03o", *ptr);
380 				}
381 				break;
382 
383 		}
384 
385 		ptr++;
386 	}
387 
388 	putc ('\"', f);
389 }
390 
391 
lowcasestrstr(const char * str,const char * substr)392 char *lowcasestrstr (const char *str, const char *substr) {
393 	int slen;
394 	int sublen;
395 	const char *end;
396 	int i;
397 
398 	if (!str || !substr) {
399 		return NULL;
400 	}
401 
402 	slen = strlen (str);
403 	sublen = strlen (substr);
404 
405 	if (slen < sublen) {
406 		return NULL;
407 	}
408 
409 	end = &str[slen - sublen + 1];
410 
411 	while (str < end) {
412 		for (i = 0; i < sublen; i++) {
413 			if (tolower (substr[i]) != tolower (str[i])) {
414 				goto loop;
415 			}
416 		}
417 		return (char *) str;
418 
419 loop:
420 		str++;
421 	}
422 
423 	return NULL;
424 }
425 
426 
427 /*
428  *  Parse string to tokens
429  */
430 
tokenize(char * str,char * token[],int max,const char * dlm)431 int tokenize (char *str, char *token[], int max, const char *dlm) {
432 	int num = 0;
433 
434 	while (str && num < max) {
435 		str += strspn (str, dlm);
436 
437 		if (*str == '\0') {
438 			break;
439 		}
440 
441 		token[num++] = str;
442 
443 		if (num == max) {
444 			break;
445 		}
446 
447 		str += strcspn (str, dlm);
448 
449 		if (*str == '\0') {
450 			break;
451 		}
452 
453 		*str++ = '\0';
454 	}
455 
456 	return num;
457 }
458 
459 
safe_tokenize(const char * str,char * token[],int max,const char * dlm)460 int safe_tokenize (const char *str, char *token[], int max, const char *dlm) {
461 	int num = 0;
462 
463 	while (str && num < max) {
464 		str += strspn (str, dlm);
465 
466 		if (*str == '\0') {
467 			break;
468 		}
469 
470 		token[num++] = (char *) str;
471 		str += strcspn (str, dlm);
472 	}
473 
474 	return num;
475 }
476 
477 
tokenize_bychar(char * str,char * token[],int max,char dlm)478 int tokenize_bychar (char *str, char *token[], int max, char dlm) {
479 	int num = 0;
480 
481 	if (!str || str[0] == '\0') {
482 		return 0;
483 	}
484 
485 	while (str && num < max) {
486 		token[num++] = str;
487 		str = strchr (str, dlm);
488 		if (str) {
489 			*str++ = '\0';
490 		}
491 	}
492 
493 	return num;
494 }
495 
496 
hostname_is_valid(const char * hostname)497 int hostname_is_valid (const char *hostname) {
498 	const char *ptr;
499 
500 	if (!hostname) {
501 		return FALSE;
502 	}
503 
504 	for (ptr = hostname; *ptr; ptr++) {
505 		if (*ptr != '.' && *ptr != '-' &&
506 				(*ptr < '0' || *ptr > '9') &&
507 				(*ptr < 'a' || *ptr > 'z') &&
508 				(*ptr < 'A' || *ptr > 'Z')) {
509 #ifdef DEBUG
510 			fprintf (stderr, "Host name is not valid: %s\n", hostname);
511 #endif
512 			return FALSE;
513 		}
514 	}
515 	return TRUE;
516 }
517 
518 
find_server_setting_for_key(char * key,char ** info_ptr)519 char* find_server_setting_for_key (char *key, char **info_ptr){
520 	char **ptr;
521 
522 	if (key == NULL || info_ptr == NULL) {
523 		return (NULL);
524 	}
525 
526 	for (ptr = info_ptr; ptr && *ptr; ptr += 2) {
527 		if (strcasecmp (*ptr, key) == 0){
528 			debug(3, "find_server_setting_for_key() -- Found key '%s' with value '%s'", key, *(ptr+1));
529 			return (*(ptr+1));
530 		} else {
531 			debug(8, "Key '%s' w/val '%s' did not match '%s'", *ptr, *(ptr+1), key);
532 		}
533 	}
534 	return (NULL);
535 }
536 
537 // returns true if str is "true", false otherwise
str2bool(const char * str)538 int str2bool(const char* str) {
539 	if (!str) {
540 		return FALSE;
541 	}
542 
543 	if (!strcasecmp(str,"true")) {
544 		return TRUE;
545 	}
546 
547 	return FALSE;
548 }
549 
550 // return "false" if i == 0, "true" otherwise
bool2str(int i)551 const char* bool2str(int i) {
552 	if (!i) {
553 		return "false";
554 	}
555 	return "true";
556 }
557 
558 /** find executable file in $PATH. file is a colon seperated list of
559  * executables to search for. return name of first found file, NULL otherwise
560  * must be freed manually
561  */
find_file_in_path(const char * files)562 char* find_file_in_path(const char* files) {
563 	return _find_file_in_path(files, FALSE);
564 }
565 
find_file_in_path_relative(const char * files)566 char* find_file_in_path_relative(const char* files) {
567 	return _find_file_in_path(files, TRUE);
568 }
569 
find_file_in_path_list(char ** files)570 char* find_file_in_path_list(char** files) {
571 	return _find_file_in_path_list(files, FALSE);
572 }
573 
find_file_in_path_list_relative(char ** files)574 char* find_file_in_path_list_relative(char** files) {
575 	return _find_file_in_path_list(files, TRUE);
576 }
577 
578 /**
579   @param binaries NULL terminated list of strings
580 */
_find_file_in_path_list(char ** binaries,gboolean relative)581 static char* _find_file_in_path_list(char** binaries, gboolean relative) {
582 	char* path = NULL;
583 	int i = 0, j = 0;
584 	char** directories = NULL;
585 	char* found = NULL;
586 
587 	path = getenv("PATH");
588 
589 	if (!binaries) {
590 		return NULL;
591 	}
592 	if (!path) {
593 		return NULL;
594 	}
595 
596 	directories = g_strsplit(path,":",0);
597 
598 	for (i=0; binaries[i] && !found; i++) {
599 		//	debug(0,"search for %s",binaries[i]);
600 		for (j=0; directories[j] && !found; j++) {
601 			//	    debug(0,"search in %s",directories[j]);
602 			char *file = file_in_dir (directories[j], binaries[i]);
603 			if (!file) {
604 				continue;
605 			}
606 
607 			if (!access(file,X_OK)) {
608 				// If directory name is blank, don't add a / - happens if
609 				// a complete path was passed
610 				if (!relative && strlen(directories[j])) {
611 					found = g_strconcat(directories[j],"/",binaries[i],NULL);
612 				}
613 				else {
614 					found = g_strdup(binaries[i]);
615 				}
616 				debug(3,"found %s in %s",binaries[i],directories[j]);
617 			}
618 			g_free(file);
619 		}
620 	}
621 
622 	g_strfreev(directories);
623 
624 	return found;
625 }
626 
_find_file_in_path(const char * files,gboolean relative)627 static char* _find_file_in_path(const char* files, gboolean relative) {
628 	char** binaries = NULL;
629 	char* found = NULL;
630 
631 	binaries = g_strsplit(files,":",0);
632 
633 	found = _find_file_in_path_list(binaries, relative);
634 
635 	g_strfreev(binaries);
636 
637 	return found;
638 }
639 
640 /** find a directory inside another, prefer matching case otherwise search case insensitive
641  * @param basegamedir where to search
642  * @param game directory to search for
643  * @param match_result output: 0 = not found, 1 = exact match, 2 = differnet case match
644  * @returns name of found directory or NULL, must be freed manually
645  */
find_game_dir(const char * basegamedir,const char * game,int * match_result)646 char *find_game_dir (const char *basegamedir, const char *game, int *match_result) {
647 	DIR *dp = NULL;
648 	struct stat buf = {0};
649 	char *path = NULL;
650 	char* ret = NULL;
651 	int result = 0;
652 
653 	if (!game || !basegamedir) {
654 		return NULL;
655 	}
656 
657 	debug(3, "Looking for subdir %s in %s", game, basegamedir);
658 
659 	// Look for exact match
660 	path = file_in_dir (basegamedir, game);
661 	if (!stat (path, &buf) && S_ISDIR(buf.st_mode)) {
662 		debug(3, "Found exact match for subdir %s in %s", game, basegamedir);
663 		result = 1; // Exact match
664 		ret = g_strdup(game);
665 	}
666 	g_free (path);
667 
668 	// Did not find exact match, perform search
669 	if (!ret) {
670 		debug(3, "Did not find exact match for subdir %s in %s", game, basegamedir);
671 		debug(3, "Searching for subdir %s in %s ignoring case", game, basegamedir);
672 
673 		dp = opendir (basegamedir);
674 		if (!dp) {
675 			debug(3, "Could not open base directory %s!", basegamedir);
676 		}
677 		else {
678 			struct dirent *ep;
679 			while ((ep = readdir (dp))) {
680 				char* name = ep->d_name;
681 
682 				if (!strcmp(name, ".") || !strcmp(name,"..")) {
683 					continue;
684 				}
685 
686 				path = file_in_dir(basegamedir, name);
687 
688 				if (stat(path, &buf) == -1) {
689 					g_free(path);
690 					continue;
691 				}
692 
693 				g_free(path);
694 
695 				if (!S_ISDIR(buf.st_mode)) {
696 					continue;
697 				}
698 
699 				if (!g_ascii_strcasecmp(name, game)) {
700 					debug(3, "Found subdir %s in %s that matches %s", ep->d_name, basegamedir, game);
701 					result = 2; // Different case match
702 					ret = g_strdup (name);
703 					break;
704 				}
705 			}
706 			closedir (dp);
707 		}
708 	}
709 
710 	if (!ret) {
711 		debug(3, "Could not find any match for subdir %s in %s.",game, basegamedir);
712 	}
713 
714 	if (match_result) {
715 		*match_result = result;
716 	}
717 
718 	return ret;
719 }
720 
721 /** sort list and remove duplicates
722  * @param list list to sort
723  * @compare_func function to use for comparing
724  * @unref_func function to call for each deleted entry
725  * @return sorted list without duplicates
726  */
slist_sort_remove_dups(GSList * list,GCompareFunc compare_func,void (* unref_func)(void *))727 GSList* slist_sort_remove_dups(GSList* list, GCompareFunc compare_func, void (*unref_func)(void*)) {
728 	int i;
729 	GSList* serverlist = NULL;
730 	GSList* serverlistnext = NULL;
731 
732 	if (!list) {
733 		return NULL;
734 	}
735 
736 	list = serverlist = g_slist_sort (list, compare_func);
737 
738 	i = 0;
739 	while (serverlist) {
740 		serverlistnext=serverlist->next;
741 		if (!serverlistnext) {
742 			// last element, quit loop
743 			serverlist = serverlistnext;
744 		}
745 		else if (!compare_func(serverlist->data,serverlistnext->data)) {
746 			GSList* dup = serverlistnext;
747 			serverlistnext = g_slist_remove_link(serverlistnext, dup);
748 			serverlist->next = serverlistnext;
749 			if (unref_func) unref_func(dup->data);
750 			g_slist_free_1(dup);
751 		}
752 		else {
753 			serverlist= serverlistnext;
754 		}
755 		i++;
756 	}
757 	debug(2,"number of servers %d",i);
758 
759 	return list;
760 }
761 
762 /** \brief determine directory for game binary
763  *
764  * Extracts the path from path using the following rules:
765  * - If path is a symlink:
766  *   - If pointed to file contains '..', just stop, otherwise strip filename and
767  *     store as directory
768  *   - If there is no /'s in the pointed to file, use the original path instead and
769  *     strip filename and store as directory
770  *
771  * - If path is not a symlink:
772  *   - strip filename and store as directory
773  *
774  * Path can be either a file or a directory
775  *
776  * Examples:
777  *
778  * path:	/usr/bin/quake2 symlink to /games/quake2/quake2
779  * result:	/games/quake2/
780  *
781  * path:	/usr/bin/quake symlink to ../../games/quake2/quake2
782  * result:	(stops - leaves as-is)
783  *
784  * path:	/games/quake2/quake2
785  * result:	/games/quake2/
786  *
787  * path:	quake2
788  * result:	search $PATH for binary then use above rules
789  *
790  * path:	~/bin/quake2
791  * result:	expand tilde then use above rules
792  *
793  * @returns game direcory or NULL. must be freed
794  */
795 
resolve_path(const char * path)796 char* resolve_path(const char* path) {
797 
798 	struct stat statbuf;
799 	int length = 0;
800 	char buf[256];
801 	char *ptr = NULL;
802 	char *dir = NULL;
803 	char* tmp = NULL;
804 
805 	if (!path || !*path) {
806 		return NULL;
807 	}
808 
809 	if (path[0] == '~') {
810 		tmp = expand_tilde(path);
811 		path = tmp;
812 	}
813 	else if (path[0] != '/') {
814 		tmp = find_file_in_path(path);
815 		if (!tmp) {
816 			debug(3, "%s not found in $PATH", path);
817 			return NULL;
818 		}
819 		path = tmp;
820 	}
821 
822 	if (lstat(path, &statbuf) == -1) {
823 		debug(3, "lstat on %s failed", path);
824 		g_free(tmp);
825 		return NULL;
826 	}
827 
828 	if (S_ISLNK(statbuf.st_mode) == 1) {
829 		// Grab directory from sym link of cmd_entry
830 
831 		debug(3, "path is a sym link");
832 
833 		length = readlink (path, buf, sizeof(buf) - 1);
834 
835 		if (length > 0) {
836 			buf[length]='\0';
837 
838 			if (buf[length-1] == '/') {
839 				buf[length-1] = '\0';
840 			}
841 
842 			ptr = strrchr(buf, '/');
843 
844 			if (ptr) {                      // contains a / so pull from symlink
845 				if (!strstr(buf,"..")) {    // don't bother if it's got any ..'s in it
846 					dir = g_strndup(buf, ptr-buf+1);
847 				}
848 			}
849 			else {                          // no / so pull from path instead
850 				ptr = strrchr(path, '/');
851 				if (ptr) {                  // contains a /
852 					dir = g_strndup(path, ptr-path+1);
853 				}
854 			}
855 		}
856 	}
857 	else {
858 		// Grab directory from cmd_entry
859 
860 		debug(3,"path is NOT a sym link");
861 
862 		ptr = strrchr(path, '/');
863 
864 		if (ptr) // contains a /
865 		{
866 			gboolean dir_is_path = FALSE;
867 			char* PATH = getenv("PATH");
868 
869 			// ignore direcory if it is in $PATH. It doesn't make sense to suggest
870 			// /usr/bin as game direcory
871 			if (PATH) {
872 				int i;
873 				char** directories = g_strsplit(PATH,":",0);
874 				char* basedir = g_strndup(path, ptr-path);
875 
876 				for (i=0; directories[i]; ++i) {
877 					if (!strcmp(directories[i], basedir)) {
878 						dir_is_path = TRUE;
879 						break;
880 					}
881 				}
882 
883 				g_strfreev(directories);
884 				g_free(basedir);
885 			}
886 
887 			if (!dir_is_path) {
888 				dir = tmp;
889 				tmp = NULL;
890 			}
891 			else {
892 				debug(3, "found directory %s in $PATH, ignoring", tmp);
893 			}
894 		}
895 	}
896 
897 	g_free(tmp);
898 
899 	return dir;
900 }
901 
902 /** workaround to prevent gcc from complaining about %c as suggested by "man strftime" */
my_strftime(char * s,size_t max,const char * fmt,const struct tm * tm)903 static inline size_t my_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) {
904 	return strftime(s, max, fmt, tm);
905 }
906 
907 // return locale's string representation of t. must be freed manually
timet2string(const time_t * t)908 char* timet2string(const time_t* t) {
909 	enum { timebuf_len = 128 };
910 	char timebuf[timebuf_len] = {0};
911 	struct tm tm_s;
912 	char* str;
913 
914 	gmtime_r(t,&tm_s);
915 	if (!my_strftime(timebuf,timebuf_len,"%c",&tm_s)) {
916 		// error converting time to string representation, shouldn't happen
917 		str=_("<error>");
918 	}
919 	else {
920 		str = timebuf;
921 	}
922 
923 	return strdup(str);
924 }
925 
set_nonblock(int fd)926 int set_nonblock (int fd) {
927 	int flags;
928 
929 	flags = fcntl (fd, F_GETFL, 0);
930 	if (flags < 0 || fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0) {
931 		return -1;
932 	}
933 	return 0;
934 }
935 
936 // TODO: code is generic enough to move into separate file
937 
938 #if !defined(RCON_STANDALONE)
start_prog_and_return_fd(char * const argv[],pid_t * pid)939 int start_prog_and_return_fd(char *const argv[], pid_t *pid) {
940 	int pipefds[2];
941 
942 	*pid = -1;
943 
944 	if (pipe (pipefds) < 0) {
945 		xqf_error("error creating pipe: %s",strerror(errno));
946 		return -1;
947 	}
948 	*pid = fork();
949 	if (*pid < (pid_t) 0) {
950 		xqf_error("fork failed: %s",strerror(errno));
951 		return -1;
952 	}
953 
954 	if (*pid == 0)  // child
955 	{
956 		close(1);
957 		// close(2);
958 		close (pipefds[0]);
959 		dup2 (pipefds[1], 1);
960 		// dup2 (pipefds[1], 2);
961 		close (pipefds[1]);
962 
963 		close_fds(-1);
964 
965 		debug(3,"child about to exec %s", argv[0]);
966 
967 		execvp (argv[0], argv);
968 
969 		xqf_error("failed to exec %s: %s",argv[0],strerror(errno));
970 
971 		_exit (1);
972 	}
973 
974 	close (pipefds[1]);
975 
976 	if (set_nonblock(pipefds[0]) == -1) {
977 		close (pipefds[1]);
978 		if (*pid > 0) {
979 			kill(*pid,SIGTERM);
980 		}
981 		xqf_error("fcntl failed: %s", strerror(errno));
982 		return -1;
983 	}
984 
985 	return pipefds[0];
986 }
987 
external_program_close_input(struct external_program_connection * conn)988 void external_program_close_input(struct external_program_connection* conn) {
989 	if (!conn) {
990 		return;
991 	}
992 	gdk_input_remove(conn->tag);
993 	close(conn->fd);
994 	if (conn->pid > 0) kill(conn->pid,SIGTERM);
995 	if (conn->do_quit) gtk_main_quit();
996 }
997 
external_program_input_callback(struct external_program_connection * conn,int fd,GIOCondition condition)998 void external_program_input_callback(struct external_program_connection* conn, int fd, GIOCondition condition) {
999 	int bytes;
1000 	char* sol; // start of line pointer
1001 	char* eol; // end of line pointer
1002 
1003 	if (!conn) {
1004 		return;
1005 	}
1006 
1007 	if (conn->pos >= conn->bufsize) {
1008 		xqf_error("line %d too long",conn->linenr+1);
1009 		external_program_close_input(conn);
1010 		return;
1011 	}
1012 
1013 	bytes = read (fd, conn->buf + conn->pos, conn->bufsize - conn->pos);
1014 
1015 	if (bytes < 0) {
1016 		if (errno == EAGAIN || errno == EWOULDBLOCK) {
1017 			return;
1018 		}
1019 
1020 		xqf_error("Error reading from child: %s",g_strerror(errno));
1021 		external_program_close_input(conn);
1022 		return;
1023 	}
1024 	if (bytes == 0) {   /* EOF */
1025 		external_program_close_input(conn);
1026 		return;
1027 	}
1028 
1029 
1030 	sol = conn->buf;
1031 	eol = conn->buf + conn->pos;
1032 	conn->pos += bytes;
1033 	bytes = conn->pos;
1034 
1035 	// buffer can contain multiple lines
1036 	for (;(eol = memchr(eol,'\n',bytes-(eol-sol))) != NULL;bytes-=eol-sol+1,sol= ++eol) {
1037 		*eol = '\0';
1038 		// debug(0,"%4d, line(%4d,%4d-%4d)>%s<",bytes, eol-sol,sol-conn->buf,eol-conn->buf,sol);
1039 		++conn->linenr;
1040 		conn->current_line = sol;
1041 		if (conn->linefunc) (conn->linefunc)(conn);
1042 	}
1043 	// sol now points to begin of next line, if any
1044 
1045 	if (sol-conn->buf) {
1046 		if (bytes) {
1047 			memmove(conn->buf,sol,bytes);
1048 		}
1049 		conn->pos = bytes;
1050 	}
1051 }
1052 
external_program_foreach_line(char * argv[],void (* linefunc)(struct external_program_connection * conn),gpointer data)1053 int external_program_foreach_line(char* argv[], void (*linefunc)(struct external_program_connection* conn), gpointer data) {
1054 	struct external_program_connection conn = {0};
1055 
1056 	conn.fd = start_prog_and_return_fd(argv,&conn.pid);
1057 
1058 	if (conn.fd<0||conn.pid<=0) {
1059 		return FALSE;
1060 	}
1061 
1062 	conn.bufsize = 1024;
1063 	conn.buf = g_new0(char,conn.bufsize);
1064 	conn.result = FALSE;
1065 	conn.do_quit = TRUE;
1066 	conn.linenr = 0;
1067 	conn.linefunc = linefunc;
1068 	conn.data = data;
1069 
1070 	conn.tag = gdk_input_add (conn.fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
1071 			(GdkInputFunction) external_program_input_callback, &conn);
1072 
1073 	gtk_main();
1074 
1075 	g_free(conn.buf);
1076 
1077 	return conn.result;
1078 }
1079 
_run_program_sync(const char * argv[],void (* child_callback)(void *),gpointer data)1080 int _run_program_sync(const char* argv[], void(*child_callback)(void*), gpointer data) {
1081 	int status = -1;
1082 	pid_t pid;
1083 
1084 	pid = fork();
1085 	if (pid == 0) {
1086 		if (child_callback) {
1087 			child_callback(data);
1088 		 }
1089 		execvp(argv[0],(void*)argv);
1090 		_exit(EXIT_FAILURE);
1091 	}
1092 	else if (pid > 0) {
1093 		waitpid(pid,&status,0);
1094 
1095 		if (WIFEXITED(status)) {
1096 			debug(3,"%s exited normally", argv[0]);
1097 		}
1098 		else {
1099 			debug(3,"%s exited with status %d", argv[0], WEXITSTATUS(status));
1100 		}
1101 
1102 		if (WIFSIGNALED(status)) {
1103 			debug(3,"%s was killed by signal %d", argv[0], WTERMSIG(status));
1104 		}
1105 	}
1106 
1107 	return status;
1108 }
1109 
run_program_sync(const char * argv[])1110 int run_program_sync(const char* argv[]) {
1111 	return _run_program_sync(argv, NULL, NULL);
1112 }
1113 
run_program_sync_callback(const char * argv[],void (* child_callback)(void *),gpointer data)1114 int run_program_sync_callback(const char* argv[], void(*child_callback)(void*), gpointer data) {
1115 	return _run_program_sync(argv, child_callback, data);
1116 }
1117 
copy_file(const char * src,const char * dest)1118 const char* copy_file(const char* src, const char* dest) {
1119 	char buf[4*4096];
1120 	const char* msg = NULL;
1121 	int fdin = -1, fdout = -1;
1122 	int ret;
1123 
1124 	fdin = open(src, O_RDONLY);
1125 	if (fdin == -1) {
1126 		msg = _("Can't open source file for reading");
1127 		goto error_out;
1128 	}
1129 
1130 	fdout = open(dest, O_CREAT|O_WRONLY|O_TRUNC, 0777);
1131 	if (fdout == -1) {
1132 		msg = _("Can't open destination file for writing");
1133 		goto error_out;
1134 	}
1135 
1136 	while ((ret = read(fdin, buf, sizeof(buf))) > 0) {
1137 		if (write(fdout, buf, ret) == -1) {
1138 			msg = _("write error on destination file");
1139 			goto error_out;
1140 		}
1141 	}
1142 
1143 	if (ret == -1) {
1144 		msg = _("read error on source file");
1145 		goto error_out;
1146 	}
1147 
1148 error_out:
1149 	if (fdin != -1) {
1150 		close(fdin);
1151 	}
1152 	if (fdout != -1) {
1153 		close(fdout);
1154 	}
1155 
1156 	if (msg) {
1157 		unlink(dest);
1158 	}
1159 
1160 	return msg;
1161 }
1162 
load_file_mem(const char * name,size_t * size)1163 char* load_file_mem(const char* name, size_t* size) {
1164 	char* buf = NULL;
1165 	struct stat stb;
1166 	int fd;
1167 
1168 	fd = open(name, O_RDONLY);
1169 	if (fd == -1) {
1170 		goto out;
1171 	}
1172 
1173 	if (fstat(fd, &stb) == -1) {
1174 		goto out;
1175 	}
1176 
1177 	buf = g_malloc(stb.st_size);
1178 
1179 	if (read(fd, buf, stb.st_size) != stb.st_size) {
1180 		g_free(buf);
1181 		buf = NULL;
1182 		goto out;
1183 	}
1184 
1185 out:
1186 	if (fd != -1) {
1187 		close(fd);
1188 	}
1189 
1190 	if (buf) {
1191 		*size = stb.st_size;
1192 	}
1193 
1194 	return buf;
1195 }
1196 #endif  // ! RCON_STANDALONE
1197 
close_fds(int exclude)1198 void close_fds(int exclude) {
1199 	unsigned i;
1200 	int maxfd = sysconf(_SC_OPEN_MAX);
1201 	for (i = 3; i < maxfd; ++i) {
1202 		if (i == exclude) {
1203 			continue;
1204 		}
1205 		close(i);
1206 	}
1207 }
1208