Skip to Content.
Sympa Menu

shibboleth-dev - LightTPD SP module v0.1

Subject: Shibboleth Developers

List archive

LightTPD SP module v0.1


Chronological Thread 
  • From: André Cruz <>
  • To:
  • Subject: LightTPD SP module v0.1
  • Date: Mon, 08 Jan 2007 15:31:42 +0000

Hello.

The module is working. On my TODO list I still have the option
"ShibDisable" which I want to add in the future and the removal of the
debug prints.
You need lighttpd's source to compile this. Some instructions for doing
this are in the beginning of the file. After compiling and linking just
copy it to the plugins dir.

Just thought I'd share this if someone wants to use lighttpd with
shibboleth.
André Cruz
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>

extern "C" {

#include "base.h"
#include "log.h"
#include "buffer.h"
#include "plugin.h"
#include "inet_ntop_cache.h"
#include "response.h"
  
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

}

// SAML Runtime
#include <saml/saml.h>
#include <shib/shib.h>
#include <shib/shib-threads.h>
#include <shib-target/shib-target.h>
#include <xercesc/util/regx/RegularExpression.hpp>

using namespace std;
using namespace saml;
using namespace shibboleth;
using namespace shibtarget;

/*
  Shibboleth 1.3 plug-in for Lighty v0.1

  server.name has to be set!
  alias for error page logo and css should be set for default pages
        alias.url += (
                "/shibboleth-sp/logo.jpg" => "/usr/local/shib-sp/doc/shibboleth/logo.jpg",
                "/shibboleth-sp/main.css" => "/usr/local/shib-sp/doc/shibboleth/main.css"
        )

  Lighttpd >= 1.4.13 is needed

  Patch for lighttpd bug #289 is needed to compile the module.

  Compile: /bin/bash ../libtool --mode=compile --tag=CXX g++ -DHAVE_CONFIG_H -DLIBRARY_DIR="\"/usr/local/lib\"" -I. -I. -I.. -I/usr/local/shib-sp/include  -D_REENTRANT -D__EXTENSIONS__  -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES  -g -O2 -Wall -W -Wshadow  -MT mod_shib.lo -MD -MP -MF ".deps/mod_shib.Tpo" -c -o mod_shib.lo mod_shib.c

  Link: /bin/bash ../libtool --tag=CXX --mode=link g++  -g -O2 -Wall -W -Wshadow -o mod_shib.la -rpath /usr/local/lib -module -export-dynamic -avoid-version -no-undefined -L/usr/local/shib-sp/lib mod_shib.lo ../../shibboleth-1.3/shib/libshib.la ../../shibboleth-1.3/shib-target/libshib-target.la -lsaml -lxml-security-c -lxerces-c -lssl -lcrypto -ldl


  TODO:
  Remove uneeded configuration from module (shib_session). Add ShibDisable? move config and schema to plugin_data?

*/

typedef enum {
  SHIB_RETURN_OK,
  SHIB_RETURN_KO,
  SHIB_RETURN_DONE

} shib_return_t;
    

typedef enum {
  SHIB_SESSION_NOTSET = 0,
  SHIB_SESSION_ALWAYS,
  SHIB_SESSION_OPTIONAL,
  SHIB_SESSION_PASS

} shib_session_t;

typedef struct {
  buffer *shib_session_conf;
  shib_session_t shib_session;

  buffer *shib_config;
  buffer *shib_schema_dir;

} plugin_config;

typedef struct {
  PLUGIN_DATA;

  ShibTargetConfig* g_Config;
  buffer *g_unsetHeaderValue;

  plugin_config **config_storage;
  plugin_config conf;

} plugin_data;

class ShibTargetLighty : public ShibTarget
{
 public:
  server *_srv;
  connection *_con;
  plugin_data *_p_d;

  ShibTargetLighty(server *srv, connection *con, plugin_data *p_d) :
    _srv(srv), _con(con), _p_d(p_d) {

    init(_con->uri.scheme->ptr,
         _con->server_name->ptr,
         _srv->srvconf.port,
         _con->uri.path->used ? _con->uri.path->ptr : _con->uri.path_raw->ptr,
         _con->request.http_content_type,
         inet_ntop_cache_get_ip(_srv, &(_con->dst_addr)),
         (_con->request.http_method == HTTP_METHOD_GET) ? "GET" :
         (_con->request.http_method == HTTP_METHOD_POST) ? "POST" :
         (_con->request.http_method == HTTP_METHOD_HEAD) ? "HEAD" :
         ""
         );

  }

  ~ShibTargetLighty() { }

