]> git.saurik.com Git - apple/security.git/blob - Keychain/ExecCLITool.cpp
Security-176.tar.gz
[apple/security.git] / Keychain / ExecCLITool.cpp
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
9 ExecCLITool::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
15 ExecCLITool::~ExecCLITool()
16 {
17 if (dataRead)
18 free(dataRead);
19 reset();
20 }
21
22 int 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
60 void 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
73 void 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
88 void 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
125 void 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
144 void 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
182 void 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
223 void 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
262 void 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
273 void 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
292 int 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