/* * (C) Gregory Trubetskoy May 1998 * * mod_python.c * * See accompanying documentation for details. See COPYRIGHT file * for Copyright. This file is part of Httpdapy. * */ /* Apache headers */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "util_script.h" #include "http_log.h" #include "compat.h" /* Python headers */ #include "Python.h" /****************************************************************** Declarations ******************************************************************/ /********************************* Python things *********************************/ /********************************* members of httpdapi_hook module *********************************/ /* the CallBack object */ static PyObject *obCallBack = NULL; extern void inithttpdapi_hook(); /* this function is called at startup by httpd to obtain the CallBack object reference */ static PyObject * SetCallBack( PyObject *self, PyObject *args ); /* methods of httpdapi_hook */ static struct PyMethodDef httpdapi_hook_module_methods[] = { {"SetCallBack", (PyCFunction) SetCallBack, 1}, {NULL, NULL} /* sentinel */ }; /******************************** pblockobject ********************************/ typedef struct pblockobject { PyObject_VAR_HEAD table * table; } pblockobject; static PyTypeObject pblockobjecttype; static PyObject * pblock2str ( pblockobject *self, PyObject *args ); static PyObject * nvinsert ( pblockobject *self, PyObject *args ); static PyObject * findval ( pblockobject *self, PyObject *args ); static PyObject * pblock_remove ( pblockobject *self, PyObject *args ); static PyObject * pblockgetitem ( pblockobject *self, PyObject *key ); static int pblocklength ( pblockobject *self ); static int pblocksetitem ( pblockobject *self, PyObject *key, PyObject *val ); static int pb_setitem ( pblockobject *self, char *key, char *val ); static pblockobject * make_pblockobject( table * t ); static PyMethodDef pblockmethods[] = { { "pblock2str", (PyCFunction) pblock2str, 1}, { "nvinsert", (PyCFunction) nvinsert, 1}, { "findval", (PyCFunction) findval, 1}, { "pblock_remove", (PyCFunction) pblock_remove, 1}, { NULL, NULL } /* sentinel */ }; static PyMappingMethods pblock_mapping = { (inquiry) pblocklength, /*mp_length*/ (binaryfunc) pblockgetitem, /*mp_subscript*/ (objobjargproc) pblocksetitem, /*mp_ass_subscript*/ }; /******************************** requestobject ********************************/ typedef struct requestobject { PyObject_HEAD request_rec * req; pblockobject * reqpb; pblockobject * headers; pblockobject * srvhdrs; pblockobject * vars; int status_code; } requestobject; static PyTypeObject requestobjecttype; static PyObject * reqstart_response ( requestobject *self, PyObject *args ); static PyObject * request_header ( requestobject *self, PyObject *args ); static PyObject * protocol_status ( requestobject *self, PyObject *args ); static PyObject * log_err ( requestobject *self, PyObject *args ); static PyMethodDef requestobjectmethods[] = { { "start_response", (PyCFunction) reqstart_response, 1}, { "request_header", (PyCFunction) request_header, 1}, { "log_error", (PyCFunction) log_err, 1}, { "protocol_status", (PyCFunction) protocol_status, 1}, { NULL, NULL } /* sentinel */ }; /******************************** sessionobject ********************************/ typedef struct sessionobject { PyObject_VAR_HEAD request_rec * req; int bytes_read; } sessionobject; static PyTypeObject sessionobjecttype; static PyObject * session_dns ( sessionobject *self, PyObject *args ); static PyObject * net_write ( sessionobject *self, PyObject *args ); static PyObject * sn_client ( sessionobject *self, PyObject *args ); static PyObject * net_read ( sessionobject *self, PyObject *args ); static PyMethodDef sessionmethods[] = { { "session_dns", (PyCFunction) session_dns, 1}, { "net_write", (PyCFunction) net_write, 1}, { "client", (PyCFunction) sn_client, 1}, { "net_read", (PyCFunction) net_read, 1}, { "form_data", (PyCFunction) net_read, 1}, { NULL, NULL } /* sentinel */ }; /********************************/ /* some other functions */ PyObject * dict_from_request_rec( request_rec *req ); pblockobject * pblock_headers_in( request_rec *req ); /******************************** *** end of Python things *** ********************************/ /******************************** Apache things ********************************/ /* Apache module declaration */ module MODULE_VAR_EXPORT python_module; extern module python_module; /* private module memory pools */ static pool *python_pool = NULL; /* structure describing authentication parameters */ typedef struct { char * pymodule; int authoritative; } py_auth_config_rec; /******************************** *** end of Apache things *** ********************************/ /****************************************************************** *** end of declarations *** ******************************************************************/ /****************************************************************** Python objects and their methods ******************************************************************/ /******************************** pblock object ********************************/ /* * This is a mapping of a Python object to an Apache table. * * The name "pblock" comes from NSAPI and I continue using it * for consistency. NSAPI's pblocks are very similiar to Apache * tables, at least from the outside. * * This pblock object has all the functions of Netscape's pblock * available in NSAPY ( pblock2str, nvinsert, findval, etc ), * largely for reasons of backwards compatibility, but * in addition to that can behave like a Python dictionary, * which is the recommended approach. So mypb['abc'] is * equivalent (and preferred over) mypb.findval('abc'). * */ /** ** make_pblockobject ** * This routine creates a Python pblockobject given an Apache * table pointer. * */ static pblockobject * make_pblockobject( table * t ) { pblockobject *result; result = PyMem_NEW( pblockobject, 1 ); if ( ! result ) return ( pblockobject * ) PyErr_NoMemory(); result->table = t; result->ob_type = &pblockobjecttype; _Py_NewReference( result ); return result; } /** ** pblock_getattr ** * Gets pblock's attributes */ static PyObject * pblock_getattr( PyObject *self, char *name ) { return Py_FindMethod( pblockmethods, self, name ); } /** ** pblockgetitem ** * Gets a dictionary item */ static PyObject * pblockgetitem( pblockobject *self, PyObject *key ) { char *v; char *k; k = PyString_AsString( key ); v = table_get( self->table, k ); if ( ! v ) { PyErr_SetObject( PyExc_KeyError, key ); return NULL; } return PyString_FromString( v ); } /** ** pblocklength ** * Number of elements in the pblock. Called * when you do len( pblock ) in Python. */ static int pblocklength( pblockobject *self ) { return table_elts( self->table )->nelts; }; /** ** pblocksetitem ** * insert into pblock dictionary-style * Since the underlying table_set makes a *copy* of the string, * there is no need to increment the reference to the Python * string passed in. */ static int pblocksetitem( pblockobject *self, PyObject *key, PyObject *val ) { char *k; k = PyString_AsString( key ); if ( ( val == Py_None ) || ( val == NULL ) ) table_unset( self->table, k ); else table_set( self->table, k, PyString_AsString( val ) ); return 0; }; /** ** pb_setitem ** * This is a wrapper around pblocksetitem that takes * char * for convenience, for internal use. */ static int pb_setitem( pblockobject *self, char *key, char *val ) { PyObject *ps1, *ps2; ps1 = PyString_FromString( key ); ps2 = PyString_FromString( val ); pblocksetitem( self, ps1, ps2 ); Py_DECREF( ps1 ); Py_DECREF( ps2 ); return 0; } /** ** pblock_dealloc ** * Frees pblock's memory */ static void pblock_dealloc( pblockobject *self ) { free( self ); } /** ** pblock_repr ** * prints pblock like a dictionary */ static PyObject * pblock_repr( pblockobject *self ) { PyObject *s; array_header *ah; table_entry *elts; int i; s = PyString_FromString( "{'" ); ah = table_elts ( self->table ); elts = ( table_entry * ) ah->elts; i = ah->nelts; while ( i-- ) if ( elts[i].key ) { PyString_ConcatAndDel( &s, PyString_FromString( elts[i].key ) ); PyString_ConcatAndDel( &s, PyString_FromString( "': '" ) ); PyString_ConcatAndDel( &s, PyString_FromString( elts[i].val ) ); PyString_ConcatAndDel( &s, PyString_FromString( "'" ) ); if ( i > 0 ) PyString_ConcatAndDel( &s, PyString_FromString( ", '" ) ); else PyString_ConcatAndDel( &s, PyString_FromString( "}" ) ); } return s; } /** ** pblock.pblock2str( pblock self ) ** * Converts a pblock to a string * */ static PyObject * pblock2str( pblockobject *self, PyObject *args ) { PyObject *s; array_header *ah; table_entry *elts; int i; s = PyString_FromString( "" ); ah = table_elts ( self->table ); elts = ( table_entry * ) ah->elts; i = ah->nelts; while ( i-- ) if ( elts[i].key ) { PyString_ConcatAndDel( &s, PyString_FromString( elts[i].key ) ); PyString_ConcatAndDel( &s, PyString_FromString( "=\"" ) ); PyString_ConcatAndDel( &s, PyString_FromString( elts[i].val ) ); PyString_ConcatAndDel( &s, PyString_FromString( "\" " ) ); } return s; } /** ** pblock.nvinsert( pblock self, string name, string value ) ** * insert a new value into a pblock * (does not check for duplicates) */ static PyObject * nvinsert( pblockobject *self, PyObject *args ) { char *name, *value; if (! PyArg_ParseTuple(args, "ss", &name, &value) ) return NULL; table_add( self->table, name, value ); Py_INCREF(Py_None); return Py_None; } /** ** pblock.findval( pblock self, string key) ** * finds a value in a pblock * */ static PyObject * findval( pblockobject *self, PyObject *args ) { char *key, *value; if (! PyArg_ParseTuple( args, "s", &key ) ) return NULL; value = table_get( self->table, key ); if ( ! value ) { PyErr_SetString( PyExc_ValueError, "no such key" ); return NULL; } return PyString_FromString( value ); } /** ** pblock.pblock_remove( pblock self, string key ) ** * removes a pblock entry * (but only one) */ static PyObject * pblock_remove( pblockobject *self, PyObject *args ) { char *name; if (! PyArg_ParseTuple(args, "s", &name) ) return NULL; table_unset( self->table, name ); Py_INCREF( Py_None ); return Py_None; } /******************************** *** end of pblock object *** ********************************/ /******************************** session object ********************************/ /** ** make_session_object ** * Creates a session. Takes a request pointer as an argument. */ static sessionobject * make_sessionobject( request_rec * req ) { sessionobject *sess; sess = PyMem_NEW( sessionobject, 1 ); if (! sess ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "make_sessionobject: PyMem_NEW() failed! No more memory." ); exit(1); } sess->req = req; sess->bytes_read = 0; sess->ob_type = &sessionobjecttype; _Py_NewReference( sess ); return sess; } /** ** session_dealloc ** * Called by Python's garbage collector to destroy the object. */ static void session_dealloc( sessionobject *self ) { free( self ); } /** ** session.session_dns( session self ) ** * return the dns host name */ static PyObject * session_dns( sessionobject *sess, PyObject *args ) { if ( sess->req->connection->remote_logname ) return PyString_FromString( sess->req->connection->remote_logname ); else { Py_INCREF( Py_None ); return Py_None; } } /** ** session.net_write( session self, string what ) ** * write output to the client */ static PyObject * net_write( sessionobject *self, PyObject *args ) { int len; int i; char *string; if (! PyArg_ParseTuple( args, "s#", &string, &len ) ) return NULL; /* bad args */ for ( i = 0 ; i < len ; i++ ) rputc( string[i], self->req ); Py_INCREF( Py_None ); return Py_None; } /** ** session.net_read( session self, int bytes ) ** * Reads stuff like POST requests from the client */ static PyObject * net_read( sessionobject *self, PyObject *args ) { int len, n, rc; char *buffer; PyObject *result; if (! PyArg_ParseTuple( args, "i", &len ) ) return NULL; if ( len <= 0 ) { PyErr_SetString( PyExc_ValueError, "net_read must have positive integer parameter" ); return NULL; } /* is this the first read? */ if (! self->bytes_read ) { /* then do some initial setting up */ rc = setup_client_block( self->req, REQUEST_CHUNKED_ERROR ); if( rc != OK ) { PyErr_SetObject( PyExc_IOError, PyInt_FromLong( rc ) ); return NULL; } if (! should_client_block( self->req ) ) { /* client has nothing to send */ Py_INCREF( Py_None ); return Py_None; } } result = PyString_FromStringAndSize( NULL, len ); /* possibly no more memory */ if ( result == NULL ) return NULL; /* read it in */ buffer = PyString_AS_STRING( ( PyStringObject * ) result ); /* get_client_block() returns number of bytes read or 0 when * there is nothing more to read; inconsistent if you ask me! * So you can't trust its return value to be the real number * of bytes read. We look at req->read_length before and after * the read instead. */ n = self->req->read_length; get_client_block( self->req, buffer, len ); self->bytes_read = self->req->read_length - n; /* resize if necessary */ if ( self->bytes_read < len ) if( _PyString_Resize( &result, self->bytes_read ) ) return NULL; return result; } /** ** session.sn_client( session self ) ** * Returns session->client. client pblock contains information * about the client - its ip and dns name. */ static PyObject * sn_client( sessionobject *self, PyObject *args ) { pblockobject * sn_client; if (! PyArg_ParseTuple(args, "") ) return NULL; /* error */ sn_client = make_pblockobject( make_table( python_pool, 2 ) ); if ( self->req->connection->remote_ip ) pb_setitem( sn_client, "ip", self->req->connection->remote_ip ); if ( self->req->connection->remote_host ) pb_setitem( sn_client, "dns", self->req->connection->remote_host ); return (PyObject *) sn_client; } /** ** session_getattr ** */ static PyObject * session_getattr( PyObject *self, char *name ) { return Py_FindMethod( sessionmethods, self, name ); } /******************************** *** end of session object *** ********************************/ /******************************** request object ********************************/ /** ** make_requestobject ** * make a request object */ static requestobject * make_requestobject( request_rec * req ) { requestobject * req_obj; char * pw; req_obj = PyMem_NEW( requestobject, 1 ); if (! req_obj ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "make_requestobject: PyMem_NEW() failed! No more memory?" ); exit(1); } /* this is necessary for path-translated for example */ add_cgi_vars( req ); /* req.reqpb */ req_obj->reqpb = make_pblockobject( make_table( python_pool, 4 ) ); if ( req->the_request ) pb_setitem( req_obj->reqpb, "clf-request", req->the_request ); if ( req->method ) pb_setitem( req_obj->reqpb, "method", req->method ); if ( table_get( req->subprocess_env, "SERVER_PROTOCOL" ) ) pb_setitem( req_obj->reqpb, "protocol", table_get( req->subprocess_env, "SERVER_PROTOCOL" ) ); if ( req->uri ) pb_setitem( req_obj->reqpb, "uri", req->uri ); /* req.headers */ req_obj->headers = pblock_headers_in( req ); /* req.srvhdrs */ req_obj->srvhdrs = make_pblockobject( req->headers_out ); /* req.vars */ req_obj->vars = make_pblockobject( make_table( python_pool, 6 ) ); if (! get_basic_auth_pw( req, &pw ) ) pb_setitem( req_obj->vars, "auth-password", pw ); if ( req->connection->auth_type ) pb_setitem( req_obj->vars, "auth-type", req->connection->auth_type ); if ( req->connection->user ) pb_setitem( req_obj->vars, "auth-user", req->connection->user ); if ( req->path_info ) pb_setitem( req_obj->vars, "path-info", req->path_info ); if ( table_get( req->subprocess_env, "PATH_TRANSLATED" ) ) pb_setitem( req_obj->vars, "path-translated", table_get( req->subprocess_env, "PATH_TRANSLATED" ) ); if ( req->filename ) pb_setitem( req_obj->vars, "path", req->filename ); req_obj->req = req; req_obj->status_code = 0; req_obj->ob_type = &requestobjecttype; _Py_NewReference( req_obj ); return req_obj; } /** ** pblock_headers_in( request_rec *req ) ** * creates a pblock from request headers. */ pblockobject * pblock_headers_in( request_rec *req ) { pblockobject * headers; array_header *ah; table_entry *elts; char * key; int i, ii; /* req_headers (these are headers sent by the client) */ headers = make_pblockobject( make_table( python_pool, 15 ) ); ah = table_elts ( req->headers_in ); elts = ( table_entry * ) ah->elts; i = ah->nelts; while ( i-- ) if ( elts[i].key ) { /* make a copy of the key */ key = palloc( python_pool, strlen( elts[i].key ) ); strcpy( key, elts[i].key ); /* convert to lower case */ for ( ii = 0; ii < (int) strlen( key ); ii++ ) key[ii] = tolower( key[ii] ); pb_setitem( headers, key, elts[i].val ); } return headers; } /** ** req_dealloc ** * deallocates the request object */ static void req_dealloc( requestobject *self ) { /* Note that this frees the Python objects, but not * the refrenece to the table that it holds. That is * taken care of when we destroy the pool. */ Py_XDECREF( self->reqpb ); Py_XDECREF( self->headers ); Py_XDECREF( self->srvhdrs ); Py_XDECREF( self->vars ); free( self ); } /** ** request.reqstart_response( request self ) ** * Begin response. Must be called before sending any data. * This function sends HTTP headers, so doing anything with * headers is meaningless after it's been called. */ static PyObject * reqstart_response( requestobject *self, PyObject *args ) { /* set the right content-type header first */ self->req->content_type = table_get( self->req->headers_out, "content-type" ); send_http_header( self->req ); Py_INCREF( Py_None ); return Py_None; } /** ** requestobjectgetattr ** * Called by Python to get attributes of the request object. */ static PyObject * requestobjectgetattr( requestobject *req_obj, char *name ) { if ( strcmp( name, "headers" ) == 0 ) { Py_INCREF( req_obj->headers ); return ( PyObject *) req_obj->headers; } else if ( strcmp( name, "reqpb" ) == 0 ) { Py_INCREF( req_obj->reqpb ); return ( PyObject *) req_obj->reqpb; } else if ( strcmp( name, "srvhdrs" ) == 0 ) { Py_INCREF( req_obj->srvhdrs ); return ( PyObject *) req_obj->srvhdrs; } else if ( strcmp( name, "vars" ) == 0 ) { Py_INCREF( req_obj->vars ); return ( PyObject *) req_obj->vars; } else return Py_FindMethod( requestobjectmethods, (PyObject *)req_obj, name ); } /** ** request.request_header( request self, string header, session session ) ** * returns a client header */ static PyObject * request_header( requestobject *self, PyObject *args ) { char *v, *header; int i; sessionobject *sess; if (! PyArg_ParseTuple(args, "sO", &header, &sess) ) return NULL; /* convert header to lower case */ for ( i = 0; i < (int) strlen( header ); i++ ) header[i] = tolower( header[i] ); v = table_get( self->headers->table, header ); if ( ! v ) { Py_INCREF(Py_None); return Py_None; } return PyString_FromString( v ); } /** ** request.protocol_status( request self, session session, string status ) ** * Sets protocol status. Use this function to set HTTP protocol status. */ static PyObject * protocol_status( requestobject *self, PyObject *args ) { sessionobject *sno; char *responsestr; if (! PyArg_ParseTuple(args, "Os", &sno, &responsestr )) return NULL; /* error */ /* Here is a table showing how NSAPI codes are matched to * Apache codes. I don't understand 80% of them... * * PROTOCOL_CONTINUE HTTP_CONTINUE * PROTOCOL_SWITCHING HTTP_SWITCHING_PROTOCOLS * PROTOCOL_OK HTTP_OK * PROTOCOL_CREATED HTTP_CREATED * PROTOCOL_NO_RESPONSE HTTP_NO_CONTENT * PROTOCOL_PARTIAL_CONTENT HTTP_PARTIAL_CONTENT * PROTOCOL_REDIRECT HTTP_MOVED_TEMPORARILY * PROTOCOL_NOT_MODIFIED HTTP_NOT_MODIFIED * PROTOCOL_BAD_REQUEST HTTP_BAD_REQUEST * PROTOCOL_UNAUTHORIZED HTTP_UNAUTHORIZED * PROTOCOL_FORBIDDEN HTTP_FORBIDDEN * PROTOCOL_NOT_FOUND HTTP_NOT_FOUND * PROTOCOL_METHOD_NOT_ALLOWED HTTP_METHOD_NOT_ALLOWED * PROTOCOL_PROXY_UNAUTHORIZED HTTP_PROXY_AUTHENTICATION_REQUIRED * PROTOCOL_CONFLICT HTTP_CONFLICT * PROTOCOL_LENGTH_REQUIRED HTTP_LENGTH_REQUIRED * PROTOCOL_PRECONDITION_FAIL HTTP_PRECONDITION_FAILED * PROTOCOL_ENTITY_TOO_LARGE HTTP_REQUEST_ENTITY_TOO_LARGE * PROTOCOL_URI_TOO_LARGE HTTP_REQUEST_URI_TOO_LARGE * PROTOCOL_SERVER_ERROR HTTP_INTERNAL_SERVER_ERROR * PROTOCOL_NOT_IMPLEMENTED HTTP_NOT_IMPLEMENTED * PROTOCOL_VERSION_NOT_SUPPORTED HTTP_VERSION_NOT_SUPPORTED */ /* default to PROTOCOL_FORBIDDEN. */ self->status_code = HTTP_FORBIDDEN; if ( strcmp( responsestr, "PROTOCOL_OK")==0) self->status_code = OK; else if (! strcmp( responsestr, "PROTOCOL_CONTINUE" ) ) self->status_code = HTTP_CONTINUE; else if (! strcmp( responsestr, "PROTOCOL_SWITCHING") ) self->status_code = HTTP_SWITCHING_PROTOCOLS; else if (! strcmp( responsestr, "PROTOCOL_CREATED") ) self->status_code = HTTP_CREATED; else if (! strcmp( responsestr, "PROTOCOL_NO_RESPONSE") ) self->status_code = HTTP_NO_CONTENT; else if (! strcmp( responsestr, "PROTOCOL_PARTIAL_CONTENT") ) self->status_code = HTTP_PARTIAL_CONTENT; else if (! strcmp( responsestr, "PROTOCOL_REDIRECT") ) self->status_code = HTTP_MOVED_TEMPORARILY; else if (! strcmp( responsestr, "PROTOCOL_NOT_MODIFIED") ) self->status_code = HTTP_NOT_MODIFIED; else if (! strcmp( responsestr, "PROTOCOL_BAD_REQUEST") ) self->status_code = HTTP_BAD_REQUEST; else if (! strcmp( responsestr, "PROTOCOL_UNAUTHORIZED") ) self->status_code = HTTP_UNAUTHORIZED; else if (! strcmp( responsestr, "PROTOCOL_FORBIDDEN") ) self->status_code = HTTP_FORBIDDEN; else if (! strcmp( responsestr, "PROTOCOL_NOT_FOUND") ) self->status_code = HTTP_NOT_FOUND; else if (! strcmp( responsestr, "PROTOCOL_METHOD_NOT_ALLOWED") ) self->status_code = HTTP_METHOD_NOT_ALLOWED; else if (! strcmp( responsestr, "PROTOCOL_PROXY_UNAUTHORIZED") ) self->status_code = HTTP_PROXY_AUTHENTICATION_REQUIRED; else if (! strcmp( responsestr, "PROTOCOL_CONFLICT") ) self->status_code = HTTP_CONFLICT; else if (! strcmp( responsestr, "PROTOCOL_LENGTH_REQUIRED") ) self->status_code = HTTP_LENGTH_REQUIRED; else if (! strcmp( responsestr, "PROTOCOL_PRECONDITION_FAIL") ) self->status_code = HTTP_PRECONDITION_FAILED; else if (! strcmp( responsestr, "PROTOCOL_ENTITY_TOO_LARGE") ) self->status_code = HTTP_REQUEST_ENTITY_TOO_LARGE; else if (! strcmp( responsestr, "PROTOCOL_URI_TOO_LARGE") ) self->status_code = HTTP_REQUEST_URI_TOO_LARGE; else if (! strcmp( responsestr, "PROTOCOL_SERVER_ERROR") ) self->status_code = HTTP_INTERNAL_SERVER_ERROR; else if (! strcmp( responsestr, "PROTOCOL_NOT_IMPLEMENTED") ) self->status_code = HTTP_NOT_IMPLEMENTED; else if (! strcmp( responsestr, "PROTOCOL_VERSION_NOT_SUPPORTED") ) self->status_code = HTTP_VERSION_NOT_SUPPORTED; Py_XINCREF( Py_None ); return Py_None; } /** ** request.log_error( request self, string function, string message ) ** * This function writes to the error log. */ static PyObject * log_err ( requestobject *self, PyObject *args ) { char *message; char *function; sessionobject *sno; if (! PyArg_ParseTuple( args, "ssO", &function, &message, &sno ) ) return NULL; /* error */ aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, self->req->server, "for host %s trying to %s, %s reports: %s", self->req->connection->remote_ip, self->req->the_request, function, message ); Py_INCREF(Py_None); return Py_None; } /******************************** *** end of request object *** ********************************/ /****************************************************************** *** end of Python objects and their methods *** ******************************************************************/ /****************************************************************** functions called by Apache or Python ******************************************************************/ /** ** PythonInitFunction ** * When Apache sees "PythonInitFunction" in httpd.conf, it * calls this function. This is where Python is initialized. */ static const char *PythonInitFunction( cmd_parms *cmd, void *dummy, const char *module, const char *initstring) { char buff[1000]; /* make sure the directive is syntactically correct */ if ( !module ) { fprintf( stderr, "PythonInitFunction: no module specified" ); exit(1); } if ( !initstring ) { fprintf( stderr, "PythonInitFunction: no initstring specified" ); exit(1); } /* initialize Python */ Py_Initialize(); /* Initialize httpdapi_hook. * This makes an httpdapi_hook module available for import, but remember, * YOU should use "import httpdapi" not "httpdapi_hook". "httpdapi_hook" * is for internal use only. ( The only time it's needed is to assign * obCallBack ) */ inithttpdapi_hook(); /* Now execute the equivalent of * >>> import sys * >>> import * >>> * in the __main__ module to start up Python. */ sprintf( buff, "import %s\n", module); if ( PyRun_SimpleString( buff ) ) { fprintf( stderr, "PythonInitFunction: could not import %s.\n", module ); exit(1); } sprintf( buff, "%s\n", initstring ); if ( PyRun_SimpleString( buff ) ) { fprintf( stderr, "PythonInitFunction: could not call %s.\n", initstring ); exit(1); } /* the "initstring" should call a function to do something like * >>> import apache * >>> apache.SetCallBack( someCallBackObject ) * at this point obCallBack is a reference to someobject */ if ( ! obCallBack ) { fprintf( stderr, "PythonInitFunction: after %s no callback object found.\n", initstring ); exit(1); } /* Wow, this worked! */ return NULL; } /** ** SetCallBack - assign a CallBack object ** * This function must be called from Python upon execution * the initstring parameter, like this * * >>> import httpdapi_hook * >>> httpdapi_hook.SetCallBack( instance ) * * providing the instance as the CallBack object. */ static PyObject * SetCallBack( PyObject *self, PyObject *args ) { PyObject *callback; /* see if CallBack is in *args */ if ( ! PyArg_ParseTuple( args, "O", &callback ) ) return NULL; /* dispose of the old call back object, if there was one */ Py_XDECREF( obCallBack ); /* store the object, incref */ obCallBack = callback; Py_INCREF( obCallBack ); /* return None */ Py_INCREF(Py_None); return Py_None; } /** ** inithttpdapi_hook ** * This function is called when you do this * >>> import httpdapi_hook * */ extern void inithttpdapi_hook() { /* initialize requestobjecttype */ PyTypeObject rt = { PyObject_HEAD_INIT( &PyType_Type ) 0, "request", sizeof(requestobject), 0, (destructor)req_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ (getattrfunc)requestobjectgetattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ }; /* initialize pblockobjecttype */ PyTypeObject pt = { PyObject_HEAD_INIT( &PyType_Type ) 0, "pblock", sizeof( pblockobject ), 0, ( destructor ) pblock_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ ( getattrfunc ) pblock_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ (reprfunc)pblock_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ &pblock_mapping, /*tp_as_mapping*/ 0, /*tp_hash*/ }; /* initialize sessionobjecttype */ PyTypeObject st = { PyObject_HEAD_INIT(&PyType_Type) 0, "session", sizeof(sessionobject), 0, (destructor)session_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ (getattrfunc)session_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ }; pblockobjecttype = pt; requestobjecttype = rt; sessionobjecttype = st; Py_InitModule( "httpdapi_hook", httpdapi_hook_module_methods ); } /** ** create_objects ** * This creates the three objects: * pblock, request and session. */ static void create_objects( request_rec *req, pblockobject **pb_obj, requestobject **req_obj, sessionobject **sess_obj ) { char * pw; /* pb (these are lies!) */ *pb_obj = make_pblockobject( make_table( python_pool, 7 ) ); pb_setitem( *pb_obj, "fn", "python_request_handler" ); pb_setitem( *pb_obj, "method", "GET|HEAD|POST" ); pb_setitem( *pb_obj, "server-name", "Apache" ); if ( req->content_type ) pb_setitem( *pb_obj, "type", req->content_type ); if (! get_basic_auth_pw( req, &pw ) ) pb_setitem( *pb_obj, "auth-password", pw ); if ( req->connection->auth_type ) pb_setitem( *pb_obj, "auth-type", req->connection->auth_type ); if ( req->connection->user ) pb_setitem( *pb_obj, "auth-user", req->connection->user ); /* request */ *req_obj = make_requestobject( req ); /* session */ *sess_obj = make_sessionobject( req ); } /** ** python_request_handler ** * The request handler. This function is called for every * hit envoking a Python program. */ static int python_request_handler( request_rec *req ) { PyObject *resultobject = NULL; pblockobject *pb; requestobject *req_obj; sessionobject *sess_obj; char *resultstring; int result; /* we must have a callback object to succeed! */ if ( !obCallBack ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_request_handler: no obCallBack!" ); return HTTP_INTERNAL_SERVER_ERROR; } /* create a memory pool if we haven't yet */ if ( python_pool == NULL ) python_pool = make_sub_pool(NULL); /* nsapy-like objects */ create_objects( req, &pb, &req_obj, &sess_obj ); /* * Here is where we call into Python! * This is the C equivalent of * >>> resultobject = obCallBack.Service(pbo, sno, rqo) */ resultobject = PyObject_CallMethod( obCallBack, "Service", "OOO", pb, sess_obj, req_obj ); if (! resultobject ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_request_handler: Service() returned nothing!" ); return HTTP_INTERNAL_SERVER_ERROR; } else { /* Attempt to analyse the result as a string indicating which result to return */ if (! PyString_Check( resultobject ) ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_request_handler: Service() returned non-string!" ); return HTTP_INTERNAL_SERVER_ERROR; } else { resultstring = PyString_AS_STRING( ( PyStringObject * ) resultobject ); if ( strcmp( resultstring, "REQ_NOACTION" ) == 0 ) result = DECLINED; else if ( strcmp( resultstring, "REQ_PROCEED" ) == 0 ) result = OK; else { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_request_handler: Service() returned REQ_EXIT" ); result = HTTP_INTERNAL_SERVER_ERROR; } } } /* in case a specific status was specified via protocol_status() */ if ( req_obj->status_code ) result = req_obj->status_code; /* clean up */ Py_XDECREF( pb ); Py_XDECREF( sess_obj ); Py_XDECREF( req_obj ); Py_XDECREF( resultobject ); if ( python_pool != NULL ) { destroy_pool( python_pool ); python_pool = NULL; } /* return the translated result (or default result) to the Server. */ return result; } /** ** python_check_user_id ** * Handle authentication */ static int python_check_user_id( request_rec *req ) { PyObject *resultobject = NULL; pblockobject *pb; requestobject *req_obj; sessionobject *sess_obj; char *resultstring; py_auth_config_rec *sec; int result; /* we must have a callback object to succeed! */ if ( !obCallBack ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_request_handler: no obCallBack!" ); return HTTP_INTERNAL_SERVER_ERROR; } /* create a memory pool if we haven't yet */ if ( python_pool == NULL ) python_pool = make_sub_pool(NULL); /* TODO - why do I need this? * A trailing slash will make Apache call add_cgi_vars * recursively ad infinitum. May be it's an Apache bug? */ if ( ( req->path_info ) && ( req->path_info[ strlen( req->path_info ) - 1 ] == '/' ) ) { /* take out the slash */ req->path_info[ strlen( req->path_info ) - 1 ] = 0; /* nsapy-like objects */ create_objects( req, &pb, &req_obj, &sess_obj ); /* put the slash back in */ req->path_info[ strlen( req->path_info ) ] = '/'; } else { /* nsapy-like objects */ create_objects( req, &pb, &req_obj, &sess_obj ); } /* get the name of the module to do authentication and * stick it into pb.userdb */ sec = ( py_auth_config_rec * ) get_module_config( req->per_dir_config, &python_module ); if (! sec->pymodule ) return DECLINED; pb_setitem( pb, "userdb", sec->pymodule ); /* * Here is where we call into Python! * This is the C equivalent of * >>> resultobject = obCallBack.Service(pbo, sno, rqo) */ resultobject = PyObject_CallMethod( obCallBack, "AuthTrans", "OOO", pb, sess_obj, req_obj ); if (! resultobject ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_check_user_id: AuthTrans() returned nothing." ); return HTTP_INTERNAL_SERVER_ERROR; } else { /* Attempt to analyse the result as a string indicating which * result to return */ if (! PyString_Check( resultobject ) ) { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_check_user_id: AuthTrans() returned non-string." ); return HTTP_INTERNAL_SERVER_ERROR; } else { resultstring = PyString_AS_STRING( ( PyStringObject * ) resultobject ); if ( strcmp( resultstring, "REQ_NOACTION" ) == 0 ) if (! ( sec->authoritative ) ) /* let other modules figure it out */ return DECLINED; else /* ask password */ result = HTTP_UNAUTHORIZED; else if ( strcmp( resultstring, "REQ_PROCEED" ) == 0 ) /* password OK */ result = OK; else { aplog_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, req->server, "python_request_handler: Service() returned REQ_EXIT" ); result = HTTP_INTERNAL_SERVER_ERROR; } } } /* in case a specific status was specified via protocol_status() */ if ( req_obj->status_code ) result = req_obj->status_code; /* clean up */ Py_XDECREF( pb ); Py_XDECREF( sess_obj ); Py_XDECREF( req_obj ); Py_XDECREF( resultobject ); if ( python_pool != NULL ) { destroy_pool( python_pool ); python_pool = NULL; } return result; } /** ** create_py_auth_dir_config ** * Allocate memory and initialize the strucure that will * hold configuration parametes. */ static void *create_py_auth_dir_config( pool *p, char *d ) { py_auth_config_rec *sec = ( py_auth_config_rec * ) pcalloc( p, sizeof( py_auth_config_rec ) ); sec->pymodule = NULL; sec->authoritative = 1; return sec; } /** ** set_py_slot ** * This function is called whenever Apache encounters * the AuthPythonModule directive. This may happen at server * startup (once for every virtual host that has it), plus * at every hit where there is an .htaccess file with the * directive. In other words - a lot. * * It basically saves the name of the Python module to do * authorization. */ static const char *set_py_slot( cmd_parms *cmd, void *offset, char *f, char *t ) { char *p; int ci = ( int ) cmd->info; p = pstrdup( cmd->pool, f ); /* I love C! */ *( char ** ) ( ( char * ) offset + ci ) = p; return NULL; } /****************************************************************** * *** end of functions called by Apache or Python *** ******************************************************************/ /****************************************************************** * Apache module stuff ******************************************************************/ /* content handlers */ static handler_rec python_handlers[] = { { "python-program", python_request_handler }, { NULL } }; /* command table */ command_rec python_commands[] = { { "PythonInitFunction", /* directive name */ PythonInitFunction, /* config action routine */ NULL, /* argument to include in call */ RSRC_CONF, /* where available */ TAKE2, /* arguments */ "A line of Python to initialize it." /* directive description */ }, { "AuthPythonModule", set_py_slot, /* strange name, but we follow the apache convention */ ( void * ) XtOffsetOf( py_auth_config_rec, pymodule ), OR_AUTHCFG, TAKE1, "Python module to process authentication." }, {NULL} }; /* module definition */ module python_module = { STANDARD_MODULE_STUFF, NULL, /* module initializer */ create_py_auth_dir_config, /* per-directory config creator */ NULL, /* dir config merger */ NULL, /* server config creator */ NULL, /* server config merger */ python_commands, /* command table */ python_handlers, /* [7] list of handlers */ NULL, /* [2] filename-to-URI translation */ python_check_user_id, /* [5] check/validate user_id */ NULL, /* [6] check user_id is valid *here* */ NULL, /* [4] check access by host address */ NULL, /* [7] MIME type checker/setter */ NULL, /* [8] fixups */ NULL, /* [10] logger */ NULL, /* [3] header parser */ NULL, /* process initializer */ NULL, /* process exit/cleanup */ NULL /* [1] post read_request handling */ }; /****************************************************************** * LE FIN ******************************************************************/