  virtual void log(ShibLogLevel level, const string &msg) {
    ShibTarget::log(level,msg);

    if (level == LogLevelError)
      log_error_write(_srv, __FILE__, __LINE__, "ss", "shib: ", msg.c_str());
  }

  virtual string getCookies(void) const {
    data_string *ds;

    if (NULL != (ds = (data_string *)array_get_element(_con->request.headers, "Cookie"))) {
      return string(ds->value->ptr ? ds->value->ptr : "");
    } else {
      return "";
    }
  }

  virtual void setCookie(const string &name, const string &value) {
    data_string *ds;

    if (NULL == (ds = (data_string *)array_get_unused_element(_con->response.headers, TYPE_STRING))) {
      ds = data_response_init();
    }
     
    buffer_copy_string(ds->key, "Set-Cookie");
    buffer_copy_string(ds->value, name.c_str());
    buffer_append_string(ds->value, "=");
    buffer_append_string(ds->value, value.c_str());
    array_insert_unique(_con->response.headers, (data_unset *)ds);          
  }

  virtual string getArgs(void) {
    return string(_con->uri.query->ptr ? _con->uri.query->ptr : "");
  }

  virtual string getPostData(void) {

    string post_data;

    if (_con->request.content_length) {

      chunkqueue *req_cq = _con->request_content_queue;
      buffer *b = buffer_init();

      chunk *req_c;
      off_t offset;
      
      for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; req_c = req_c->next) {
        off_t weWant = req_cq->bytes_in - offset;
        off_t weHave = 0;
        
        switch (req_c->type) {
        case chunk::FILE_CHUNK:
          weHave = req_c->file.length - req_c->offset;
          
          if (weHave > weWant) weHave = weWant;
          
          if (req_c->file.mmap.start == MAP_FAILED) {
            if (-1 == req_c->file.fd &&  /* open the file if not already open */
                -1 == (req_c->file.fd = open(req_c->file.name->ptr, O_RDONLY))) {
              log_error_write(_srv, __FILE__, __LINE__, "sss", "shib: ", "open failed: ", strerror(errno));                            
              return post_data;
            }
            
            req_c->file.mmap.length = req_c->file.length;
            
            if (MAP_FAILED == (req_c->file.mmap.start = (char *)mmap(0,  req_c->file.mmap.length, PROT_READ, MAP_SHARED, req_c->file.fd, 0))) {
              log_error_write(_srv, __FILE__, __LINE__, "sssbd", "shib: ", "mmap failed: ",
                              strerror(errno), req_c->file.name,  req_c->file.fd);
              
              close(req_c->file.fd);
              req_c->file.fd = -1;
              return post_data;
            }
            
            close(req_c->file.fd);
            req_c->file.fd = -1;
            
            /* chunk_reset() or chunk_free() will cleanup for us */
          }
          
          buffer_append_memory(b, req_c->file.mmap.start + req_c->offset, req_c->file.length - req_c->offset);
          req_c->offset += req_c->file.length - req_c->offset;
          req_cq->bytes_out += req_c->file.length - req_c->offset;

          break;
        case chunk::MEM_CHUNK:
          /* append to the buffer */
          weHave = req_c->mem->used - 1 - req_c->offset;

          if (weHave > weWant) weHave = weWant;

          buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave);
          b->used++; /* add virtual \0 */

          req_c->offset += weHave;
          req_cq->bytes_out += weHave;

          break;
        default:
          break;
        }

        offset += weHave;
      }
      post_data = b->ptr;
      buffer_free(b);
    }

    return post_data;
  }

  virtual void clearHeader(const string &name) {
    log_error_write(_srv, __FILE__, __LINE__, "sssss", "shib: ", "clearHeader START -> Name: ", name.c_str(), " Value: ", getHeader(name).c_str());                            

    if (_p_d->g_unsetHeaderValue->used) {
      setHeader(name, _p_d->g_unsetHeaderValue->ptr);

    } else {
      data_string *ds_dst;

      if (NULL != (ds_dst = (data_string *)array_get_element(_con->request.headers, name.c_str()))) {
        buffer_reset(ds_dst->value);
      }
    }

    log_error_write(_srv, __FILE__, __LINE__, "sssss", "shib: ", "clearHeader END -> Name: ", name.c_str(), " Value: ", getHeader(name).c_str());                            
  }
  
  virtual void setHeader(const string &name, const string &value) {
    log_error_write(_srv, __FILE__, __LINE__, "sssssss", "shib: ", "setHeader START -> Name: ", name.c_str(), "Original: ", getHeader(name).c_str(), " Value: ", value.c_str());                            
    data_string *ds_dst;
    
    if (NULL != (ds_dst = (data_string *)array_get_element(_con->request.headers, name.c_str()))) {
      buffer_copy_string_len(ds_dst->value, value.c_str(), value.length());
      log_error_write(_srv, __FILE__, __LINE__, "sssss", "shib: ", "setHeader ENDFOUND -> Name: ", name.c_str(), "Value: ", getHeader(name).c_str());                            
      return;
    }

    if (NULL == (ds_dst = (data_string *)array_get_unused_element(_con->request.headers, TYPE_STRING))) {
      ds_dst = data_string_init();
    }
    
    buffer_copy_string(ds_dst->key, name.c_str());
    buffer_copy_string(ds_dst->value, value.c_str());
    array_insert_unique(_con->request.headers, (data_unset *)ds_dst);
    log_error_write(_srv, __FILE__, __LINE__, "sssss", "shib: ", "setHeader ENDNEW -> Name: ", name.c_str(), "Value: ", getHeader(name).c_str());                            
  }

  virtual string getHeader(const string &name) {
    data_string *ds;
    
    if (NULL != (ds = (data_string *)array_get_element(_con->request.headers, name.c_str()))) {
      return string(ds->value->ptr ? ds->value->ptr : "");
    } else {
      return "";
    }
  }

  virtual void setRemoteUser(const string &user) {
    buffer_copy_string(_con->authed_user, user.c_str());
  }

  virtual string getRemoteUser(void) {
    if (_con->authed_user)
      return string(_con->authed_user->ptr ? _con->authed_user->ptr : "");
    else
      return "";
  }

  virtual void* sendPage(const string& msg,
                         int code=200,
                         const string& content_type="text/html",
                         const Iterator<header_t>& headers=EMPTY(header_t)) {

    while (headers.hasNext()) {
      const header_t& h=headers.next();
      response_header_insert(_srv, _con, h.first.c_str(), h.first.length(), h.second.c_str(), h.second.length());
    }
    response_header_overwrite(_srv, _con, CONST_STR_LEN("Content-Type"), content_type.c_str(), content_type.length());

    chunkqueue_append_mem(_con->write_queue, msg.c_str(), msg.length());
    
    _con->http_status = code;
    _con->file_finished = 1;

    return (void*)SHIB_RETURN_DONE;
  }

  virtual void* sendRedirect(const string& url) {
    response_header_insert(_srv, _con, CONST_STR_LEN("Location"), url.c_str(), url.length());
    _con->http_status = 302;
    _con->file_finished = 1;
    return (void*)SHIB_RETURN_DONE;
  }

  virtual void* returnDecline(void) { return (void*)SHIB_RETURN_KO; }

  virtual void* returnOK(void) { return (void*)SHIB_RETURN_OK; }

};

