source: TI05-delivery/trunk/components/server/ext/bbftpd.c @ 1541

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/trunk/components/server/ext/bbftpd.c@1541
Revision 1541, 13.6 KB checked in by spascoe, 13 years ago (diff)

Major directory structure reorganisation.

The python source tree is now divided into 3 components: common,
server and client. Each component has it's own setup.py.

Do "python setup.py bdist_egg" to create 3 eggs for each component.

Some things in the distribution are now out of sync (e.g. documentation) but
the test cases work (I havn't tried the remote test cases).

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
13#include <Python.h>
14#include <stdlib.h>
15#include <stdarg.h>
16#include <ndg.h>
17#include "util.h"
18#include <structures.h>
19#include <bbftpd_private_log.h>
20
21extern char **environ;
22/*
23 * variables and prototypes from bbftpd source
24 */
25
26extern int be_daemon;
27extern int newcontrolport;
28
29int bbftpd_main(int argc, char **argv, char **envp);
30int ndg_message_send(char *buffer, int buffertosendlength, char *logmessage) ;
31int ndg_message_recv(char **buffer, int *length, char *logmessage) ;
32
33/*
34 * Static variables.
35 */
36static PyObject *authHandler = NULL;
37static PyObject *authzHandler = NULL;
38static PyObject *serverLogger = NULL;
39static PyObject *bbftpdModule = NULL;
40
41
42/**
43 * Get a logger object from the logging module.
44 *
45 * @param loggerName the name of the logger passed to logging.getLogger()
46 * @return a new reference to the logger object
47 */
48static PyObject *getLogger(char *loggerName) {
49  PyObject *logging, *logger;
50
51  if ((logging = PyImport_ImportModule("logging")) == NULL) {
52    return NULL;
53  }
54  if ((logger = PyObject_CallMethod(logging, "getLogger", "s", loggerName)) == NULL) {
55    return NULL;
56  }
57
58  return logger;
59}
60
61
62/*---------------------------------------------------------------------------------------------------
63 * The following functions interact with bbftpd_private_user.  They are placed here to separate
64 * python and bbftp APIs.
65 *
66 */
67
68
69
70/**
71 * Make a callback bbftpd->python to handle authentication.
72 *
73 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
74 *     for storing an error message when returning -1.
75 * @return 0 on success, -1 on failure
76 */
77int ndg_auth(char *logmessage) {
78
79  if (authzHandler != NULL) {
80    sprintf(logmessage, "ndg_auth: authzHandler already present");
81    return -1;
82  }
83 
84  if (authHandler == NULL) {
85    sprintf(logmessage, "ndg_auth: no authHandler set");
86    return -1;
87  }
88
89  /** @todo exceptions of type delivery.server.AuthenticationFailure should be logged as
90   *      failure rather than error.
91   */
92  if ((authzHandler = PyObject_CallMethod(authHandler, "authenticate", NULL)) == NULL) {
93    ndg_pyerr_to_logmessage(logmessage, "ndg_auth");
94    return -1;
95  }
96
97  /* Authorisation is considered failed if the return value isn't True.  */
98  if (!PyObject_IsTrue(authzHandler)) {
99    sprintf(logmessage, "ndg_auth: python authentication failed");
100    return -1;
101  }
102
103  return 0;
104}
105
106/**
107 * retrieve the username from authzHandler.
108 *
109 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
110 *     for storing an error message when returning NULL.
111 * @return pointer to the username or NULL on failure
112 */
113char *ndg_getusername(char *logmessage) {
114  char *username;
115  PyObject *str_obj;
116
117  if (authzHandler == NULL) {
118    sprintf(logmessage, "bbftpd_private_auth_getusername: no authzHandler set");
119    return NULL;
120  }
121
122  if ((str_obj = PyObject_GetAttrString(authzHandler, "username")) == NULL) {
123    ndg_pyerr_to_logmessage(logmessage, "ndg_getusername");
124    return NULL;
125  }
126
127  if ((username = PyString_AsString(str_obj)) == NULL) {
128    ndg_pyerr_to_logmessage(logmessage, "ndg_getusername");
129    Py_DECREF(str_obj);
130    return NULL;
131  }
132
133  return username;
134}
135
136
137/**
138 * Make a callback to do authorisation of a control command.
139 *
140 * @see bbftpd_private_authz_control()
141 */
142int ndg_authz_control(int msgcode, int transferoption, char *path, char *logmessage) {
143  PyObject *ret_obj;
144
145  if (authzHandler == NULL) {
146    sprintf(logmessage, "ndg_authz_control: no authzHandler set");
147    return -1;
148  }
149 
150  if ((ret_obj = PyObject_CallMethod(authzHandler, "authzControl", "iis", msgcode, transferoption, path)) == NULL) {
151    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_control");
152    return -1;
153  }
154 
155  /* Authorisation fails if ret_obj is false. */
156  if (!PyObject_IsTrue(ret_obj)) {
157    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_control");
158    Py_DECREF(ret_obj);
159    return -1;
160  }
161
162  return 0;
163}
164
165/**
166 * Make a callback to do authorisation of a retr command.
167 *
168 * @see bbftpd_private_authz_retr()
169 */
170int ndg_authz_retr(char *path, char *logmessage) {
171  PyObject *ret_obj;
172
173  if (authzHandler == NULL) {
174    sprintf(logmessage, "ndg_authz_retr: no authzHandler set");
175    return -1;
176  }
177 
178  if ((ret_obj = PyObject_CallMethod(authzHandler, "authzRetr", "s", path)) == NULL) {
179    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_retr");
180    return -1;
181  }
182 
183  /* Authorisation fails if ret_obj is false. */
184  if (!PyObject_IsTrue(ret_obj)) {
185    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_retr");
186    Py_DECREF(ret_obj);
187    return -1;
188  }
189
190  return 0;
191}
192
193
194/**
195 * Make a callback to do authorisation of a store command.
196 *
197 * @see bbftpd_private_authz_store()
198 */
199int ndg_authz_store(char *path, char *logmessage) {
200  PyObject *ret_obj;
201
202  if (authzHandler == NULL) {
203    sprintf(logmessage, "ndg_authz_store: no authzHandler set");
204    return -1;
205  }
206 
207  if ((ret_obj = PyObject_CallMethod(authzHandler, "authzStore", "s", path)) == NULL) {
208    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_store");
209    return -1;
210  }
211 
212  /* Authorisation fails if ret_obj is false. */
213  if (!PyObject_IsTrue(ret_obj)) {
214    ndg_pyerr_to_logmessage(logmessage, "ndg_authz_store");
215    Py_DECREF(ret_obj);
216    return -1;
217  }
218
219  return 0;
220}
221 
222/**
223 * Make a callback to log a message to the ndg.delivery.server logger.
224 *
225 * @param priority syslog-style priority
226 * @param format the message with printf-style format specifiers
227 * @param vargs argument list for inclusion in the message.
228 */
229void ndg_log(int priority, const char *format, va_list vargs) {
230  PyObject *message;
231  char *priority_method;
232
233  /* Just in case this is somehow called outsite pybbftpd_run() */
234  if (serverLogger == NULL) return;
235 
236  if ((message = PyString_FromFormatV(format, vargs)) == NULL) {
237    if ((message = PyString_FromString("ndg_log: PyString_FromFormatV error")) == NULL) {
238      /* Desperate measures! */
239      abort();
240    }
241  }
242
243  switch (priority) {
244  case LOG_DEBUG:
245    priority_method = "debug"; break;
246  case LOG_INFO:
247    priority_method = "info"; break;
248  case LOG_NOTICE:
249    priority_method = "info"; break;
250  case LOG_WARNING:
251    priority_method = "warning"; break;
252  case LOG_ERR:
253    priority_method = "error"; break;
254  default:
255    priority_method = "critical"; break;
256  }
257
258  if ((PyObject_CallMethod(serverLogger, priority_method, "O", message)) == NULL) {
259    /*!TODO: Work out how better to recover from this error */
260    abort();
261  }
262
263  Py_DECREF(message);
264  return;
265}
266
267/**
268 * Take action before a fork if pybbftp.before_fork_hook is callable.
269 *
270 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
271 *     for storing an error message when returning NULL.
272 * @return 0 on success or -1 on failure.
273 *
274 */
275int ndg_before_fork_hook(char *logmessage) {
276  PyObject *hook;
277
278  if ((hook = PyObject_GetAttrString(bbftpdModule, "before_fork_hook")) == NULL) {
279    PyErr_Clear();
280    return 0;
281  }
282  Py_INCREF(hook);
283
284  if (!PyCallable_Check(hook)) return 0;
285
286  if (PyObject_CallObject(hook, NULL) == NULL) {
287    ndg_pyerr_to_logmessage(logmessage, "ndg_before_fork_hook");
288    Py_DECREF(hook);
289    return -1;
290  }
291
292  Py_DECREF(hook);
293  return 0;
294}
295
296/**
297 * Take action after a fork if pybbftp.after_fork_hook is callable.
298 *
299 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
300 *     for storing an error message when returning NULL.
301 * @return 0 on success or -1 on failure.
302 *
303 */
304int ndg_after_fork_hook(char *logmessage) {
305  PyObject *hook;
306
307  if ((hook = PyObject_GetAttrString(bbftpdModule, "after_fork_hook")) == NULL) {
308    PyErr_Clear();
309    return 0;
310  }
311  Py_INCREF(hook);
312
313  if (!PyCallable_Check(hook)) return 0;
314
315  if (PyObject_CallObject(hook, NULL) == NULL) {
316    ndg_pyerr_to_logmessage(logmessage, "ndg_after_fork_hook");
317    Py_DECREF(hook);
318    return -1;
319  }
320
321  Py_DECREF(hook);
322  return 0;
323}
324
325     
326/*-------------------------------------------------------------------------------------------------
327 * Functions exported to python
328 *
329 */
330
331/* log message to bbftpd logger */
332static PyObject *pybbftpd_log(PyObject *self, PyObject *args) {
333  int priority;
334  char *message;
335
336  if (!PyArg_ParseTuple(args, "is", &priority, &message)) {
337    return NULL;
338  }
339
340  bbftpd_log(priority, message);
341
342  Py_RETURN_NONE;
343}
344
345/* send(string) */
346static PyObject *pybbftpd_send(PyObject *self, PyObject *args) {
347  char *buffer, logmessage[1024];
348  int len;
349
350  if (!PyArg_ParseTuple(args, "s#", &buffer, &len)) {
351    return NULL;
352  }
353
354  if (authHandler == NULL) {
355    PyErr_SetString(PyExc_RuntimeError, "Must be called within a bbftpd callback");
356    return NULL;
357  }
358
359  if (ndg_message_send(buffer, len, logmessage) == -1) {
360    PyErr_SetString(PyExc_IOError, logmessage);
361    return NULL;
362  }
363
364  Py_RETURN_NONE;
365}
366
367/* recv(length) -> string */
368static PyObject *pybbftpd_recv(PyObject *self, PyObject *args) {
369  char *buffer, logmessage[1024];
370  int len;
371  PyObject *ret;
372
373  if (!PyArg_ParseTuple(args, "")) {
374    return NULL;
375  }
376
377  if (authHandler == NULL) {
378    PyErr_SetString(PyExc_RuntimeError, "Must be called within a bbftpd callback");
379    return NULL;
380  }
381
382  if (ndg_message_recv(&buffer, &len, logmessage) == -1) {
383    PyErr_SetString(PyExc_IOError, logmessage);
384    return NULL;
385  }
386
387  ret = Py_BuildValue("s#", buffer, len);
388  free(buffer);
389
390  return ret;
391}
392
393/**
394 * Main entry point for the python module.
395 */
396static PyObject *pybbftpd_run(PyObject *self, PyObject *args) {
397  int argc, i;
398  char **argv, **arg_p, *p;
399  int pid;
400  char preargs[2][20] = { "bbftpd_embedded", "-b" };
401  PyObject *daemon_args, *item;
402
403  if (!PyArg_ParseTuple(args, "OO", &authHandler, &daemon_args)) {
404    return NULL;
405  }
406  Py_INCREF(authHandler);
407  Py_INCREF(daemon_args);
408
409  /*
410   * Convert arguments into a standard argv sequence.
411   */
412  argc = PySequence_Size(daemon_args);
413  if ((argv = (char**)malloc((argc + 2)*sizeof(char*))) == NULL) {
414    PyErr_SetString(PyExc_MemoryError, "malloc failed");
415    Py_DECREF(daemon_args);
416    return NULL;
417  }
418
419  // Add fixed arguments to daemon's argv
420  arg_p = argv;
421  *arg_p = preargs[0]; arg_p++;
422  *arg_p = preargs[1]; arg_p++;
423
424  for (i=0; i<argc; i++) {
425    if ((item = PySequence_GetItem(daemon_args, i)) == NULL) {
426      free(argv);
427      Py_DECREF(authHandler);
428      Py_DECREF(daemon_args);
429      return NULL;
430    }
431    if ((p = PyString_AsString(item)) == NULL) {
432      free(argv);
433      Py_DECREF(item);
434      Py_DECREF(authHandler);
435      Py_DECREF(daemon_args);
436      return NULL;
437    }
438    if ((*arg_p = (char *)malloc(strlen(p))) == NULL) {
439      Py_DECREF(item);
440      Py_DECREF(authHandler);
441      Py_DECREF(daemon_args);
442      PyErr_SetString(PyExc_MemoryError, "malloc failed");
443      return NULL;
444    }
445    strcpy(*arg_p, p);
446
447    arg_p++;
448    Py_DECREF(item);
449  }
450
451  /* Initialise logger */
452  if (serverLogger != NULL) {
453    Py_DECREF(serverLogger);
454  }
455  if ((serverLogger = getLogger("ndg.delivery.server")) == NULL) {
456    return NULL;
457  }
458
459  /*
460   * We must reset some global variables so that bbftpd_run can be executed multiple times.
461   */
462  be_daemon = 0;
463
464  pid = bbftpd_main(argc+2, argv, environ);
465
466  free(argv);
467  Py_DECREF(authHandler); authHandler = NULL;
468  Py_DECREF(daemon_args);
469
470  return Py_BuildValue("i", pid);
471}
472
473static PyMethodDef BbftpdMethods[] = {
474  {
475    "run", pybbftpd_run, METH_VARARGS, 
476    "Execute the bbftpd server.\n"
477    "\n"
478    "Forks an embedded bbftpd process as if it were executed\n"
479    "with the command line $ bbftpd -b <args>."
480    "\n"
481    "@param args: command line arguments to pass to the server\n"
482    "@return: the PID of the server process\n"
483  },
484  {
485    "send", pybbftpd_send, METH_VARARGS,
486    "Send an authentication message to the client.\n"
487    "\n"
488    "Equivilent to bbftpd_private_send() C call.\n"
489    "\n"
490    "@param buffer: A string containing the message\n"
491    "@raise IOError: if bbftpd_private_send() call fails\n"
492  },
493  {
494    "recv", pybbftpd_recv, METH_VARARGS,
495    "Receive an authentication message from the client.\n"
496    "\n"
497    "Equivilent to bbftpd_private_recv() C call.\n"
498    "\n"
499    "@param len: The length of message to expect\n"
500    "@return: A string containing the message\n"
501    "@raise IOError: if bbftpd_private_recv() call fails\n"
502  },
503  {
504    "log", pybbftpd_log, METH_VARARGS,
505    "Send a message to the bbftpd logger.\n"
506    "\n"
507    "@param priority: as syslog priority\n"
508    "@param message: A string containing the message to send\n"
509  },
510  {NULL, NULL, 0, NULL}
511};
512
513
514#define ADDCONST(m,x) PyModule_AddIntConstant(m, #x, x)
515
516PyMODINIT_FUNC initbbftpd(void) {
517  bbftpdModule = Py_InitModule("bbftpd", BbftpdMethods);
518  Py_INCREF(bbftpdModule);
519
520  ADDCONST(bbftpdModule, MSG_CHDIR_V2);
521  ADDCONST(bbftpdModule, MSG_CHDIR_V2);
522  ADDCONST(bbftpdModule, MSG_LIST_V2);
523  ADDCONST(bbftpdModule, MSG_MKDIR_V2);
524  ADDCONST(bbftpdModule, MSG_RM);
525  ADDCONST(bbftpdModule, MSG_STAT);
526  ADDCONST(bbftpdModule, MSG_DF);
527 
528  ADDCONST(bbftpdModule, TROPT_TMP);
529  ADDCONST(bbftpdModule, TROPT_ACC);
530  ADDCONST(bbftpdModule, TROPT_MODE);
531  ADDCONST(bbftpdModule, TROPT_DIR);
532  ADDCONST(bbftpdModule, TROPT_GZIP);
533  ADDCONST(bbftpdModule, TROPT_RFIO);
534  ADDCONST(bbftpdModule, TROPT_RFIO_O);
535  ADDCONST(bbftpdModule, TROPT_QBSS);
536
537  /* after_fork_hook and before_fork_hook allows python to take action before and after bbftpd forks.
538     They is called from within the bbftpd source if they are callable.  By default this isn't the case.
539  */
540  Py_INCREF(Py_None); /* PyModule_AddObject steels a reference. */
541  PyModule_AddObject(bbftpdModule, "after_fork_hook", Py_None);
542  Py_INCREF(Py_None);
543  PyModule_AddObject(bbftpdModule, "before_fork_hook", Py_None);
544
545}
546
Note: See TracBrowser for help on using the repository browser.