1/*
2 * util.c
3 *
4 * Copyright (c) 1990, 1991, John W. Eaton.
5 *
6 * You may distribute under the terms of the GNU General Public
7 * License as specified in the file COPYING that comes with the man
8 * distribution.
9 *
10 * John W. Eaton
11 * jwe@che.utexas.edu
12 * Department of Chemical Engineering
13 * The University of Texas at Austin
14 * Austin, Texas  78712
15 */
16
17#include <stdio.h>
18#include <string.h>
19#include <stdlib.h>
20#include <stdarg.h>
21#include <signal.h>
22#include <sys/types.h>
23#include <sys/stat.h>
24#include <sys/wait.h>
25#include <unistd.h>
26
27#include "util.h"
28#include "gripes.h"
29#include "man.h"		/* for debug */
30
31/*
32 * Extract last element of a name like /foo/bar/baz.
33 */
34const char *
35mkprogname (const char *s) {
36     const char *t;
37
38     t = strrchr (s, '/');
39     if (t == (char *)NULL)
40	  t = s;
41     else
42	  t++;
43
44     return my_strdup (t);
45}
46
47/*
48 * Is file a nonempty and newer than file b?
49 *
50 * case:
51 *
52 *   a newer than b              returns    1
53 *   a older than b              returns    0
54 *   stat on a fails or a empty  returns   -1
55 *   stat on b fails or b empty  returns   -2
56 *   both fail or empty  	 returns   -3
57 */
58int
59is_newer (const char *fa, const char *fb) {
60     struct stat fa_sb;
61     struct stat fb_sb;
62     register int fa_stat;
63     register int fb_stat;
64     register int status = 0;
65
66     fa_stat = stat (fa, &fa_sb);
67     if (fa_stat != 0 || fa_sb.st_size == 0)
68	  status = 1;
69
70     fb_stat = stat (fb, &fb_sb);
71     if (fb_stat != 0 || fb_sb.st_size == 0)
72	  status |= 2;
73
74     if (status != 0)
75	  return -status;
76
77     return (fa_sb.st_mtime > fb_sb.st_mtime);
78}
79
80int ruid, rgid, euid, egid, suid;
81
82void
83get_permissions (void) {
84     ruid = getuid();
85     euid = geteuid();
86     rgid = getgid();
87     egid = getegid();
88     suid = (ruid != euid || rgid != egid);
89}
90
91void
92no_privileges (void) {
93     if (suid) {
94#if !defined (__CYGWIN32__) && !defined (__BEOS__)
95	  setreuid(ruid, ruid);
96	  setregid(rgid, rgid);
97#endif
98	  suid = 0;
99     }
100}
101
102/*
103 * What to do upon an interrupt?  Experience shows that
104 * if we exit immediately, sh notices that its child has
105 * died and will try to fiddle with the tty.
106 * Simultaneously, also less will fiddle with the tty,
107 * resetting the mode before exiting.
108 * This leads to undesirable races. So, we catch SIGINT here
109 * and exit after the child has exited.
110 */
111static int interrupted = 0;
112static void catch_int(int a) {
113	interrupted = 1;
114}
115
116static int
117system1 (const char *command) {
118	void (*prev_handler)(int) = signal (SIGINT,catch_int);
119	int ret = system(command);
120
121	/* child terminated with signal? */
122	if (WIFSIGNALED(ret) &&
123	    (WTERMSIG(ret) == SIGINT || WTERMSIG(ret) == SIGQUIT))
124		exit(1);
125
126	/* or we caught an interrupt? */
127	if (interrupted)
128		exit(1);
129
130	signal(SIGINT,prev_handler);
131	return ret;
132}
133
134static int
135my_system (const char *command) {
136     int pid, pid2, status, stat;
137
138     if (!suid)
139	  return system1 (command);
140
141#ifdef _POSIX_SAVED_IDS
142
143     /* we need not fork */
144     setuid(ruid);
145     setgid(rgid);
146     status = system1(command);
147     setuid(euid);
148     setgid(egid);
149     return (WIFEXITED(status) ? WEXITSTATUS(status) : 127);
150#endif
151
152     fflush(stdout); fflush(stderr);
153     pid = fork();
154     if (pid == -1) {
155	  perror(progname);
156	  fatal (CANNOT_FORK, command);
157     }
158     if (pid == 0) {
159	  setuid(ruid);
160	  setgid(rgid);
161	  status = system1 (command);
162	  exit(WIFEXITED(status) ? WEXITSTATUS(status) : 127);
163     }
164     pid2 = wait (&stat);
165     if (pid2 == -1) {
166	  perror(progname);
167	  fatal (WAIT_FAILED, command); 	/* interrupted? */
168     }
169     if (pid2 != pid)
170	  fatal (GOT_WRONG_PID);
171     if (WIFEXITED(stat) && WEXITSTATUS(stat) != 127)
172	  return WEXITSTATUS(stat);
173     fatal (CHILD_TERMINATED_ABNORMALLY, command);
174     return -1;			/* not reached */
175}
176
177FILE *
178my_popen(const char *command, const char *type) {
179     FILE *r;
180
181     if (!suid)
182	  return popen(command, type);
183
184#ifdef _POSIX_SAVED_IDS
185     setuid(ruid);
186     setgid(rgid);
187     r = popen(command, type);
188     setuid(euid);
189     setgid(egid);
190     return r;
191#endif
192
193     no_privileges();
194     return popen(command, type);
195}
196
197#define NOT_SAFE "/unsafe/"
198
199/*
200 * Attempt a system () call.
201 */
202int
203do_system_command (const char *command, int silent) {
204     int status = 0;
205
206     /*
207      * If we're debugging, don't really execute the command
208      */
209     if ((debug & 1) || !strncmp(command, NOT_SAFE, strlen(NOT_SAFE)))
210	  gripe (NO_EXEC, command);
211     else
212	  status = my_system (command);
213
214     if (status && !silent)
215	  gripe (SYSTEM_FAILED, command, status);
216
217     return status;
218}
219
220char *
221my_malloc (int n) {
222    char *s = malloc(n);
223    if (!s)
224	fatal (OUT_OF_MEMORY, n);
225    return s;
226}
227
228char *
229my_strdup (const char *s) {
230    char *t = my_malloc(strlen(s) + 1);
231    strcpy(t, s);
232    return t;
233}
234
235/*
236 * Call: my_xsprintf(format,s1,s2,...) where format only contains %s/%S/%Q
237 * (or %d or %o) and all %s/%S/%Q parameters are strings.
238 * Result: allocates a new string containing the sprintf result.
239 * The %S parameters are checked for being shell safe.
240 * The %Q parameters are checked for being shell safe inside single quotes.
241 */
242
243static int
244is_shell_safe(const char *ss, int quoted) {
245	char *bad = " ;'\\\"<>|";
246	char *p;
247
248	if (quoted)
249		bad++;			/* allow a space inside quotes */
250	for (p = bad; *p; p++)
251		if (strchr(ss, *p))
252			return 0;
253	return 1;
254}
255
256static void
257nothing(int x) {}
258
259char *
260my_xsprintf (char *format, ...) {
261	va_list p;
262	char *s, *ss, *fm;
263	int len;
264
265	len = strlen(format) + 1;
266	fm = my_strdup(format);
267
268	va_start(p, format);
269	for (s = fm; *s; s++) {
270		if (*s == '%') {
271			switch (s[1]) {
272			case 'Q':
273			case 'S': /* check and turn into 's' */
274				ss = va_arg(p, char *);
275				if (!is_shell_safe(ss, (s[1] == 'Q')))
276					return my_strdup(NOT_SAFE);
277				len += strlen(ss);
278				s[1] = 's';
279				break;
280			case 's':
281				len += strlen(va_arg(p, char *));
282				break;
283			case 'd':
284			case 'o':
285			case 'c':
286				len += 20;
287				nothing(va_arg(p, int)); /* advance */
288				break;
289			default:
290				fprintf(stderr,
291					"my_xsprintf called with %s\n",
292					format);
293				exit(1);
294			}
295		}
296	}
297	va_end(p);
298
299	s = my_malloc(len);
300	va_start(p, format);
301	vsprintf(s, fm, p);
302	va_end(p);
303
304	return s;
305}
306