/* init the plugin data */
INIT_FUNC(mod_shib_init) {
  plugin_data *p;

  p = (plugin_data *)calloc(1, sizeof(*p));
  p->g_Config = NULL;
  p->g_unsetHeaderValue = buffer_init();
  return p;
}

/* detroy the plugin data */
FREE_FUNC(mod_shib_free) {
  plugin_data *p = (plugin_data *)p_d;

  UNUSED(srv);

  if (!p) return HANDLER_GO_ON;

  if (p->config_storage) {
    size_t i;

    for (i = 0; i < srv->config_context->used; i++) {
      plugin_config *s = p->config_storage[i];

      if (!s) continue;

      buffer_free(s->shib_session_conf);
      buffer_free(s->shib_config);
      buffer_free(s->shib_schema_dir);

      free(s);
    }
    free(p->config_storage);
  }

  if (p->g_Config) {
    p->g_Config->shutdown();
    p->g_Config = NULL;
  }

  buffer_free(p->g_unsetHeaderValue);

  free(p);

  return HANDLER_GO_ON;
}

/* handle plugin config and check values */

SETDEFAULTS_FUNC(mod_shib_set_defaults) {
  plugin_data *p = (plugin_data *)p_d;
  size_t i = 0;
  plugin_config *s;

  config_values_t cv[] = {
    { "shib.session",               NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
    { "shib.config",                NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
    { "shib.schema.dir",            NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
    { NULL,                         NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
  };

  if (!p) return HANDLER_ERROR;

  p->config_storage = (plugin_config**)calloc(1, srv->config_context->used * sizeof(specific_config *));

  for (i = 0; i < srv->config_context->used; i++) {

    s = (plugin_config *)calloc(1, sizeof(plugin_config));
    s->shib_session_conf = buffer_init();
    s->shib_config = buffer_init();
    s->shib_schema_dir = buffer_init();

    cv[0].destination = s->shib_session_conf;
    cv[1].destination = s->shib_config;
    cv[2].destination = s->shib_schema_dir;

    p->config_storage[i] = s;

    if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
      return HANDLER_ERROR;
    }

    if (s->shib_session_conf->used) {
      if (0 == strcmp(s->shib_session_conf->ptr, "always")) {
        s->shib_session = SHIB_SESSION_ALWAYS;
      } else if (0 == strcmp(s->shib_session_conf->ptr, "optional")) {
        s->shib_session = SHIB_SESSION_OPTIONAL;
      } else if (0 == strcmp(s->shib_session_conf->ptr, "pass")) {
        s->shib_session = SHIB_SESSION_PASS;
      } else {
        log_error_write(srv, __FILE__, __LINE__, "sb", "shib.session not supported:", s->shib_session_conf);
        
        return HANDLER_ERROR;
      }
    }
  }
  
  s = p->config_storage[0];
  if (s->shib_schema_dir->used && s->shib_config->used) {
    
    log_error_write(srv, __FILE__, __LINE__, "s", "shib_init starting");
    if (p->g_Config) {
      log_error_write(srv, __FILE__, __LINE__, "s", "shib_init() already initialized!");
      return HANDLER_ERROR;
    }

    try {
      p->g_Config = &ShibTargetConfig::getConfig();
      p->g_Config->setFeatures(ShibTargetConfig::Listener |
                               ShibTargetConfig::Metadata |
                               ShibTargetConfig::AAP |
                               ShibTargetConfig::RequestMapper |
                               ShibTargetConfig::LocalExtensions |
                               ShibTargetConfig::Logging
                               );
      if (!p->g_Config->init(s->shib_schema_dir->ptr)) {
        log_error_write(srv, __FILE__, __LINE__, "s", "shib_init() failed to initialize libraries");
        return HANDLER_ERROR;
      }
        
      if (!p->g_Config->load(s->shib_config->ptr)) {
        log_error_write(srv, __FILE__, __LINE__, "s", "shib_init() failed to load configuration");
        return HANDLER_ERROR;
      }

      IConfig* conf=p->g_Config->getINI();
      Locker locker(conf);
      const IPropertySet* props=conf->getPropertySet("Local");
      if (props) {
        pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
        if (unsetValue.first)
          buffer_copy_string(p->g_unsetHeaderValue, unsetValue.second);
      }
    }
    catch (...) {
      log_error_write(srv, __FILE__, __LINE__, "s", "shib_init() failed to initialize system");
      return HANDLER_ERROR;
    }

  } else {
    log_error_write(srv, __FILE__, __LINE__, "s", "shib module config and/or schema dir not initialized.");
    return HANDLER_ERROR;
        
  }
  
  return HANDLER_GO_ON;
}

#define PATCH(x) \
	p->conf.x = s->x;
static int mod_shib_patch_connection(server *srv, connection *con, plugin_data *p) {
  size_t i, j;
  plugin_config *s = p->config_storage[0];

  PATCH(shib_session);
  PATCH(shib_session_conf);

  /* skip the first, the global context */
  for (i = 1; i < srv->config_context->used; i++) {
    data_config *dc = (data_config *)srv->config_context->data[i];
    s = p->config_storage[i];

    /* condition didn't match */
    if (!config_check_cond(srv, con, dc)) continue;

    /* merge config */
    for (j = 0; j < dc->value->used; j++) {
      data_unset *du = dc->value->data[j];

      if (buffer_is_equal_string(du->key, CONST_STR_LEN("shib.session"))) {

        PATCH(shib_session);
        PATCH(shib_session_conf);
      }
    }
  }

  return 0;
}
#undef PATCH

URIHANDLER_FUNC(mod_shib_raw_handler) {
  plugin_data *p = (plugin_data *)p_d;
  
  UNUSED(srv);
  
  if (con->uri.path_raw->used == 0) return HANDLER_GO_ON;
  
  mod_shib_patch_connection(srv, con, p);
  
  log_error_write(srv, __FILE__, __LINE__, "ssb", "shib: ", "RAW URL : ", con->uri.path_raw);

  try {
    saml::NDC ndc("mod_shib_raw_handler");
    ShibTargetLighty sta(srv, con, p);
      
    pair<bool,void*> res = sta.doHandler();
    if (res.first) {
      log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doHandler() processed request.");
      
      switch((int)res.second) {
      case SHIB_RETURN_OK:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doHandler() returned OK.");
        return HANDLER_GO_ON;
        
      case SHIB_RETURN_KO:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doHandler() returned KO.");
        return HANDLER_GO_ON;

      case SHIB_RETURN_DONE:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doHandler() returned FINISHED.");
        return HANDLER_FINISHED;

      default:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doHandler() returned something strange.");
        return HANDLER_ERROR;
        
      }
    }
    
  } catch (SAMLException& e) {
    log_error_write(srv, __FILE__, __LINE__, "sss", "shib: ", "mod_shib_raw_handler threw an exception: ", e.what());
    return HANDLER_ERROR;

  } catch (...) {
    log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "mod_shib_raw_handler threw an uncaught exception!");
    return HANDLER_ERROR;

  }

  log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doHandler() did not do anything.");
  return HANDLER_ERROR;
    
}

URIHANDLER_FUNC(mod_shib_uri_handler) {
  plugin_data *p = (plugin_data *)p_d;
  
  UNUSED(srv);
  
  if (con->uri.path->used == 0) return HANDLER_GO_ON;
  
  mod_shib_patch_connection(srv, con, p);
  
  log_error_write(srv, __FILE__, __LINE__, "ssb", "shib: ", "CLEAN URL : ", con->uri.path);
    
  try {
    saml::NDC ndc("mod_shib_uri_handler");
    ShibTargetLighty sta(srv, con, p);
      
    // Check user authentication and export information, then set the handler bypass
    pair<bool,void*> res = sta.doCheckAuthN();
    if (res.first) {
      log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthN caught request");

      switch((int)res.second) {
      case SHIB_RETURN_OK:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthN returned OK.");
        return HANDLER_GO_ON;

      case SHIB_RETURN_KO:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthN returned KO.");
        return HANDLER_GO_ON;

      case SHIB_RETURN_DONE:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthN returned FINISHED.");
        return HANDLER_FINISHED;        

      default:
        log_error_write(srv, __FILE__, __LINE__, "ssd", "shib: ", "doCheckAuthN returned something strange: ", (int)res.second);
        return HANDLER_ERROR;
        
      }
    } else {
      log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthN passed request.");
    }

      
    // User auth was okay -- export the assertions now
    res = sta.doExportAssertions();
    if (res.first) {
      log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doExportAssertions caught request");
        
      switch((int)res.second) {
      case SHIB_RETURN_OK:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doExportAssertions returned OK.");
        return HANDLER_GO_ON;
          
      case SHIB_RETURN_KO:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doExportAssertions returned KO.");
        return HANDLER_GO_ON;

      case SHIB_RETURN_DONE:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doExportAssertions returned FINISHED.");
        return HANDLER_FINISHED;        

      default:
        log_error_write(srv, __FILE__, __LINE__, "ssd", "shib: ", "doExportAssertions returned something strange: ", (int)res.second);
        return HANDLER_ERROR;
        
      }
    } else {
      log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doExportAssertions passed request.");
    }


    // Check authorization
    res = sta.doCheckAuthZ();
    if (res.first) {
      log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthZ caught request");
        
      switch((int)res.second) {
      case SHIB_RETURN_OK:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthZ returned OK.");
        return HANDLER_GO_ON;
          
      case SHIB_RETURN_KO:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthZ returned KO.");
        return HANDLER_GO_ON;

      case SHIB_RETURN_DONE:
        log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthZ returned FINISHED.");
        return HANDLER_FINISHED;        

      default:
        log_error_write(srv, __FILE__, __LINE__, "ssd", "shib: ", "doCheckAuthZ returned something strange: ", (int)res.second);
        return HANDLER_ERROR;
        
      }
    } else {
      log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "doCheckAuthZ passed request.");
    }

  } catch (SAMLException& e) {
    log_error_write(srv, __FILE__, __LINE__, "sss", "shib: ", "mod_shib_uri_handler threw an exception: ", e.what());
    return HANDLER_ERROR;

  } catch (...) {
    log_error_write(srv, __FILE__, __LINE__, "ss", "shib: ", "mod_shib_uri_handler threw an uncaught exception!");
    return HANDLER_ERROR;

  }
    
  /* not found */
  return HANDLER_GO_ON;
}

/* this function is called at dlopen() time and inits the callbacks */

extern "C" int mod_shib_plugin_init(plugin *p) {

  p->version     = LIGHTTPD_VERSION_ID;
  p->name        = buffer_init_string("shib");

  p->init        = mod_shib_init;
  p->handle_uri_clean  = mod_shib_uri_handler;
  p->handle_uri_raw = mod_shib_raw_handler;
  p->set_defaults  = mod_shib_set_defaults;
  p->cleanup     = mod_shib_free;

  p->data        = NULL;

  return 0;
}

Attachment: signature.asc
Description: OpenPGP digital signature




Archive powered by MHonArc 2.6.16.

Top of Page