1 : /*
2 : * Copyright 2010 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can
4 : * be found in the LICENSE file.
5 : */
6 :
7 : #if NACL_WINDOWS
8 : # include "io.h"
9 : # include "fcntl.h"
10 : /* The version of Python we pull in for Windows includes python26.lib
11 : but not python26_d.lib, which pyconfig.h insists on linking against
12 : when _DEBUG is defined. We work around this by undefining _DEBUG. */
13 : # undef _DEBUG
14 : #endif
15 :
16 : #include <Python.h>
17 :
18 : #include "native_client/src/trusted/desc/nacl_desc_base.h"
19 : #include "native_client/src/trusted/desc/nacl_desc_imc.h"
20 : #include "native_client/src/trusted/desc/nacl_desc_io.h"
21 : #include "native_client/src/trusted/desc/nrd_all_modules.h"
22 : #include "native_client/src/trusted/desc/nrd_xfer.h"
23 :
24 :
25 : static PyTypeObject nacldesc_type;
26 :
27 : struct PyDesc {
28 : PyObject_HEAD
29 : struct NaClDesc *nacldesc;
30 : };
31 :
32 :
33 : /* Internal helper functions */
34 :
35 : /* Create a Python wrapper for a NaClDesc. */
36 75 : static PyObject *NaClToPyDesc(struct NaClDesc *nacldesc) {
37 75 : struct PyDesc *pydesc = PyObject_New(struct PyDesc, &nacldesc_type);
38 75 : if (pydesc == NULL)
39 0 : return NULL;
40 75 : pydesc->nacldesc = nacldesc;
41 75 : return (PyObject *) pydesc;
42 : }
43 :
44 : /* Unwrap the Python wrapper to get a NaClDesc.
45 : This returns a *borrowed* reference. */
46 14 : static struct NaClDesc *PyToNaClDesc(PyObject *obj) {
47 14 : if (PyObject_TypeCheck(obj, &nacldesc_type)) {
48 11 : struct PyDesc *pydesc = (struct PyDesc *) obj;
49 11 : return pydesc->nacldesc;
50 : } else {
51 3 : PyErr_SetString(PyExc_TypeError, "Argument is not a NaClDesc");
52 3 : return NULL;
53 : }
54 : }
55 :
56 :
57 : /* Module-level functions */
58 :
59 12 : static PyObject *PyDescImcMakeBoundSock(PyObject *self, PyObject *args) {
60 : struct NaClDesc *pair[2];
61 : UNREFERENCED_PARAMETER(self);
62 : UNREFERENCED_PARAMETER(args);
63 12 : if (NaClCommonDescMakeBoundSock(pair) != 0) {
64 0 : PyErr_SetString(PyExc_Exception, "imc_makeboundsock() failed");
65 0 : return NULL;
66 : }
67 12 : return Py_BuildValue("NN", NaClToPyDesc(pair[0]),
68 : NaClToPyDesc(pair[1]));
69 : }
70 :
71 : /* Python's libraries are not consistent in how or whether they wrap
72 : Windows handles.
73 :
74 : The modules _subprocess and win32api each have their own wrappers
75 : which automagically convert to integers, but the constructors are
76 : not exposed. (_subprocess is a private, Windows-only module for
77 : use by subprocess.py.)
78 :
79 : msvcrt.get_osfhandle() just returns an integer. This seems like
80 : the simplest approach, and it fits with how Python handles Unix
81 : file descriptors, so we just use integers below. Windows handles
82 : are really just integer IDs, even if HANDLE is a typedef for
83 : void*. */
84 :
85 2 : static PyObject *PyDescOsSocketpair(PyObject *self, PyObject *args) {
86 : NaClHandle pair[2];
87 : UNREFERENCED_PARAMETER(self);
88 : UNREFERENCED_PARAMETER(args);
89 2 : if (NaClSocketPair(pair) != 0) {
90 0 : PyErr_SetString(PyExc_Exception, "NaClSocketPair() failed");
91 0 : return NULL;
92 : }
93 2 : return Py_BuildValue("kk", (unsigned long) pair[0],
94 : (unsigned long) pair[1]);
95 : }
96 :
97 4 : static PyObject *PyDescFromOsSocket(PyObject *self, PyObject *args) {
98 : unsigned long socket;
99 : struct NaClDescImcDesc *nacldesc;
100 : UNREFERENCED_PARAMETER(self);
101 :
102 4 : if (!PyArg_ParseTuple(args, "k", &socket))
103 0 : return NULL;
104 4 : nacldesc = malloc(sizeof(*nacldesc));
105 4 : if (nacldesc == NULL) {
106 0 : PyErr_SetString(PyExc_MemoryError, "Failed to allocate NaClDesc");
107 0 : return NULL;
108 : }
109 4 : if (!NaClDescImcDescCtor(nacldesc, (NaClHandle) socket)) {
110 0 : free(nacldesc);
111 0 : PyErr_SetString(PyExc_MemoryError, "Failed to allocate NaClDesc");
112 0 : return NULL;
113 : }
114 4 : return NaClToPyDesc(&nacldesc->base.base);
115 : }
116 :
117 1 : static PyObject *PyDescFromOsFileDescriptor(PyObject *self, PyObject *args) {
118 : unsigned long fd;
119 : struct NaClDescIoDesc *nacldesc;
120 : UNREFERENCED_PARAMETER(self);
121 :
122 1 : if (!PyArg_ParseTuple(args, "k", &fd))
123 0 : return NULL;
124 : #if NACL_WINDOWS
125 : /* Wrap the Windows handle to get a crt-emulated file descriptor.
126 : The FD is specific to the instance of crt that we are linked to,
127 : which is the same instance used by the NaClDesc library. */
128 : fd = _open_osfhandle(fd, _O_RDWR | _O_BINARY);
129 : #endif
130 : /* The "mode" argument is only used for a sanity check and is not
131 : stored, so passing 0 works. */
132 1 : nacldesc = NaClDescIoDescMake(NaClHostDescPosixMake(fd, /* mode= */ 0));
133 1 : if (nacldesc == NULL) {
134 0 : PyErr_SetString(PyExc_MemoryError, "Failed to create NaClDesc wrapper");
135 0 : return NULL;
136 : }
137 1 : return NaClToPyDesc(&nacldesc->base);
138 : }
139 :
140 :
141 : /* Methods */
142 :
143 75 : static void PyDescDealloc(struct PyDesc *self) {
144 75 : NaClDescUnref(self->nacldesc);
145 75 : }
146 :
147 38 : static struct NaClDescVtbl *GetVtbl(struct NaClDesc *nacldesc) {
148 38 : return (struct NaClDescVtbl *) nacldesc->base.vtbl;
149 : }
150 :
151 19 : static PyObject *PyDescImcConnect(PyObject *self, PyObject *args) {
152 19 : struct NaClDesc *self_desc = ((struct PyDesc *) self)->nacldesc;
153 : int errval;
154 : struct NaClDesc *result_desc;
155 : UNREFERENCED_PARAMETER(args);
156 19 : Py_BEGIN_ALLOW_THREADS;
157 19 : errval = GetVtbl(self_desc)->ConnectAddr(self_desc, &result_desc);
158 19 : Py_END_ALLOW_THREADS;
159 19 : if (errval == 0) {
160 18 : return NaClToPyDesc(result_desc);
161 : } else {
162 : /* TODO(mseaborn): Record errno value in exception. */
163 1 : PyErr_SetString(PyExc_Exception, "imc_connect() failed");
164 1 : return NULL;
165 : }
166 : }
167 :
168 19 : static PyObject *PyDescImcAccept(PyObject *self, PyObject *args) {
169 19 : struct NaClDesc *self_desc = ((struct PyDesc *) self)->nacldesc;
170 : int errval;
171 : struct NaClDesc *result_desc;
172 : UNREFERENCED_PARAMETER(args);
173 19 : Py_BEGIN_ALLOW_THREADS;
174 19 : errval = GetVtbl(self_desc)->AcceptConn(self_desc, &result_desc);
175 19 : Py_END_ALLOW_THREADS;
176 19 : if (errval == 0) {
177 18 : return NaClToPyDesc(result_desc);
178 : } else {
179 : /* TODO(mseaborn): Record errno value in exception. */
180 1 : PyErr_SetString(PyExc_Exception, "imc_accept() failed");
181 1 : return NULL;
182 : }
183 : }
184 :
185 30 : static PyObject *PyDescImcSendmsg(PyObject *self, PyObject *args) {
186 30 : struct NaClDesc *self_desc = ((struct PyDesc *) self)->nacldesc;
187 : char *data;
188 : int data_size;
189 : Py_ssize_t desc_array_len;
190 : PyObject *pydesc_array;
191 : struct NaClDesc *nacldesc_array[NACL_ABI_IMC_DESC_MAX];
192 : struct NaClImcMsgIoVec iov;
193 : struct NaClImcTypedMsgHdr message;
194 : ssize_t sent;
195 : int i;
196 :
197 30 : if (!PyArg_ParseTuple(args, "s#O", &data, &data_size, &pydesc_array) ||
198 : !PyTuple_Check(pydesc_array))
199 0 : return NULL;
200 30 : desc_array_len = PyTuple_GET_SIZE(pydesc_array);
201 30 : if (desc_array_len > NACL_ABI_IMC_DESC_MAX) {
202 : /* Checking the size early saves the hassle of a dynamic allocation.
203 : TODO(mseaborn): Record errno value in exception as NACL_ABI_EINVAL. */
204 1 : PyErr_SetString(PyExc_Exception,
205 : "imc_sendmsg() failed: Too many descriptor arguments");
206 1 : return NULL;
207 : }
208 40 : for (i = 0; i < desc_array_len; i++) {
209 14 : PyObject *obj = PyTuple_GET_ITEM(pydesc_array, i);
210 : /* PyToNaClDesc() returns a borrowed reference. Because
211 : pydesc_array is a tuple, and therefore immutable, and each of
212 : its PyDesc members is also immutable, this is safe. This saves
213 : us having to increment and decrement the refcounts across the
214 : NaClImcSendTypedMessage() call below. */
215 14 : struct NaClDesc *nacldesc = PyToNaClDesc(obj);
216 14 : if (nacldesc == NULL)
217 3 : return NULL;
218 11 : nacldesc_array[i] = nacldesc;
219 : }
220 26 : iov.base = data;
221 26 : iov.length = data_size;
222 26 : message.iov = &iov;
223 26 : message.iov_length = 1;
224 26 : message.ndescv = nacldesc_array;
225 26 : message.ndesc_length = (nacl_abi_size_t) desc_array_len;
226 26 : message.flags = 0;
227 26 : Py_BEGIN_ALLOW_THREADS;
228 26 : sent = NaClImcSendTypedMessage(self_desc, &message, 0);
229 26 : Py_END_ALLOW_THREADS;
230 26 : if (sent >= 0) {
231 26 : return Py_BuildValue("i", sent);
232 : } else {
233 : /* TODO(mseaborn): Record errno value in exception. */
234 0 : PyErr_SetString(PyExc_Exception, "imc_sendmsg() failed");
235 0 : return NULL;
236 : }
237 : }
238 :
239 27 : static PyObject *PyDescImcRecvmsg(PyObject *self, PyObject *args) {
240 27 : struct NaClDesc *self_desc = ((struct PyDesc *) self)->nacldesc;
241 : int buffer_size;
242 : void *buffer;
243 : struct NaClDesc *nacldesc_array[NACL_ABI_IMC_DESC_MAX];
244 : struct NaClImcMsgIoVec iov;
245 : struct NaClImcTypedMsgHdr message;
246 : ssize_t received;
247 : unsigned int i;
248 : PyObject *descs_tuple;
249 : PyObject *result;
250 :
251 27 : if (!PyArg_ParseTuple(args, "i", &buffer_size))
252 0 : return NULL;
253 27 : buffer = malloc(buffer_size);
254 27 : if (buffer == NULL) {
255 0 : PyErr_SetString(PyExc_MemoryError,
256 : "imc_recvmsg(): Failed to allocate buffer");
257 0 : return NULL;
258 : }
259 27 : iov.base = buffer;
260 27 : iov.length = buffer_size;
261 27 : message.iov = &iov;
262 27 : message.iov_length = 1;
263 27 : message.ndescv = nacldesc_array;
264 27 : message.ndesc_length = NACL_ABI_IMC_DESC_MAX;
265 27 : message.flags = 0;
266 27 : Py_BEGIN_ALLOW_THREADS;
267 : /* No quota management from these bindings. */
268 27 : received = NaClImcRecvTypedMessage(self_desc, &message, 0, NULL);
269 27 : Py_END_ALLOW_THREADS;
270 :
271 27 : if (received < 0) {
272 1 : free(buffer);
273 : /* TODO(mseaborn): Record errno value in exception. */
274 1 : PyErr_SetString(PyExc_Exception, "imc_recvmsg() failed");
275 1 : return NULL;
276 : }
277 :
278 26 : descs_tuple = PyTuple_New(message.ndesc_length);
279 26 : if (descs_tuple == NULL) {
280 0 : free(buffer);
281 0 : return NULL;
282 : }
283 36 : for (i = 0; i < message.ndesc_length; i++) {
284 10 : PyObject *wrapper = NaClToPyDesc(message.ndescv[i]);
285 10 : if (wrapper == NULL) {
286 : /* On error, free the remaining received descriptors. The
287 : already-processed descriptors are freed when unreffing
288 : descs_tuple. Unreffing a partially-initialised tuple is safe
289 : because the fields are NULL-initialised. */
290 : unsigned int j;
291 0 : for (j = i; j < message.ndesc_length; j++) {
292 0 : NaClDescUnref(message.ndescv[j]);
293 : }
294 0 : free(buffer);
295 0 : Py_DECREF(descs_tuple);
296 0 : return NULL;
297 : }
298 10 : PyTuple_SET_ITEM(descs_tuple, i, wrapper);
299 : }
300 :
301 26 : result = Py_BuildValue("s#N", buffer, received, descs_tuple);
302 26 : free(buffer);
303 26 : return result;
304 : }
305 :
306 : static PyMethodDef nacldesc_methods[] = {
307 : { "imc_connect", (PyCFunction) PyDescImcConnect, METH_NOARGS,
308 : "imc_connect() -> socket\n\n"
309 : "Make a connection to a NaCl IMC SocketAddress."
310 : },
311 : { "imc_accept", (PyCFunction) PyDescImcAccept, METH_NOARGS,
312 : "imc_accept() -> socket\n\n"
313 : "Accept a connection on a NaCl IMC BoundSocket."
314 : },
315 : { "imc_sendmsg", (PyCFunction) PyDescImcSendmsg, METH_VARARGS,
316 : "imc_sendmsg(data, desc_tuple) -> byte_count\n\n"
317 : "Send a message on a NaCl IMC connected socket. "
318 : "Returns the number of bytes sent."
319 : },
320 : { "imc_recvmsg", (PyCFunction) PyDescImcRecvmsg, METH_VARARGS,
321 : "imc_recvmsg(buffer_size) -> (data, desc_tuple)\n\n"
322 : "Receive a message on a NaCl IMC connected socket."
323 : },
324 : { NULL, NULL, 0, NULL }
325 : };
326 :
327 : static PyTypeObject nacldesc_type = {
328 : PyObject_HEAD_INIT(NULL)
329 : 0, /* ob_size */
330 : "naclimc.NaClDesc", /* tp_name */
331 : sizeof(struct PyDesc), /* tp_basicsize */
332 : 0, /* tp_itemsize */
333 : (destructor) PyDescDealloc, /* tp_dealloc */
334 : NULL, /* tp_print */
335 : NULL, /* tp_getattr */
336 : NULL, /* tp_setattr */
337 : NULL, /* tp_compare */
338 : NULL, /* tp_repr */
339 : NULL, /* tp_as_number */
340 : NULL, /* tp_as_sequence */
341 : NULL, /* tp_as_mapping */
342 : NULL, /* tp_hash */
343 : NULL, /* tp_call */
344 : NULL, /* tp_str */
345 : NULL, /* tp_getattro */
346 : NULL, /* tp_setattro */
347 : NULL, /* tp_as_buffer */
348 : Py_TPFLAGS_DEFAULT, /* tp_flags */
349 : "NaCl descriptor", /* tp_doc */
350 : NULL, /* tp_traverse */
351 : NULL, /* tp_clear */
352 : NULL, /* tp_richcompare */
353 : 0, /* tp_weaklistoffset */
354 : NULL, /* tp_iter */
355 : NULL, /* tp_iternext */
356 : nacldesc_methods, /* tp_methods */
357 : NULL, /* tp_members */
358 : NULL, /* tp_getset */
359 : NULL, /* tp_base */
360 : NULL, /* tp_dict */
361 : NULL, /* tp_descr_get */
362 : NULL, /* tp_descr_set */
363 : 0, /* tp_dictoffset */
364 : NULL, /* tp_init */
365 : NULL, /* tp_alloc */
366 : NULL, /* tp_new */
367 : };
368 :
369 : static PyMethodDef module_methods[] = {
370 : { "imc_makeboundsock", PyDescImcMakeBoundSock, METH_NOARGS,
371 : "imc_makeboundsock() -> (boundsock, sockaddr)\n\n"
372 : "Creates a new socket pair. boundsock can be used with imc_accept(), "
373 : "while sockaddr can be used with imc_connect()."
374 : },
375 : { "os_socketpair", PyDescOsSocketpair, METH_NOARGS,
376 : "os_socketpair() -> (fd1, fd2)\n\n"
377 : "Returns a pair of host-OS sockets. "
378 : "On Unix, these are file descriptors. On Windows, these are handles. "
379 : "This is for setting up a connection to a newly-spawned subprocess."
380 : },
381 : { "from_os_socket", PyDescFromOsSocket, METH_VARARGS,
382 : "from_os_socket(fd) -> socket\n\n"
383 : "Takes a host-OS socket and returns a connected socket NaClDesc. "
384 : "This takes ownership of the host-OS socket. "
385 : "This is for setting up a connection inside or to a "
386 : "newly-spawned subprocess."
387 : },
388 : { "from_os_file_descriptor", PyDescFromOsFileDescriptor, METH_VARARGS,
389 : "from_os_file_descriptor(fd) -> nacldesc\n\n"
390 : "Takes a host-OS file handle and returns a NaClDesc wrapper for it. "
391 : "This takes ownership of the host-OS file handle.\n\n"
392 : "On Unix, this takes a file descriptor.\n\n"
393 : "On Windows, this takes a Windows handle, not a file descriptor. "
394 : "This is because Windows file descriptors are emulated by crt (the "
395 : "C runtime library), and python.exe can be linked to a different crt "
396 : "instance from the Python extension DLL, and so the two can be using "
397 : "separate file descriptor tables."
398 : },
399 : { NULL, NULL, 0, NULL }
400 : };
401 :
402 : /* Note that although this does not return an error code, Python's
403 : importdl.c checks PyErr_Occurred(), so our early returns should
404 : raise exceptions correctly. */
405 2 : void initnaclimc(void) {
406 : PyObject *module;
407 :
408 2 : if (PyType_Ready(&nacldesc_type) < 0)
409 0 : return;
410 2 : module = Py_InitModule3("naclimc", module_methods, "NaCl IMC");
411 2 : if (module == NULL)
412 0 : return;
413 2 : if (PyModule_AddIntConstant(module, "DESC_MAX", NACL_ABI_IMC_DESC_MAX) != 0)
414 0 : return;
415 :
416 2 : NaClNrdAllModulesInit();
417 : }
|