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

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

Implemented two hooks in the bbftpd source that allow
python code to be executed before and afer a fork(). This is needed
to reset python logging and will probably be usefull for other things
in the future.

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    free(buffer);
385    return NULL;
386  }
387
388  ret = Py_BuildValue("s#", buffer, len);
389  free(buffer);
390
391  return ret;
392}
393
394/**
395 * Main entry point for the python module.
396 */
397static PyObject *pybbftpd_run(PyObject *self, PyObject *args) {
398  int argc, i;
399  char **argv, **arg_p, *p;
400  int pid;
401  char preargs[2][20] = { "bbftpd_embedded", "-b" };
402  PyObject *daemon_args, *item;
403
404  if (!PyArg_ParseTuple(args, "OO", &authHandler, &daemon_args)) {
405    return NULL;
406  }
407  Py_INCREF(authHandler);
408  Py_INCREF(daemon_args);
409
410  /*
411   * Convert arguments into a standard argv sequence.
412   */
413  argc = PySequence_Size(daemon_args);
414  if ((argv = (char**)malloc((argc + 2)*sizeof(char*))) == NULL) {
415    PyErr_SetString(PyExc_MemoryError, "malloc failed");
416    Py_DECREF(daemon_args);
417    return NULL;
418  }
419
420  // Add fixed arguments to daemon's argv
421  arg_p = argv;
422  *arg_p = preargs[0]; arg_p++;
423  *arg_p = preargs[1]; arg_p++;
424
425  for (i=0; i<argc; i++) {
426    if ((item = PySequence_GetItem(daemon_args, i)) == NULL) {
427      free(argv);
428      Py_DECREF(authHandler);
429      Py_DECREF(daemon_args);
430      return NULL;
431    }
432    if ((p = PyString_AsString(item)) == NULL) {
433      free(argv);
434      Py_DECREF(item);
435      Py_DECREF(authHandler);
436      Py_DECREF(daemon_args);
437      return NULL;
438    }
439    if ((*arg_p = (char *)malloc(strlen(p))) == NULL) {
440      Py_DECREF(item);
441      Py_DECREF(authHandler);
442      Py_DECREF(daemon_args);
443      PyErr_SetString(PyExc_MemoryError, "malloc failed");
444      return NULL;
445    }
446    strcpy(*arg_p, p);
447
448    arg_p++;
449    Py_DECREF(item);
450  }
451
452  /* Initialise logger */
453  if (serverLogger != NULL) {
454    Py_DECREF(serverLogger);
455  }
456  if ((serverLogger = getLogger("ndg.delivery.server")) == NULL) {
457    return NULL;
458  }
459
460  /*
461   * We must reset some global variables so that bbftpd_run can be executed multiple times.
462   */
463  be_daemon = 0;
464
465  pid = bbftpd_main(argc+2, argv, environ);
466
467  free(argv);
468  Py_DECREF(authHandler); authHandler = NULL;
469  Py_DECREF(daemon_args);
470
471  return Py_BuildValue("i", pid);
472}
473
474static PyMethodDef BbftpdMethods[] = {
475  {
476    "run", pybbftpd_run, METH_VARARGS, 
477    "Execute the bbftpd server.\n"
478    "\n"
479    "Forks an embedded bbftpd process as if it were executed\n"
480    "with the command line $ bbftpd -b <args>."
481    "\n"
482    "@param args: command line arguments to pass to the server\n"
483    "@return: the PID of the server process\n"
484  },
485  {
486    "send", pybbftpd_send, METH_VARARGS,
487    "Send an authentication message to the client.\n"
488    "\n"
489    "Equivilent to bbftpd_private_send() C call.\n"
490    "\n"
491    "@param buffer: A string containing the message\n"
492    "@raise IOError: if bbftpd_private_send() call fails\n"
493  },
494  {
495    "recv", pybbftpd_recv, METH_VARARGS,
496    "Receive an authentication message from the client.\n"
497    "\n"
498    "Equivilent to bbftpd_private_recv() C call.\n"
499    "\n"
500    "@param len: The length of message to expect\n"
501    "@return: A string containing the message\n"
502    "@raise IOError: if bbftpd_private_recv() call fails\n"
503  },
504  {
505    "log", pybbftpd_log, METH_VARARGS,
506    "Send a message to the bbftpd logger.\n"
507    "\n"
508    "@param priority: as syslog priority\n"
509    "@param message: A string containing the message to send\n"
510  },
511  {NULL, NULL, 0, NULL}
512};
513
514
515#define ADDCONST(m,x) PyModule_AddIntConstant(m, #x, x)
516
517PyMODINIT_FUNC initbbftpd(void) {
518  bbftpdModule = Py_InitModule("bbftpd", BbftpdMethods);
519  Py_INCREF(bbftpdModule);
520
521  ADDCONST(bbftpdModule, MSG_CHDIR_V2);
522  ADDCONST(bbftpdModule, MSG_CHDIR_V2);
523  ADDCONST(bbftpdModule, MSG_LIST_V2);
524  ADDCONST(bbftpdModule, MSG_MKDIR_V2);
525  ADDCONST(bbftpdModule, MSG_RM);
526  ADDCONST(bbftpdModule, MSG_STAT);
527  ADDCONST(bbftpdModule, MSG_DF);
528 
529  ADDCONST(bbftpdModule, TROPT_TMP);
530  ADDCONST(bbftpdModule, TROPT_ACC);
531  ADDCONST(bbftpdModule, TROPT_MODE);
532  ADDCONST(bbftpdModule, TROPT_DIR);
533  ADDCONST(bbftpdModule, TROPT_GZIP);
534  ADDCONST(bbftpdModule, TROPT_RFIO);
535  ADDCONST(bbftpdModule, TROPT_RFIO_O);
536  ADDCONST(bbftpdModule, TROPT_QBSS);
537
538  /* after_fork_hook and before_fork_hook allows python to take action before and after bbftpd forks.
539     They is called from within the bbftpd source if they are callable.  By default this isn't the case.
540  */
541  Py_INCREF(Py_None); /* PyModule_AddObject steels a reference. */
542  PyModule_AddObject(bbftpdModule, "after_fork_hook", Py_None);
543  Py_INCREF(Py_None);
544  PyModule_AddObject(bbftpdModule, "before_fork_hook", Py_None);
545
546}
547
Note: See TracBrowser for help on using the repository browser.