]> git.saurik.com Git - apple/security.git/blame - Keychain/ExecCLITool.cpp
Security-163.tar.gz
[apple/security.git] / Keychain / ExecCLITool.cpp
CommitLineData
df0e469f
A
1
2#include <sys/wait.h>
3#include "ExecCLITool.h"
4#include <Security/utilities.h>
5#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
6
7#pragma mark -------------------- ExecCLITool implementation --------------------
8
9ExecCLITool::ExecCLITool() : dataRead(NULL),dataLength(0),dataToWrite(NULL),dataToWriteLength(0)
10{
11 stdinpipe[0]=0, stdinpipe[1]=0;
12 stdoutpipe [0]=0, stdoutpipe [1]=0;
13}
14
15ExecCLITool::~ExecCLITool()
16{
17 if (dataRead)
18 free(dataRead);
19 reset();
20}
21
22int ExecCLITool::run(const char *toolPath, const char *toolEnvVar, ...)
23{
24 try
25 {
26 reset();
27 initialize();
28
29 // try to run the tool
30 switch (pid_t pid = fork())
31 {
32 case 0: // child
33 {
34 VAArgList arglist;
35 va_list params;
36 va_start(params, toolEnvVar);
37 arglist.set(toolPath,params);
38 va_end(params);
39 child(toolPath,toolEnvVar,arglist);
40 }
41 break;
42 case -1: // error (in parent)
43 UnixError::throwMe();
44 break;
45 default: // parent
46 parent(pid);
47 break;
48 }
49 }
50 catch (...)
51 {
52 closeAllPipes();
53 return errno;
54 }
55
56 closeAllPipes();
57 return 0;
58}
59
60void ExecCLITool::reset()
61{
62 closeAllPipes();
63#if 0
64 if (dataToWrite)
65 {
66 free(dataToWrite);
67 dataToWrite = NULL;
68 }
69 dataToWriteLength = 0;
70#endif
71}
72
73void ExecCLITool::input(const char *data,unsigned int length)
74{
75 if (dataToWrite)
76 {
77 ::free(dataToWrite);
78 dataToWrite = NULL;
79 }
80 dataToWriteLength=length;
81 if (!data)
82 return;
83
84 dataToWrite=reinterpret_cast<char *>(malloc(length));
85 ::memmove(dataToWrite, data, dataToWriteLength);
86}
87
88void ExecCLITool::input(CFStringRef theString, bool appendNULL)
89{
90 // Used mainly for preserving UTF-8 passwords
91 // hdiutil et al require the NULL to be sent as part of the password string from STDIN
92 Boolean isExternalRepresentation = false;
93 CFStringEncoding encoding = kCFStringEncodingUTF8;
94 CFIndex usedBufLen = 0;
95 UInt8 lossByte = 0;
96
97 if (!theString)
98 MacOSError::throwMe(paramErr);
99
100 CFRange stringRange = CFRangeMake(0,CFStringGetLength(theString));
101 // Call once first just to get length
102 CFIndex length = CFStringGetBytes(theString, stringRange, encoding, lossByte,
103 isExternalRepresentation, NULL, 0, &usedBufLen);
104
105 if (dataToWrite)
106 ::free(dataToWrite);
107 dataToWriteLength=usedBufLen;
108 if (appendNULL)
109 {
110 dataToWriteLength++;
111 dataToWriteLength++;
112}
113
114 dataToWrite=reinterpret_cast<char *>(malloc(dataToWriteLength));
115 length = CFStringGetBytes(theString, stringRange, encoding, lossByte, isExternalRepresentation,
116 reinterpret_cast<UInt8 *>(dataToWrite), dataToWriteLength, &usedBufLen);
117
118 if (appendNULL)
119 {
120 dataToWrite[dataToWriteLength-1]=0;
121 dataToWrite[dataToWriteLength]='\n';
122 }
123}
124
125void ExecCLITool::initialize()
126{
127 dataLength = 0; // ignore any previous output on new run
128
129 if (!dataRead) // Allocate buffer for child's STDOUT return
130 {
131 dataRead = (char *)malloc(256);
132 if (!dataRead)
133 UnixError::throwMe();
134 }
135
136 // Create pipe to catch tool output
137 if (pipe(stdoutpipe)) // for reading data from child into parent
138 UnixError::throwMe();
139
140 if (pipe(stdinpipe)) // for writing data from parent to child
141 UnixError::throwMe();
142}
143
144void ExecCLITool::child(const char *toolPath, const char *toolEnvVar, VAArgList& arglist)
145{
146 // construct path to tool
147 try
148 {
149 char toolExecutable[PATH_MAX + 1];
150 const char *path = toolEnvVar ? getenv(toolEnvVar) : NULL;
151 if (!path)
152 path = toolPath;
153 snprintf(toolExecutable, sizeof(toolExecutable), "%s", toolPath);
154
155 close(stdoutpipe[0]); // parent read
156 close(STDOUT_FILENO);
157 if (dup2(stdoutpipe[1], STDOUT_FILENO) < 0)
158 UnixError::throwMe();
159 close(stdoutpipe[1]);
160
161 close(stdinpipe[1]); // parent write
162 close(STDIN_FILENO);
163 if (dup2(stdinpipe[0], STDIN_FILENO) < 0)
164 UnixError::throwMe();
165 close(stdinpipe[0]);
166
167 // std::cerr << "execl(\"" << toolExecutable << "\")" << std::endl;
168 execv(toolPath, const_cast<char * const *>(arglist.get()));
169 // std::cerr << "execl of " << toolExecutable << " failed, errno=" << errno << std::endl;
170 }
171 catch (...)
172 {
173 int err = errno;
174// closeAllPipes();
175 _exit(err);
176 }
177
178 // Unconditional suicide follows.
179 _exit(1);
180}
181
182void ExecCLITool::parent(pid_t pid)
183{
184 static const int timeout = 300;
185 static const bool dontNeedToWait = false;
186
187 close(stdinpipe[0]); // child read
188 close(stdoutpipe[1]); // child write
189
190 parentWriteInput();
191
192 parentReadOutput();
193
194 struct timespec rqtp = {0,};
195 rqtp.tv_nsec = 100000000; // 10^8 nanoseconds = 1/10th of a second
196 for (int nn = timeout; nn > 0; nanosleep(&rqtp, NULL), nn--)
197 {
198 if (dontNeedToWait)
199 break;
200 int status;
201 switch (waitpid(pid, &status, WNOHANG))
202 {
203 case 0: // child still running
204 break;
205 case -1: // error
206 switch (errno)
207 {
208 case EINTR:
209 case EAGAIN: // transient
210 continue;
211 case ECHILD: // no such child (dead; already reaped elsewhere)
212 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
213 default:
214 UnixError::throwMe();
215 }
216 default:
217 // std::cerr << "waitpid succeeded, pid=" << rc << std::endl;
218 return;
219 }
220 }
221}
222
223void ExecCLITool::parentReadOutput()
224{
225 // parent - resulting blob comes in on stdoutpipe[0]
226 unsigned int totalRead = 0;
227 char buffer[kReadBufSize];
228
229 for (;;)
230 {
231 int thisRead = read(stdoutpipe[0], buffer, kReadBufSize);
232 if (thisRead < 0)
233 {
234 if (errno==EINTR) // try some more
235 continue;
236// std::cerr << "abnormal read end:" << errno << std::endl;
237 break;
238 }
239 if (thisRead == 0) // normal termination
240 {
241 dataLength = totalRead;
242// std::cerr << "Normal read end" << std::endl;
243 break;
244 }
245
246 // Resize dataRead if necessary
247 if (kReadBufSize < (totalRead + (unsigned int)thisRead))
248 {
249 uint32 newLen = dataLength + kReadBufSize;
250 dataRead = (char *)realloc(dataRead, newLen);
251 dataLength = newLen;
252 }
253
254 // Append the data to dataRead
255 memmove(dataRead + totalRead, buffer, thisRead);
256 totalRead += thisRead;
257 }
258 close(stdoutpipe[0]);
259
260}
261
262void ExecCLITool::parentWriteInput()
263{
264 if (dataToWriteLength>0)
265 {
266 int bytesWritten = write(stdinpipe[1],dataToWrite,dataToWriteLength);
267 if (bytesWritten < 0)
268 UnixError::throwMe();
269 }
270 close(stdinpipe[1]);
271}
272
273void ExecCLITool::closeAllPipes()
274{
275 for (int ix=0;ix<2;ix++)
276 if (stdoutpipe[ix])
277 {
278 close(stdoutpipe[ix]);
279 stdoutpipe[ix]=0;
280 }
281
282 for (int ix=0;ix<2;ix++)
283 if (stdinpipe[ix])
284 {
285 close(stdinpipe[ix]);
286 stdinpipe[ix]=0;
287 }
288}
289
290#pragma mark -------------------- VAArgList implementation --------------------
291
292int VAArgList::set(const char *path,va_list params)
293{
294 va_list params2;
295 va_copy(params2, params);
296
297 // Count up the number of arguments
298 int nn = 1;
299 while (va_arg(params,const char *) != NULL)
300 nn++;
301 argn = nn;
302 argv = (ArgvArgPtr *)malloc((nn + 1) * sizeof(*argv));
303 if (argv == NULL)
304 return 0;
305
306 nn = 1;
307 argv[0]=path;
308 while ((argv[nn]=va_arg(params2,const char *)) != NULL)
309 nn++;
310 mSet = true;
311 return 0;
312}
313