1 /** @file runfilter.cc
2  * @brief Run an external filter and capture its output in a std::string.
3  *
4  * Copyright (C) 2003,2006,2007,2009,2010,2011 Olly Betts
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  */
20 
21 #include <config.h>
22 
23 #include "runfilter.h"
24 
25 #include <iostream>
26 #include <string>
27 
28 #include <sys/types.h>
29 #include "safeerrno.h"
30 #include "safefcntl.h"
31 #include <cstdio>
32 #ifdef HAVE_SYS_TIME_H
33 # include <sys/time.h>
34 #endif
35 #ifdef HAVE_SYS_RESOURCE_H
36 # include <sys/resource.h>
37 #endif
38 #include "safesysselect.h"
39 #ifdef HAVE_SYS_SOCKET_H
40 # include <sys/socket.h>
41 #endif
42 #include "safesyswait.h"
43 #include "safeunistd.h"
44 
45 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR && defined HAVE_SETRLIMIT
46 # include <csignal>
47 #endif
48 
49 #include "freemem.h"
50 
51 #ifdef _MSC_VER
52 # define popen _popen
53 # define pclose _pclose
54 #endif
55 
56 using namespace std;
57 
58 string
stdout_to_string(const string & cmd)59 stdout_to_string(const string &cmd)
60 {
61     string out;
62 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR && defined HAVE_SETRLIMIT
63     // We want to be able to get the exit status of the child process.
64     signal(SIGCHLD, SIG_DFL);
65 
66     int fds[2];
67     if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) < 0)
68 	throw ReadError();
69 
70     pid_t child = fork();
71     if (child == 0) {
72 	// We're the child process.
73 
74 	// Close the parent's side of the socket pair.
75 	close(fds[0]);
76 
77 	// Connect stdout to our side of the socket pair.
78 	dup2(fds[1], 1);
79 
80 	// Impose some pretty generous resource limits to prevent run-away
81 	// filter programs from causing problems.
82 
83 	// Limit CPU time to 300 seconds (5 minutes).
84 	struct rlimit cpu_limit = { 300, RLIM_INFINITY } ;
85 	setrlimit(RLIMIT_CPU, &cpu_limit);
86 
87 #if defined RLIMIT_AS || defined RLIMIT_VMEM || defined RLIMIT_DATA
88 	// Limit process data to free physical memory.
89 	long mem = get_free_physical_memory();
90 	if (mem > 0) {
91 	    struct rlimit ram_limit = {
92 		static_cast<rlim_t>(mem),
93 		RLIM_INFINITY
94 	    };
95 #ifdef RLIMIT_AS
96 	    setrlimit(RLIMIT_AS, &ram_limit);
97 #elif defined RLIMIT_VMEM
98 	    setrlimit(RLIMIT_VMEM, &ram_limit);
99 #else
100 	    // Only limits the data segment rather than the total address
101 	    // space, but that's better than nothing.
102 	    setrlimit(RLIMIT_DATA, &ram_limit);
103 #endif
104 	}
105 #endif
106 
107 	execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), (void*)NULL);
108 	_exit(-1);
109     }
110 
111     // We're the parent process.
112 
113     // Close the child's side of the socket pair.
114     close(fds[1]);
115     if (child == -1) {
116 	// fork() failed.
117 	close(fds[0]);
118 	throw ReadError();
119     }
120 
121     int fd = fds[0];
122 
123     fd_set readfds;
124     FD_ZERO(&readfds);
125     while (true) {
126 	// If we wait 300 seconds (5 minutes) without getting data from the
127 	// filter, then give up to avoid waiting forever for a filter which
128 	// has ended up blocked waiting for something which will never happen.
129 	struct timeval tv;
130 	tv.tv_sec = 300;
131 	tv.tv_usec = 0;
132 	FD_SET(fd, &readfds);
133 	int r = select(fd + 1, &readfds, NULL, NULL, &tv);
134 	if (r <= 0) {
135 	    if (r < 0) {
136 		if (errno == EINTR) {
137 		    // select() interrupted by a signal, so retry.
138 		    continue;
139 		}
140 		cerr << "Reading from filter failed (" << strerror(errno) << ")"
141 		     << endl;
142 	    } else {
143 		cerr << "Filter inactive for too long" << endl;
144 	    }
145 	    kill(child, SIGKILL);
146 	    close(fd);
147 	    int status;
148 	    (void)waitpid(child, &status, 0);
149 	    throw ReadError();
150 	}
151 
152 	char buf[4096];
153 	ssize_t res = read(fd, buf, sizeof(buf));
154 	if (res == 0) break;
155 	if (res == -1) {
156 	    if (errno == EINTR) {
157 		// read() interrupted by a signal, so retry.
158 		continue;
159 	    }
160 	    close(fd);
161 	    int status;
162 	    (void)waitpid(child, &status, 0);
163 	    throw ReadError();
164 	}
165 	out.append(buf, res);
166     }
167 
168     close(fd);
169     int status;
170     if (waitpid(child, &status, 0) == -1) {
171 	throw ReadError();
172     }
173 #else
174     FILE * fh = popen(cmd.c_str(), "r");
175     if (fh == NULL) throw ReadError();
176     while (!feof(fh)) {
177 	char buf[4096];
178 	size_t len = fread(buf, 1, 4096, fh);
179 	if (ferror(fh)) {
180 	    (void)pclose(fh);
181 	    throw ReadError();
182 	}
183 	out.append(buf, len);
184     }
185     int status = pclose(fh);
186 #endif
187 
188     if (status != 0) {
189 	if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
190 	    throw NoSuchFilter();
191 	}
192 #ifdef SIGXCPU
193 	if (WIFSIGNALED(status) && WTERMSIG(status) == SIGXCPU) {
194 	    cerr << "Filter process consumed too much CPU time" << endl;
195 	}
196 #endif
197 	throw ReadError();
198     }
199     return out;
200 }
201