source: TI05-delivery/trunk/components/client/ext/bbftpc.c @ 1541

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/trunk/components/client/ext/bbftpc.c@1541
Revision 1541, 9.8 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#include <Python.h>
13#include <stdio.h>
14#include <stdarg.h>
15#include <time.h>
16#include <setjmp.h>
17#include <client.h>
18#include <ndg_client.h>
19
20#include "util.h"
21
22#define STATE_NOTCONNECTED 0
23#define STATE_CONNECTED 1
24#define STATE_AUTH 2
25
26
27extern char **environ;
28
29int bbftpc_init(int argc, char **argv, char **envp);
30void bbftpc_final(void);
31int treatcommand(char *cmd);
32
33
34/**
35 * Static variables.
36 */
37static PyObject *authClientHandler = NULL;
38static int state = STATE_NOTCONNECTED;
39static PyObject *clientMessages = NULL;
40static jmp_buf error_jmp;
41
42/**
43 * Make a callback bbftpc->python to handle authentication.
44 *
45 * @param logmessage a pointer to a buffer of length \c NDG_MAX_LOGMESSAGE
46 *     for storing an error message when returning -1.
47 * @return 0 on success, -1 on failure
48 */
49int ndg_client_auth(char *logmessage) {
50  PyObject *ret_obj;
51  int mybool;
52
53  state = STATE_AUTH;
54  ret_obj = PyObject_CallMethod(authClientHandler, "authenticate", "");
55  state = STATE_CONNECTED;
56
57  /** @todo exceptions of type delivery.server.AuthenticationFailure should be logged as
58   *      failure rather than error.
59   */
60  if (ret_obj == NULL) {
61    ndg_pyerr_to_logmessage(logmessage, "ndg_client_auth");
62    return -1;
63  }
64
65  Py_INCREF(ret_obj);
66  mybool = PyObject_IsTrue(ret_obj);
67  Py_DECREF(ret_obj);
68
69  /* Authentication is considered failed if the return value isn't True.  */
70  if (!mybool) {
71    sprintf(logmessage, "ndg_client_auth: python authentication failed");
72    return -1;
73  }
74
75  return 0;
76}
77
78/**
79 * Capture error and diagnostic messages from the bbftp client code.
80 * This function is a replacement for bbftpstatus.c:printmessage that
81 * stores it's messages in a Python list for returning to the Python layer.
82 *
83 * @note This function must be called with error_jmp set to the python entry point.
84 *     If an error occurs the Python exception flag is set and longjmp(error_jmp) is called.
85 */
86void ndg_printmessage(FILE *IGNORED, int flag, int errcode, int tok, char *fmt, ...) {
87  va_list ap;
88  time_t logtime;
89  char clogtime[50];
90  char header[32], body[128];
91  PyObject *message;
92
93  va_start(ap,fmt);
94 
95  /*
96  ** If timestamp start to print the time
97  */
98  if (tok) {
99    /*
100    ** Get time
101    */
102    if ((time(&logtime) == -1) ||
103        (strftime(clogtime,sizeof(clogtime),"%a %b %d %H:%M:%S (%Z) : ",localtime(&logtime)) == 0)) {
104      strcpy(clogtime,"") ;
105    }
106  }
107  else {
108    strcpy(clogtime, "");
109  }
110  /*
111  ** Check if it is an error
112  */
113  if ( flag == CASE_ERROR || flag == CASE_FATAL_ERROR ) {
114    /*
115    ** It is an error
116    */
117    (void) snprintf(header, 32, "BBFTP-ERROR-%05d : ",errcode) ;
118  }
119  else if ( flag == CASE_WARNING ) {
120    /*
121    ** It is a warning
122    */
123    (void) snprintf(header, 32, "BBFTP-WARNING-%05d : ",errcode) ;
124  }
125  else {
126    strcpy(header, "");
127  }
128  /*
129  ** And print the requested string
130  */
131  (void) vsnprintf(body, 128, fmt, ap);
132  va_end(ap);
133
134  /* Add the message to clientMessages */
135  if ((message = PyString_FromFormat("%s%s%s", clogtime, header, body)) == NULL) {
136    longjmp(error_jmp, 1);
137  }
138  Py_INCREF(message);
139
140  if (PyList_Append(clientMessages, message) == -1) {
141    Py_DECREF(message);
142    longjmp(error_jmp, 1);
143  }
144  Py_DECREF(message);
145
146  if ( flag == CASE_FATAL_ERROR ) {
147    /* If an error do a longjmp to get back to the python entry point.
148     */
149    PyErr_SetString(PyExc_RuntimeError, "bbftpc fatal error");
150
151    /* Escape from deep within the bbftpc code. */
152    longjmp(error_jmp, flag);
153  }
154 
155}
156
157     
158
159
160/**
161 * Functions exported to python
162 */
163
164/**
165 * Main entry point into the bbftp client from python.
166 */
167static PyObject *bbftpc_connect(PyObject *self, PyObject *args) {
168  int i, argc;
169  struct ndg_argv argv_s;
170  char *arg;
171  PyObject *client_args, *item, *ret;
172
173  if (state != STATE_NOTCONNECTED) {
174    PyErr_SetString(PyExc_RuntimeError, "Connection already in use.  Call bbftpc.close() first.");
175    return NULL;
176  }
177
178  if (clientMessages != NULL) {
179    PyErr_SetString(PyExc_RuntimeError, "Internal API error.  clientMessages variable is in use.");
180    return NULL;
181  }
182
183  if (!PyArg_ParseTuple(args, "OO", &authClientHandler, &client_args)) {
184    return NULL;
185  }
186  Py_INCREF(authClientHandler);
187  Py_INCREF(client_args);
188
189  state = STATE_CONNECTED;
190
191  /*
192   * Convert arguments into a standard argv sequence.
193   */
194
195  if ((argc = PySequence_Size(client_args)) == -1) {
196    return NULL;
197  }
198  if (!ndg_argv_init(&argv_s, argc + 1)) {
199    PyErr_SetString(PyExc_MemoryError, "malloc failed");
200    Py_DECREF(client_args);
201    return NULL;
202  }
203
204  ndg_argv_add(&argv_s, "bbftpc_connect");
205  for (i=0; i<argc; i++) {
206    if ((item = PySequence_GetItem(client_args, i)) == NULL) {
207      ndg_argv_clear(&argv_s); ndg_argv_free(&argv_s);
208      Py_DECREF(client_args);
209      return NULL;
210    }
211    if ((arg = PyString_AsString(item)) == NULL) {
212      ndg_argv_clear(&argv_s); ndg_argv_free(&argv_s);
213      Py_DECREF(item);
214      Py_DECREF(client_args);
215      return NULL;
216    }
217
218    if (!ndg_argv_add(&argv_s, arg)) {
219
220      PyErr_SetString(PyExc_MemoryError, "ndg_argv_add failed");
221      ndg_argv_clear(&argv_s); ndg_argv_free(&argv_s);
222      Py_DECREF(item);
223      Py_DECREF(client_args);
224      return NULL;
225    }
226
227    Py_DECREF(item);
228
229  }
230  Py_DECREF(client_args);
231
232  /* Set clientMessages */
233  if ((clientMessages = PyList_New(0)) == NULL) {
234    ndg_argv_clear(&argv_s); ndg_argv_free(&argv_s);
235    return NULL;
236  }
237
238  /* save the environment for returning on an error. */
239  if (setjmp(error_jmp) != 0) {
240    ndg_argv_clear(&argv_s); ndg_argv_free(&argv_s);
241    /** @todo clientMessages is lost when an exception is raised.  This isn't ideal. */
242    /* Just dump clientMessages to stderr for now. */
243    fprintf(stderr, "clientMessages: ");
244    PyObject_Print(clientMessages, stderr, 0);
245    Py_DECREF(clientMessages); clientMessages = NULL;
246    return NULL;
247  }
248
249  /* This function may call longjmp(). */
250  bbftpc_init(argv_s.argc, argv_s.argv, environ);
251 
252  ndg_argv_clear(&argv_s); ndg_argv_free(&argv_s);
253  ret = clientMessages; clientMessages = NULL;
254  return ret;
255}
256
257/**
258 * Close the connection
259 */
260static PyObject *bbftpc_close(PyObject *self, PyObject *args) {
261
262  // Check there are no arguments
263  if (!PyArg_ParseTuple(args, "")) {
264    return NULL;
265  }
266
267  if (state == STATE_AUTH) {
268    PyErr_SetString(PyExc_RuntimeError, "Cannot close connection during authentication");
269    return NULL;
270  }
271
272  if (state == STATE_CONNECTED) {
273    bbftpc_final();
274    state = STATE_NOTCONNECTED;
275    authClientHandler = NULL;
276  }
277
278  Py_RETURN_NONE;
279}
280
281/**
282 * Execute a bbftpc command.
283 */
284
285static PyObject *bbftpc_docommand(PyObject *self, PyObject *args) {
286  char *cmd;
287  PyObject *ret;
288
289  if (!PyArg_ParseTuple(args, "s", &cmd)) {
290    return NULL;
291  }
292
293  if (state == STATE_AUTH) {
294    PyErr_SetString(PyExc_RuntimeError, "Cannot do command during authentication");
295    return NULL;
296  }
297
298  if (state == STATE_NOTCONNECTED) {
299    PyErr_SetString(PyExc_RuntimeError, "No Connection.");
300    return NULL;
301  }
302
303  if (clientMessages != NULL) {
304    PyErr_SetString(PyExc_RuntimeError, "Internal API error.  clientMessages variable is in use.");
305    return NULL;
306  }
307
308  /* Set clientMessages */
309  if ((clientMessages = PyList_New(0)) == NULL) {
310    return NULL;
311  }
312
313  /* save the environment for returning on an error. */
314  if (setjmp(error_jmp) != 0) {
315    Py_DECREF(clientMessages); clientMessages = NULL;
316    return NULL;
317  }
318
319  /* This function may call longjmp() */
320  treatcommand(cmd);
321
322  ret = clientMessages; clientMessages = NULL;
323  return ret;
324}
325
326static PyObject *bbftpc_isConnected(PyObject *self, PyObject *args) {
327 
328  if (!PyArg_ParseTuple(args, "")) {
329    return NULL;
330  }
331
332  if (state == STATE_NOTCONNECTED) {
333    Py_RETURN_FALSE;
334  }
335  else {
336    Py_RETURN_TRUE;
337  }
338}
339
340/**
341 * Send a message to the server during authentication.
342 */
343static PyObject *bbftpc_send(PyObject *self, PyObject *args) {
344  char *buffer, logmessage[1024];
345  int len;
346
347  if (!PyArg_ParseTuple(args, "s#", &buffer, &len)) {
348    return NULL;
349  }
350
351  if (state != STATE_AUTH) {
352    PyErr_SetString(PyExc_RuntimeError, "Must be called within a bbftpc callback");
353    return NULL;
354  }
355
356  if (ndg_client_message_send(buffer, len, logmessage) == -1) {
357    PyErr_SetString(PyExc_IOError, logmessage);
358    return NULL;
359  }
360
361  Py_RETURN_NONE;
362}
363
364/**
365 * Receive a message from the server during authentication.
366 *
367 * @return the message as a string.
368 */
369static PyObject *bbftpc_recv(PyObject *self, PyObject *args) {
370  char *buffer, logmessage[1024];
371  int len;
372  PyObject *ret;
373
374  if (!PyArg_ParseTuple(args, "")) {
375    return NULL;
376  }
377
378  if (state != STATE_AUTH) {
379    PyErr_SetString(PyExc_RuntimeError, "Must be called within a bbftpc callback");
380    return NULL;
381  }
382
383  if (ndg_client_message_recv(&buffer, &len, logmessage) == -1) {
384    PyErr_SetString(PyExc_IOError, logmessage);
385    return NULL;
386  }
387
388  ret = Py_BuildValue("s#", buffer, len);
389  free(buffer);
390  return ret;
391}
392
393   
394
395static PyMethodDef BbftpcMethods[] = {
396  {
397    "connect", bbftpc_connect, METH_VARARGS,
398    "Execute the bbftp client.\n"
399    "\n"
400    "@param args: command line arguments"
401  },
402  {
403    "close", bbftpc_close, METH_VARARGS,
404    "Close the bbftp client."
405  },
406  {
407    "docommand", bbftpc_docommand, METH_VARARGS,
408    "Run a command in the client.\n"
409    "\n"
410    "@param cmd: The command string."
411  },
412  {
413    "isConnected", bbftpc_isConnected, METH_VARARGS,
414    "Predicate returning whether the client is connected."
415  },
416  {
417    "send", bbftpc_send, METH_VARARGS,
418    "Send an authentication message."
419  },
420  {
421    "recv", bbftpc_recv, METH_VARARGS,
422    "Receive an authentication message."
423  },
424  {NULL, NULL, 0, NULL}
425};
426
427PyMODINIT_FUNC initbbftpc(void) {
428  (void) Py_InitModule("bbftpc", BbftpcMethods);
429}
Note: See TracBrowser for help on using the repository browser.