source: TI05-delivery/trunk/src/python_ext/bbftpd.c @ 1141

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/trunk/src/python_ext/bbftpd.c@1141
Revision 1141, 9.5 KB checked in by spascoe, 14 years ago (diff)

There are various changes here.

The server now calls python for authorisation. This is untested, although the test suite passes. More flesh on delivery.server and it's now used in the
test suite. The server now logs python exceptions to syslog from within C code.

Line 
1/**
2 * NDG python embedded bbftp daemon module.
3 *
4 * @author Stephen Pascoe
5 *
6 * Copyright (C) 2006 CCLRC & NERC
7 *
8 * This software may be distributed under the terms of the Q Public Licence, version 1.0 or later.
9 *
10 */
11
12#include <Python.h>
13#include <ndg.h>
14
15extern char **environ;
16/*
17 * variables and prototypes from bbftpd source
18 */
19
20extern int be_daemon;
21extern int newcontrolport;
22
23int bbftpd_main(int argc, char **argv, char **envp);
24int ndg_message_send(char *buffer, int buffertosendlength, char *logmessage) ;
25int ndg_message_recv(char **buffer, int *length, char *logmessage) ;
26
27/*
28 * Static variables.
29 */
30static PyObject *authHandler = NULL;
31static PyObject *authzHandler = NULL;
32
33static void ndg_pyerr_to_logmessage(char *logmessage, char *prefix);
34
35/*---------------------------------------------------------------------------------------------------
36 * The following functions interact with bbftpd_private_user.  They are placed here to separate
37 * python and bbftp APIs.
38 *
39 */
40
41/**
42 * Copy the python exception message to a bbftp logmessage.
43 *
44 * If the python error indicator is set it will be cleared.
45 * If the python error indicator is not set the empty string will be copied to \a logmessage.
46 * The exception message is truncated if it exceeds \c NDG_MAX_LOGMESSAGE.
47 *
48 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
49 *     for storing an error message when returning -1.
50 * @param prefix a string to be prefixed to the logmessage.
51 */
52static void ndg_pyerr_to_logmessage(char *logmessage, char *prefix) {
53  PyObject *ptype, *pvalue, *ptraceback, *obj;
54  char *exname, *exvalue;
55
56  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
57 
58  if ((obj = PyObject_GetAttrString(ptype, "__name__")) == NULL) goto error;
59  exname = PyString_AsString(obj); Py_DECREF(obj);
60  if (exname == NULL) goto error;
61
62  if ((obj = PyObject_Str(pvalue)) == NULL) goto error;
63  exvalue = PyString_AsString(obj); Py_DECREF(obj);
64  if (exvalue == NULL) goto error;
65
66  snprintf(logmessage, NDG_MAX_LOGMESSAGE, "%s: %s: %s", prefix, exname, exvalue);
67  return;
68 
69 error:
70  sprintf("ndg_pyerr_to_logmessage: internal python error", logmessage);
71  return;
72}
73
74/**
75 * Make a callback bbftpd->python to handle authentication.
76 *
77 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
78 *     for storing an error message when returning -1.
79 * @return 0 on success, -1 on failure
80 */
81int ndg_auth(char *logmessage) {
82
83  if (authzHandler != NULL) {
84    sprintf(logmessage, "ndg_auth: authzHandler already present");
85    return -1;
86  }
87 
88  if (authHandler == NULL) {
89    sprintf(logmessage, "ndg_auth: no authHandler set");
90    return -1;
91  }
92
93  if ((authzHandler = PyObject_CallMethod(authHandler, "authorise", "")) == NULL) {
94    ndg_pyerr_to_logmessage(logmessage, "ndg_auth");
95    return -1;
96  }
97
98  /* If authHandler.authorise() returned None then authorisation failed.  */
99  if (authzHandler == Py_None) {
100    sprintf(logmessage, "ndg_auth: python authorisation failed");
101    return -1;
102  }
103
104  return 0;
105}
106
107/**
108 * retrieve the username from authzHandler.
109 *
110 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
111 *     for storing an error message when returning NULL.
112 * @return pointer to the username or NULL on failure
113 */
114char *ndg_getusername(char *logmessage) {
115  char *username;
116  PyObject *str_obj;
117
118  if (authzHandler == NULL) {
119    sprintf(logmessage, "bbftpd_private_auth_getusername: no authzHandler set");
120    return NULL;
121  }
122
123  if ((str_obj = PyObject_GetAttrString(authzHandler, "username")) == NULL) {
124    ndg_pyerr_to_logmessage(logmessage, "ndg_getusername");
125    return NULL;
126  }
127
128  if ((username = PyString_AsString(str_obj)) == NULL) {
129    ndg_pyerr_to_logmessage(logmessage, "ndg_getusername");
130    Py_DECREF(str_obj);
131    return NULL;
132  }
133
134  return username;
135}
136
137
138/**
139 * Make a callback to do authorisation of a control command.
140 *
141 * @see bbftpd_private_authz_control()
142 */
143int ndg_authz_control(int msgcode, int transferoption, char *path, char *logmessage) {
144  PyObject *ret_obj;
145  int ret;
146
147  if (authzHandler == NULL) {
148    sprintf(logmessage, "ndg_authz_control: no authzHandler set");
149    return -1;
150  }
151 
152  if ((ret_obj = PyObject_CallMethod(authzHandler, "authzControl", "iis", msgcode, transferoption, path)) == NULL) {
153    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_control");
154    return -1;
155  }
156 
157  if (!PyInt_Check(ret_obj)) {
158    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_control");
159    Py_DECREF(ret_obj);
160    return -1;
161  }
162
163  ret = (int)PyInt_AsLong(ret_obj);
164  Py_DECREF(ret_obj);
165  return ret;
166}
167
168/**
169 * Make a callback to do authorisation of a retr command.
170 *
171 * @see bbftpd_private_authz_store()
172 */
173int ndg_authz_retr(char *path, char *logmessage) {
174  PyObject *ret_obj;
175  int ret;
176
177  if (authzHandler == NULL) {
178    sprintf(logmessage, "ndg_authz_retr: no authzHandler set");
179    return -1;
180  }
181 
182  if ((ret_obj = PyObject_CallMethod(authzHandler, "authzRetr", "s", path)) == NULL) {
183    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_retr");
184    return -1;
185  }
186 
187  if (!PyInt_Check(ret_obj)) {
188    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_retr");
189    Py_DECREF(ret_obj);
190    return -1;
191  }
192
193  ret = (int)PyInt_AsLong(ret_obj);
194  Py_DECREF(ret_obj);
195  return ret;
196}
197
198
199/**
200 * Make a callback to do authorisation of a store command.
201 *
202 * @see bbftpd_private_authz_store()
203 */
204int ndg_authz_store(char *path, char *logmessage) {
205  PyObject *ret_obj;
206  int ret;
207
208  if (authzHandler == NULL) {
209    sprintf(logmessage, "ndg_authz_store: no authzHandler set");
210    return -1;
211  }
212 
213  if ((ret_obj = PyObject_CallMethod(authzHandler, "authzStore", "s", path)) == NULL) {
214    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_store");
215    return -1;
216  }
217 
218  if (!PyInt_Check(ret_obj)) {
219    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_store");
220    Py_DECREF(ret_obj);
221    return -1;
222  }
223
224  ret = (int)PyInt_AsLong(ret_obj);
225  Py_DECREF(ret_obj);
226  return ret;
227}
228 
229
230     
231/*-------------------------------------------------------------------------------------------------
232 * Functions exported to python
233 *
234 */
235
236/* send(string) */
237static PyObject *bbftpd_send(PyObject *self, PyObject *args) {
238  char *buffer, logmessage[1024];
239  int len;
240
241  if (!PyArg_ParseTuple(args, "s#", &buffer, &len)) {
242    return NULL;
243  }
244
245  if (authHandler == NULL) {
246    PyErr_SetString(PyExc_RuntimeError, "Must be called within a bbftpd callback");
247    return NULL;
248  }
249
250  if (ndg_message_send(buffer, len, logmessage) == -1) {
251    PyErr_SetString(PyExc_IOError, logmessage);
252    return NULL;
253  }
254
255  Py_RETURN_NONE;
256}
257
258/* recv(length) -> string */
259static PyObject *bbftpd_recv(PyObject *self, PyObject *args) {
260  char *buffer, logmessage[1024];
261  int len;
262  PyObject *ret;
263
264  if (!PyArg_ParseTuple(args, "")) {
265    return NULL;
266  }
267
268  if (authHandler == NULL) {
269    PyErr_SetString(PyExc_RuntimeError, "Must be called within a bbftpd callback");
270    return NULL;
271  }
272
273  if (ndg_message_recv(&buffer, &len, logmessage) == -1) {
274    PyErr_SetString(PyExc_IOError, logmessage);
275    free(buffer);
276    return NULL;
277  }
278
279  ret = Py_BuildValue("s#", buffer, len);
280  free(buffer);
281  return ret;
282}
283
284
285
286/**
287 * Main entry point for the python module.
288 */
289static PyObject *bbftpd_run(PyObject *self, PyObject *args) {
290  int argc, i;
291  char **argv, **arg_p;
292  int pid;
293  char preargs[2][20] = { "bbftpd_embedded", "-b" };
294  PyObject *daemon_args, *item;
295
296  if (!PyArg_ParseTuple(args, "OO", &authHandler, &daemon_args)) {
297    return NULL;
298  }
299  Py_INCREF(authHandler);
300  Py_INCREF(daemon_args);
301
302  /*
303   * Convert arguments into a standard argv sequence.
304   */
305  argc = PySequence_Size(daemon_args);
306  if ((argv = (char**)malloc((argc + 2)*sizeof(char*))) == NULL) {
307    PyErr_SetString(PyExc_MemoryError, "malloc failed");
308    Py_DECREF(daemon_args);
309    return NULL;
310  }
311
312  // Add fixed arguments to daemon's argv
313  arg_p = argv;
314  *arg_p = preargs[0]; arg_p++;
315  *arg_p = preargs[1]; arg_p++;
316
317  for (i=0; i<argc; i++) {
318    if ((item = PySequence_GetItem(daemon_args, i)) == NULL) {
319      Py_DECREF(authHandler);
320      Py_DECREF(daemon_args);
321      return NULL;
322    }
323    if ((*arg_p = PyString_AsString(item)) == NULL) {
324      free(argv);
325      Py_DECREF(item);
326      Py_DECREF(authHandler);
327      Py_DECREF(daemon_args);
328      return NULL;
329    }
330    arg_p++;
331    Py_DECREF(item);
332  }
333
334  /*
335   * We must reset some global variables so that bbftpd_run can be executed multiple times.
336   */
337  be_daemon = 0;
338
339  pid = bbftpd_main(argc+2, argv, environ);
340
341  free(argv);
342  Py_DECREF(authHandler); authHandler = NULL;
343  Py_DECREF(daemon_args);
344
345  return Py_BuildValue("i", pid);
346}
347
348static PyMethodDef BbftpdMethods[] = {
349  {
350    "run", bbftpd_run, METH_VARARGS, 
351    "Execute the bbftpd server.\n"
352    "\n"
353    "Forks an embedded bbftpd process as if it were executed\n"
354    "with the command line $ bbftpd -b <args>."
355    "\n"
356    "@param args: command line arguments to pass to the server\n"
357    "@return: the PID of the server process\n"
358  },
359  {
360    "send", bbftpd_send, METH_VARARGS,
361    "Send an authentication message to the client.\n"
362    "\n"
363    "Equivilent to bbftpd_private_send() C call.\n"
364    "\n"
365    "@param buffer: A string containing the message\n"
366    "@raise IOError: if bbftpd_private_send() call fails\n"
367  },
368  {
369    "recv", bbftpd_recv, METH_VARARGS,
370    "Receive an authentication message from the client.\n"
371    "\n"
372    "Equivilent to bbftpd_private_recv() C call.\n"
373    "\n"
374    "@param len: The length of message to expect\n"
375    "@return: A string containing the message\n"
376    "@raise IOError: if bbftpd_private_recv() call fails\n"
377  },
378  {NULL, NULL, 0, NULL}
379};
380
381PyMODINIT_FUNC initbbftpd(void) {
382  (void) Py_InitModule("bbftpd", BbftpdMethods);
383}
384
Note: See TracBrowser for help on using the repository browser.