]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Add BConsole Authentication Plugin framework and a LDAP Plugin.
authorRadoslaw Korzeniewski <radoslaw@korzeniewski.net>
Tue, 13 Oct 2020 10:04:57 +0000 (12:04 +0200)
committerEric Bollengier <eric@baculasystems.com>
Thu, 24 Mar 2022 08:02:58 +0000 (09:02 +0100)
30 files changed:
bacula/Makefile.in
bacula/src/console/authenticate.c
bacula/src/dird/Makefile.in
bacula/src/dird/authenticate.c
bacula/src/dird/dir_authplugin.c [new file with mode: 0644]
bacula/src/dird/dir_authplugin.h [new file with mode: 0644]
bacula/src/dird/dir_plugins.c
bacula/src/dird/dir_plugins.h
bacula/src/dird/dird.c
bacula/src/dird/dird_conf.c
bacula/src/dird/dird_conf.h
bacula/src/dird/job.c
bacula/src/dird/protos.h
bacula/src/dird/ua_server.c
bacula/src/jcr.h
bacula/src/lib/authenticatebase.cc
bacula/src/lib/authenticatebase.h
bacula/src/plugins/dir/Makefile.in
bacula/src/plugins/dir/dirpluglib.c [new file with mode: 0644]
bacula/src/plugins/dir/dirpluglib.h [new file with mode: 0644]
bacula/src/plugins/dir/ldap/src/.gitignore [new file with mode: 0644]
bacula/src/plugins/dir/ldap/src/Makefile.in [new file with mode: 0644]
bacula/src/plugins/dir/ldap/src/ldap-dir.c [new file with mode: 0644]
bacula/src/plugins/dir/ldap/src/ldap-dir.h [new file with mode: 0644]
bacula/src/plugins/dir/ldap/src/ldap-util.c [new file with mode: 0644]
bacula/src/plugins/dir/ldap/src/ldap-util.h [new file with mode: 0644]
bacula/src/plugins/dir/ldap/src/ldaptest.c [new file with mode: 0644]
bacula/src/plugins/dir/test-authentication-api-dir.c [new file with mode: 0644]
bacula/src/plugins/fd/pluglib.c
bacula/src/plugins/fd/pluglib.h

index 10fb7e7f9a760af4232206e42a4a0df560ddb757..3772c4a9308f2494b856026da40972829869415d 100755 (executable)
@@ -25,7 +25,7 @@ fd_subdirs = src scripts src/lib src/findlib src/filed \
        @READLINE_SRC@ @BAT_DIR@ src/console @FD_PLUGIN_DIR@
 
 # Non-client-only directores
-subdirs =    src/cats @DIRD_DIR@ @STORED_DIR@ src/tools
+subdirs =    src/cats @DIRD_DIR@ @STORED_DIR@ src/tools @DIR_PLUGIN_DIR@
 
 all_subdirs = ${fd_subdirs} ${@ALL_DIRS@} manpages
 
index 9331cd2de38e4d1a845845a85c0347904801a91b..4157a2a63cf9ed7c1fe00965df1182772a21f07b 100644 (file)
  * Community
  *   prior to 06Aug13 no version
  *   100  14Feb17 - added comm line compression
+ *
+ * Starting from version 9.7 both Hellos become the same:
+ *    200  1/10/2020 - added `auth interactive` functionality
  */
 #ifndef COMMUNITY
 #define UA_VERSION 1   /* Enterprise */
 #else
-#define UA_VERSION 100 /* Community */
+#define UA_VERSION 200 /* Community */
 #endif
 
 void senditf(const char *fmt, ...);
@@ -69,9 +72,9 @@ public:
    virtual bool CheckTLSRequirement();
 
    int authenticate_director(DIRRES *director, CONRES *cons);
+   bool ClientAuthenticate(CONRES *cons, const char *password);
 };
 
-
 int authenticate_director(BSOCK *dir, DIRRES *director, CONRES *cons)
 {
    ConsoleAuthenticate auth(dir);
@@ -97,6 +100,117 @@ bool ConsoleAuthenticate::CheckTLSRequirement()
    return true;
 }
 
+bool ConsoleAuthenticate::ClientAuthenticate(CONRES *cons, const char *password)
+{
+   BSOCK *dir = bsock;
+   bool legacy = true;        // by default we should execute legacy authentication
+   POOL_MEM msg(PM_MESSAGE);
+
+   // at first handle optional startTLS
+   if (!ClientEarlyTLS()) {
+      return false;
+   }
+
+   // auth interactive is handled in named console only
+   if (cons){
+      // if starttls does not left our auth interactive command get it from director
+      if (!check_early_tls){
+         if (dir->recv() <= 0) {
+            bmicrosleep(5, 0); // original cram_md5_respond() wait for 5s here
+            return false;
+         }
+      }
+
+      // temporary buffer
+      pm_strcpy(msg, dir->msg);
+
+      // the check_early_tls=false means legacy authentication should do standard recv() else it can get command already in the bsock->msg
+      check_early_tls = true;
+
+      if (strncmp(msg.c_str(), UA_AUTH_INTERACTIVE, strlen(UA_AUTH_INTERACTIVE)) == 0){
+         // do challenge/response chatting
+         check_early_tls = false;   // "tell" cram_md5_respond to do a recv()
+         legacy = false;            // we do the authentication
+
+         // first display a welcome string if available
+         char *welcome = msg.c_str() + strlen(UA_AUTH_INTERACTIVE) + 1;
+         if (strlen(welcome) > 2){
+            sendit("> ");
+            sendit(welcome);
+         }
+
+         for (;;){
+            // loop over operations
+            if (dir->recv() <= 0) {
+               bmicrosleep(5, 0); // original cram_md5_respond() wait for 5s here
+               return false;
+            }
+
+            pm_strcpy(msg, NULL);
+            pm_strcpy(msg, dir->msg + 1);
+            strip_trailing_junk(msg.c_str());
+
+            char *data = msg.c_str();
+            POOL_MEM buf(PM_MESSAGE);
+            char *passwd;
+            char *input;
+
+            // the command is encoded as a first char of the message
+            switch (dir->msg[0]){
+               case UA_AUTH_INTERACTIVE_PLAIN:
+                  sendit(data);
+                  input = fgets(buf.c_str(), buf.size(), stdin);
+                  if (!input){
+                     Dmsg0(1, "Error reading user input!\n");
+                     return false;
+                  }
+
+                  // now we should return it to director
+                  strip_trailing_junk(buf.c_str());
+                  dir->fsend("%s", buf.c_str());
+                  break;
+
+               case UA_AUTH_INTERACTIVE_HIDDEN:
+#if defined(HAVE_WIN32)
+                  sendit(data);
+                  if (win32_cgets(buf.c_str(), buf.size()) == NULL) {
+                     buf[0] = 0;
+                     return 0;
+                  } else {
+                     return strlen(buf);
+                  }
+#else
+                  passwd = getpass(data);
+                  bstrncpy(buf.c_str(), passwd, buf.size());
+#endif
+                  // now we should get a hidden response at `buf` class, return it to director
+                  dir->fsend("%s", buf.c_str());
+                  break;
+
+               case UA_AUTH_INTERACTIVE_MESSAGE:
+                  sendit(data);
+                  sendit("\n");
+                  break;
+
+               case UA_AUTH_INTERACTIVE_FINISH:
+                  return true;
+
+               default:
+                  bmicrosleep(5, 0); // original cram_md5_respond() wait for 5s here
+                  return false;
+            }
+         }
+      }
+   }
+
+   /* standard md5 handle */
+   if (legacy && !ClientCramMD5AuthenticateBase(password)) {
+      return false;
+   }
+
+   return true;
+}
+
 /*
  * Authenticate Director
  */
@@ -106,6 +220,7 @@ int ConsoleAuthenticate::authenticate_director(DIRRES *director, CONRES *cons)
    int dir_version = 0;
    char bashed_name[MAX_NAME_LENGTH];
    bool skip_msg = false;
+
    /*
     * Send my name to the Director then do authentication
     */
@@ -128,7 +243,7 @@ int ConsoleAuthenticate::authenticate_director(DIRRES *director, CONRES *cons)
 
    dir->fsend(hello, bashed_name, UA_VERSION, tlspsk_local_need);
 
-   if (!ClientCramMD5Authenticate(password)) {
+   if (!ClientAuthenticate(cons, password)){
       if (dir->is_timed_out()) {
          sendit(_("The Director is busy or the MaximumConsoleConnections limit is reached.\n"));
          skip_msg = true;
index 815318e6183b589bb3b670c52740e12ce19f3373..db40602729e1fea806c5a88ca59867f6bf29936a 100644 (file)
@@ -34,7 +34,8 @@ dummy:
 #
 SVRSRCS = dird.c admin.c authenticate.c \
          autoprune.c backup.c bsr.c \
-         catreq.c dir_plugins.c dird_conf.c expand.c \
+         catreq.c dir_plugins.c dir_authplugin.c \
+         dird_conf.c expand.c \
          fd_cmds.c getmsg.c inc_conf.c job.c \
          jobq.c mac.c mac_sql.c \
          mountreq.c msgchan.c next_vol.c newvol.c \
index c3b2ee147ce0f5b7f9aab4c13c058b56b3a1c44d..e40fdf49b2380fe03ec810403f3e8800165ad58f 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "bacula.h"
 #include "dird.h"
+#include "dir_authplugin.h"
 
 static const int dbglvl = 50;
 
@@ -235,6 +236,7 @@ public:
    }
 
    int authenticate_user_agent();
+   bool authenticate_with_plugin(CONRES * cons);
 };
 
 int authenticate_user_agent(UAContext *uac)
@@ -254,6 +256,7 @@ int UAAuthenticate::authenticate_user_agent()
    int tlspsk_remote = 0;
    bool fdcallsdir=false;
    CLIENT *cli=NULL;
+   bool legacy_auth = true;
 
    if (ua->msglen < 16 || ua->msglen >= MAX_NAME_LENGTH + 15) {
       Qmsg3(NULL, M_SECURITY, 0, _("UA Hello from %s:%s is invalid. Len=%d\n"), ua->who(),
@@ -323,13 +326,23 @@ int UAAuthenticate::authenticate_user_agent()
    }
    DecodeRemoteTLSPSKNeed(tlspsk_remote);
 
-   if (!ServerCramMD5Authenticate(password)) {
-      goto auth_done;
-   }
-
-   if (cons) {
+   // if user console
+   if (cons){
       uac->cons = cons;         /* save console resource pointer */
       bstrncpy(uac->name, uac->cons->hdr.name, sizeof(uac->name));
+
+      // require authentication and user agent is on proper version
+      if (cons->authenticationplugin){
+         legacy_auth = false;
+         Dmsg1(dbglvl, "authenticate with Plugin=%s\n", cons->authenticationplugin);
+         if (ua_version < UA_VERSION_PLUGINAUTH || !authenticate_with_plugin(cons)){
+            goto auth_done;
+         }
+      }
+   }
+
+   if (legacy_auth && !ServerCramMD5Authenticate(password)) {
+      goto auth_done;
    }
 
    this->auth_success = HandleTLS();
@@ -359,3 +372,65 @@ auth_done:
    }
    return 1;
 }
+
+/**
+ * @brief This function perform user authentication procedure augmented with Auth Plugin API.
+ *        All bconsole network chatting and interaction is forwarded to dir_authplugins module
+ *        and we just provide a required frameworka dn resources, i.e. jcr or bsock to ua.
+ *
+ * @param cons a Console resource required for auth plugin registration
+ * @return true when authentication process finish with success and we can proceed next operations
+ * @return false when some error raised or authentication was unsuccessful
+ */
+bool UAAuthenticate::authenticate_with_plugin(CONRES * cons)
+{
+   /*
+    * The user authentication procedure with Auth Plugins API follow like this:
+    * 1. get bDirAuthenticationRegister data which defines what and how we should proceed with authentication
+    * 2. if auth operation is not bDirAuthenticationOperationPluginAll (which points that you have to ask plugin for every auth operation you execute)
+    *    then iterate over returned data
+    * 3. the iteration goes like this:
+    *    a. send question to console or simple message do display if operation is bDirAuthenticationOperationMessage
+    *    b. receive response from console
+    *    c. forward response to plugin with plugin event
+    * 4. when all interactions were handled without problem then do authenticate with plugin
+    * 5. if plugin return authentication OK? return true, Not? return false
+    */
+
+   bDirAuthenticationRegister *authData;
+
+   authData = (bDirAuthenticationRegister*) dir_authplugin_getauthenticationData(uac->jcr, cons->authenticationplugin);
+   if (authData == NULL)
+   {
+      return false;
+   }
+
+   // do tls before real auth
+   if (!ServerEarlyTLS())
+   {
+      return false;
+   }
+
+   // send auth plugin start packet and optional welcome string to console
+   Dmsg1(dbglvl, "send: auth interactive %s\n", NPRT(authData->welcome));
+   if (!bsock->fsend("auth interactive %s\n", NPRTB(authData->welcome))) {
+      Dmsg1(dbglvl, "Send interactive start comm error. ERR=%s\n", bsock->bstrerror());
+      return false;
+   }
+
+   // now a console should know how to deal with it, so iterate over auth data
+   const bDirAuthenticationData *data = authData->data;
+
+   for (uint i = 0; i < authData->num; i++){
+      Dmsg1(dbglvl, "bDirAuthenticationData step %d\n", i);
+      if (dir_authplugin_do_interaction(uac->jcr, bsock, authData->name, (void *)&data[i]) != bRC_OK){
+         return false;
+      }
+   }
+
+   if (dir_authplugin_authenticate(uac->jcr, bsock, authData->name) != bRC_OK){
+      return false;
+   }
+
+   return true;
+}
\ No newline at end of file
diff --git a/bacula/src/dird/dir_authplugin.c b/bacula/src/dird/dir_authplugin.c
new file mode 100644 (file)
index 0000000..105c525
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Bacula authentication and authorization plugin framework.
+ *
+ * Author: RadosÅ‚aw Korzeniewski, MMXX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#include "bacula.h"
+#include "dird.h"
+#include "dir_authplugin.h"
+
+const int dbglvl = 500;
+
+// borrow a constant from main dir_plugins.c
+extern const char *plugin_type;
+
+/**
+ * @brief Prepares a consistent plugin name based on a differently formatted input strings.
+ *        First it checks for a plugin file name which ends with '-dir.so' defined in dir_plugins.c
+ *        Next it checks for plugin name colon ':' delimiter in plugin parameter string.
+ *        Finally it just copies the whole string as a plugin name.
+ *
+ * @param plugin a plugin name source string
+ * @return char* a prepared plugin name as a newly allocated string which caller has to free
+ */
+static char * prepare_authplugin_name(const char * plugin)
+{
+   char *buf = bstrdup(plugin);
+   char *p;
+
+   p = strstr(buf, plugin_type);
+
+   if (!p){
+      p = strchr(buf, ':');
+   }
+
+   if (p){
+      // prepare plugin name
+      *p = '\0';
+   }
+
+   return buf;
+}
+
+/**
+ * @brief Checks if selected `Plugin` is defined in Authentication Plugin parameter from `param`.
+ *        It is checking a plugin filename with console resource parameter string.
+ *
+ * @param plugin the `Plugin` class to check
+ * @param param the Authentication Plugin parameter from console resource
+ * @return true when it is an authentication plugin which matches request
+ * @return false when it is something other
+ */
+static bool is_authentication_plugin(Plugin *plugin, const char *param)
+{
+   char *pluginfile = prepare_authplugin_name(plugin->file);
+   char *pluginname = prepare_authplugin_name(param);
+   bool rc = 0;
+
+   if (pluginfile){
+      if (pluginname){
+         Dmsg2(dbglvl, "plugin: %s reqested: %s\n", pluginfile, pluginname);
+         if (strncmp(pluginfile, pluginname, strlen(pluginname)) == 0 &&
+               dirplug_func(plugin)->getPluginAuthenticationData){
+            rc = true;
+         }
+         free(pluginname);
+      }
+      free(pluginfile);
+   }
+
+   return rc;
+}
+
+/**
+ * @brief Generate a director auth plugin (BPAM) event.
+ *        The event is directed to a single plugin only as it could include sensitive authentication
+ *        data which should not be propagated to all. It is a main reason why it is a different
+ *        function and we do not use `generate_plugin_event()` from dir_plugins.c
+ *        It currently handle authentication plugins only, but it could change in the future.
+ *
+ * @param jcr job control record
+ * @param pluginname a name of the plugin which should handle the event
+ * @param eventType an event to generate
+ * @param value a generic value passed to plugin which handles an event
+ * @return bRC it could return a following values:
+ *    - bRC_Max when no plugins loaded
+ *    - bRC_Cancel - when job is canceled
+ *    - bRC_OK - on success, propagated directly from plugin handling function
+ *    - any other on error, propagated directly from plugin handling function
+ */
+static bRC dir_authplugin_generate_plugin_event(JCR *jcr, const char * pluginname, bDirEventsType eventType, void *value)
+{
+   bpContext *plugin_ctx;
+   bDirEvent event;
+   Plugin *plugin;
+   int i = 0;
+   bRC rc = bRC_OK;
+
+   if (!b_plugin_list || !jcr || !jcr->plugin_ctx_list) {
+      return bRC_Max;                  // Return if no plugins loaded.
+   }
+
+   if (jcr->is_job_canceled()) {
+      return bRC_Cancel;
+   }
+
+   bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
+   event.eventType = eventType;
+
+   foreach_alist_index(i, plugin, b_plugin_list) {
+      plugin_ctx = &plugin_ctx_list[i];
+      if (!is_authentication_plugin(plugin, pluginname)) {
+         continue;
+      }
+      if (dirplug_func(plugin)->handlePluginEvent){
+         rc = dirplug_func(plugin)->handlePluginEvent(plugin_ctx, &event, value);
+      }
+      break;
+   }
+
+   return rc;
+}
+
+/**
+ * @brief Return Authentication Data struct from selected plugin.
+ *        The Plugin should use Authentication Plugin parameter string from `param` variable
+ *        to prepare approaching authentication operations (events).
+ *        The returned struct (`bDirAuthenticationRegister`) will be used by Director to
+ *        perform required authentication operations.
+ *
+ * @param jcr job control record
+ * @param param a Console resource Authentication Plugin parameter string
+ * @return void* a `NULL` is returned on any error and a pointer to bDirAuthenticationRegister
+ *         struct (casted to void*) on success
+ */
+void * dir_authplugin_getauthenticationData(JCR *jcr, const char * param)
+{
+   bpContext *plugin_ctx;
+   Plugin *plugin;
+   int i = 0;
+   bDirAuthenticationRegister *data = NULL;
+
+   if (!b_plugin_list || !jcr || !jcr->plugin_ctx_list) {
+      return NULL;                  /* Return if no plugins loaded */
+   }
+
+   if (jcr->is_job_canceled()) {
+      return NULL;
+   }
+
+   bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
+
+   foreach_alist_index(i, plugin, b_plugin_list) {
+      plugin_ctx = &plugin_ctx_list[i];
+      if (!is_authentication_plugin(plugin, param)) {
+         continue;
+      }
+
+      if (dirplug_func(plugin)->getPluginAuthenticationData(plugin_ctx, param, (void**)&data) != bRC_OK){
+         // getting authdata operations failed
+         return NULL;
+      }
+
+      // check if data returned are valid
+      if (data->name == NULL ||
+          data->data == NULL ||
+          data->num == 0){
+         Dmsg1(1, "Invalid bDirAuthenticationRegister data for %s\n", plugin->file);
+         return NULL;
+      }
+
+      break;
+   }
+
+   return (void*)data;
+}
+
+/**
+ * @brief A main function to sending a message to bconsole using a valid `bsock`.
+ *
+ * @param bsock the `BSOCK` object for Director <-> bconsole communication
+ * @param type the message type to send
+ * @param message a message string to sent
+ * @return bRC bRC_OK on success, bRC_Error on any error
+ */
+static bRC dir_authplugin_display_message(BSOCK *bsock, const char type, const char *message)
+{
+   // display message
+   if (!bsock->fsend("%c%s\n", type, message)) {
+      Dmsg1(dbglvl, "Send auth message comm error. ERR=%s\n", bsock->bstrerror());
+      return bRC_Error;
+   }
+   return bRC_OK;
+}
+
+/**
+ * @brief Handles a response (data receive) from bconsole using a valid `bsock`.
+ *
+ * @param jcr job control record
+ * @param bsock the `BSOCK` object for Director <-> bconsole communication
+ * @param pluginname a name of the plugin which should receive an resoinse with plugin event
+ * @param seqdata the `seqdata` variable for current authentication operation (basically from bDirAuthenticationData struct)
+ * @return bRC bRC_OK on success, bRC_Error on any error
+ */
+static bRC dir_authplugin_handle_response(JCR *jcr, BSOCK *bsock, const char *pluginname, const uint32_t seqdata)
+{
+   bDirAuthValue value;
+
+   // get response
+   if (bsock->wait_data(180) <= 0 || bsock->recv() <= 0) {
+      Dmsg1(dbglvl, "Receive auth response comm error. ERR=%s\n", bsock->bstrerror());
+      bmicrosleep(5, 0);
+      return bRC_Error;
+   }
+
+   // forward response to plugin
+   value.seqdata = seqdata;
+   value.response = bsock->msg;
+   return dir_authplugin_generate_plugin_event(jcr, pluginname, bDirEventAuthenticationResponse, (void*)&value);
+}
+
+/**
+ * @brief Perform a final BPAM authenticate with a Plugin.
+ *
+ * @param jcr job control record
+ * @param bsock the `BSOCK` object for Director <-> bconsole communication
+ * @param pluginname a name of the plugin which should handle authentication
+ * @return bRC bRC_OK on success - authentication OK, bRC_Error on any error
+ */
+bRC dir_authplugin_authenticate(JCR *jcr, BSOCK *bsock, const char *pluginname)
+{
+   // sanity check
+   if (!jcr || !bsock || !pluginname){
+      Dmsg3(1, "Invalid input parameters: %p %p %p %p\n", jcr, bsock, pluginname);
+      return bRC_Error;
+   }
+
+   if (dir_authplugin_display_message(bsock, UA_AUTH_INTERACTIVE_FINISH, "") != bRC_OK){
+      return bRC_Error;
+   }
+
+   return dir_authplugin_generate_plugin_event(jcr, pluginname, bDirEventAuthenticate, NULL);
+}
+
+/**
+ * @brief Perform BPAM authentication interaction with a user/bconsole nased on authentication data operation
+ *
+ * @param jcr job control record
+ * @param bsock the `BSOCK` object for Director <-> bconsole communication
+ * @param pluginname a name of the plugin which should handle authentication interaction
+ * @param data a pointer to the bDirAuthenticationData single authentication operation
+ * @param pluginall if set to true then we are called from a recursive bDirAuthenticationOperationPluginAll
+ * @return bRC bRC_OK on success - operation OK, bRC_Error on any error
+ */
+bRC dir_authplugin_do_interaction(JCR *jcr, BSOCK *bsock, const char *pluginname, void *data, bool pluginall)
+{
+   bDirAuthenticationData *authData = (bDirAuthenticationData *)data;
+   bDirAuthValue value;
+
+   // sanity check
+   if (!jcr || !bsock || !pluginname || !data){
+      Dmsg4(1, "Invalid input parameters: %p %p %p %p\n", jcr, bsock, pluginname, data);
+      return bRC_Error;
+   }
+
+   switch (authData->operation){
+      case bDirAuthenticationOperationPlugin:
+         // ask plugin about operation to execute
+         value.seqdata = authData->seqdata;
+         if (dir_authplugin_generate_plugin_event(jcr, pluginname, bDirEventAuthenticationQuestion, (void *)&value) != bRC_OK){
+            // error
+            Dmsg1(dbglvl, "Error getting question from plugin: %s\n", pluginname);
+            return bRC_Error;
+         }
+         return dir_authplugin_do_interaction(jcr, bsock, pluginname, (void*)value.authdata, pluginall);
+
+      case bDirAuthenticationOperationPluginAll:
+         // we will handle all interaction if no bDirAuthenticationOperationPluginAll was generated before
+         if (!pluginall){
+            while (dir_authplugin_generate_plugin_event(jcr, pluginname, bDirEventAuthenticationQuestion, (void *)&value) == bRC_OK){
+               // do interaction with operation from plugin and mark it is during pluginall operation
+               if (dir_authplugin_do_interaction(jcr, bsock, pluginname, (void*)value.authdata, true) != bRC_OK){
+                  return bRC_Error;
+               }
+            }
+         } else {
+            // we do not support bDirAuthenticationOperationPluginAll during bDirAuthenticationOperationPluginAll operation
+            return bRC_Error;
+         }
+         break;
+
+      case bDirAuthenticationOperationMessage:
+         // display message
+         return dir_authplugin_display_message(bsock, UA_AUTH_INTERACTIVE_MESSAGE, authData->question);
+
+      case bDirAuthenticationOperationPlain:
+         // display message
+         if (dir_authplugin_display_message(bsock, UA_AUTH_INTERACTIVE_PLAIN, authData->question) != bRC_OK){
+            return bRC_Error;
+         }
+         return dir_authplugin_handle_response(jcr, bsock, pluginname, authData->seqdata);
+
+      case bDirAuthenticationOperationHidden:
+         // display message
+         if (dir_authplugin_display_message(bsock, UA_AUTH_INTERACTIVE_HIDDEN, authData->question) != bRC_OK){
+            return bRC_Error;
+         }
+         return dir_authplugin_handle_response(jcr, bsock, pluginname, authData->seqdata);
+
+      case bDirAuthenticationOperationAuthenticate:
+         return bRC_OK;
+
+      default:
+         return bRC_Error;
+   }
+
+   return bRC_OK;
+}
diff --git a/bacula/src/dird/dir_authplugin.h b/bacula/src/dird/dir_authplugin.h
new file mode 100644 (file)
index 0000000..8380cd8
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Bacula authentication and authorization plugin framework.
+ *
+ * Author: RadosÅ‚aw Korzeniewski, MMXX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#ifndef __DIR_AUTHPLUGINS_H
+#define __DIR_AUTHPLUGINS_H
+
+#ifndef _BACULA_H
+#ifdef __cplusplus
+/* Workaround for SGI IRIX 6.5 */
+#define _LANGUAGE_C_PLUS_PLUS 1
+#endif
+#define _REENTRANT    1
+#define _THREAD_SAFE  1
+#define _POSIX_PTHREAD_SEMANTICS 1
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE_SOURCE 1
+#define _LARGE_FILES 1
+#endif
+
+#include <sys/types.h>
+#ifndef __CONFIG_H
+#define __CONFIG_H
+#include "config.h"
+#endif
+#include "bc_types.h"
+#include "lib/plugins.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/****************************************************************************
+ *                                                                          *
+ *                Bacula definitions                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+/**
+ * @brief This is a list of supported auth plugin framework operations.
+ */
+typedef enum {
+   bDirAuthenticationOperationPlugin,
+   bDirAuthenticationOperationPluginAll,
+   bDirAuthenticationOperationMessage,
+   bDirAuthenticationOperationPlain,
+   bDirAuthenticationOperationLogin = bDirAuthenticationOperationPlain,
+   bDirAuthenticationOperationHidden,
+   bDirAuthenticationOperationPassword = bDirAuthenticationOperationHidden,
+   bDirAuthenticationOperationAuthenticate,
+} bDirAuthenticationOperation;
+
+/**
+ * @brief
+ */
+typedef struct s_bDirAuthenticationData {
+   const bDirAuthenticationOperation operation;
+   const char * question;
+   const uint32_t seqdata;
+} bDirAuthenticationData;
+
+typedef struct s_bDirAuthenticationRegister {
+   const char * name;
+   const char * welcome;
+   const uint32_t num;
+   const bDirAuthenticationData *data;
+   const int32_t nsTTL;
+} bDirAuthenticationRegister;
+
+typedef struct s_bDirAuthenticationValue {
+   union {
+      void * rawdata;                              // future extension
+      const bDirAuthenticationData * authdata;     // a pointer to single bDirAuthenticationData as a dynamic operation
+      const char *response;
+   };
+   uint32_t seqdata;
+} bDirAuthValue;
+
+typedef struct s_bDirAuthorizationACLRegister {
+   const char * name;
+   // *TBD* - Incomplete
+   const int32_t nsTTL;
+} bDirAuthorizationACLRegister;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DIR_AUTHPLUGINS_H */
index 9832eec06dcf7cd2ddcdf5587d1a58908128cfd5..5fee8e76943a79b3beb456812400cc62a37aa941 100644 (file)
@@ -461,6 +461,10 @@ static bRC baculaRegisterEvents(bpContext *ctx, ...)
    va_start(args, ctx);
    while ((event = va_arg(args, uint32_t))) {
       Dmsg1(dbglvl, "dir-Plugin wants event=%u\n", event);
+      switch(event){
+      default:
+         Dmsg1(dbglvl, "event=%u not registered\n", event);
+      }
    }
    va_end(args);
    return bRC_OK;
index 0638a60366c9312174e8ab6f2621bb81726e21d5..6dc9b20392835277129fab442368eceed0d05094 100644 (file)
@@ -94,18 +94,21 @@ typedef enum {
   bwDirVarJobLevel   = 4
 } bwDirVariable;
 
-
 typedef enum {
-  bDirEventJobStart      = 1,
-  bDirEventJobEnd        = 2,
-  bDirEventJobInit       = 3,
-  bDirEventJobRun        = 4,
-  bDirEventVolumePurged  = 5,
-  bDirEventNewVolume     = 6,
-  bDirEventNeedVolume    = 7,
-  bDirEventVolumeFull    = 8,
-  bDirEventRecyle        = 9,
-  bDirEventGetScratch    = 10
+  bDirEventJobStart                    = 1,
+  bDirEventJobEnd                      = 2,
+  bDirEventJobInit                     = 3,
+  bDirEventJobRun                      = 4,
+  bDirEventVolumePurged                = 5,
+  bDirEventNewVolume                   = 6,
+  bDirEventNeedVolume                  = 7,
+  bDirEventVolumeFull                  = 8,
+  bDirEventRecyle                      = 9,
+  bDirEventGetScratch                  = 10,
+  bDirEventAuthenticationQuestion      = 1000,     // *value is a bDirAuthValue struct allocated by Dir
+                                                   // to get return value from
+  bDirEventAuthenticationResponse      = 1001,     // *value is a char* to user response
+  bDirEventAuthenticate                = 1002,     // return bRC_OK when authenticate is successful
 } bDirEventsType;
 
 typedef struct s_bDirEvent {
@@ -165,7 +168,8 @@ typedef struct s_dirpluginInfo {
    const char *plugin_description;
 } pDirInfo;
 
-typedef struct s_dirpluginFuncs {
+typedef struct s_dirpluginFuncs
+{
    uint32_t size;
    uint32_t version;
    bRC (*newPlugin)(bpContext *ctx);
@@ -173,6 +177,8 @@ typedef struct s_dirpluginFuncs {
    bRC (*getPluginValue)(bpContext *ctx, pDirVariable var, void *value);
    bRC (*setPluginValue)(bpContext *ctx, pDirVariable var, void *value);
    bRC (*handlePluginEvent)(bpContext *ctx, bDirEvent *event, void *value);
+   bRC (*getPluginAuthenticationData)(bpContext *ctx, const char *param, void **data);
+   bRC (*getPluginAuthorizationData)(bpContext *ctx, const char *param, void **data);
 } pDirFuncs;
 
 #define dirplug_func(plugin) ((pDirFuncs *)(plugin->pfuncs))
index 5e37b6030c92336ae4d891756326471481d9234c..08e06a8a22e99ea0eae2496b2478bf7eeb8d293a 100644 (file)
@@ -130,7 +130,7 @@ static BDB *dir_get_db(JCR *jcr)
    return db;
 }
 
-static bool dir_sql_log(JCR *jcr, JobId_t jobid, utime_t mtime, char *msg) 
+static bool dir_sql_log(JCR *jcr, JobId_t jobid, utime_t mtime, char *msg)
 {
    bool ret;
    BDB *db = dir_get_db(jcr);
@@ -144,7 +144,7 @@ static bool dir_sql_log(JCR *jcr, JobId_t jobid, utime_t mtime, char *msg)
       db_close_database(jcr, db);             /* It was open just for us */
    }
    return ret;
-} 
+}
 
 static bool dir_sql_event(JCR *jcr, utime_t mtime, char *line)
 {
@@ -167,8 +167,8 @@ bail_out:
    if (!jcr || !jcr->db) {
       db_close_database(jcr, db);             /* It was open just for us */
    }
-   return ret; 
-} 
+   return ret;
+}
 
 static void usage()
 {
@@ -1153,7 +1153,7 @@ static bool check_resources()
       /* Make sure the job doesn't use the Scratch Pool to start with */
       const char *name;
       if (!check_pool(job->JobType, job->JobLevel,
-                      job->pool, job->next_pool, &name)) { 
+                      job->pool, job->next_pool, &name)) {
          Jmsg(NULL, M_FATAL, 0,
               _("%s \"Scratch\" not valid in Job \"%s\".\n"),
               name, job->name());
@@ -1319,7 +1319,7 @@ static bool check_resources()
          case COLLECTOR_BACKEND_CSV:
             /* a CSV backend require a file parameter */
             if (!collect->file){
-               Jmsg(NULL, M_FATAL, 0, _("File parameter required in Collector CSV resource \"%s\".\n"), 
+               Jmsg(NULL, M_FATAL, 0, _("File parameter required in Collector CSV resource \"%s\".\n"),
                      collect->hdr.name);
                OK = false;
             }
@@ -1327,7 +1327,7 @@ static bool check_resources()
          case COLLECTOR_BACKEND_Graphite:
             /* we require a host parameter at least */
             if (!collect->host){
-               Jmsg(NULL, M_FATAL, 0, _("Host parameter required in Collector Graphite resource \"%s\".\n"), 
+               Jmsg(NULL, M_FATAL, 0, _("Host parameter required in Collector Graphite resource \"%s\".\n"),
                      collect->hdr.name);
                OK = false;
             }
@@ -1345,7 +1345,7 @@ static bool check_resources()
 }
 
 
-/* Take note of all jobs to be canceled when we start the director 
+/* Take note of all jobs to be canceled when we start the director
  * We don't send events directly here because the events might also
  * be sent to the Catalog. Better to return a list of message.
  */
@@ -1383,13 +1383,13 @@ static bool check_catalog(cat_op mode)
        * Make sure we can open catalog, otherwise print a warning
        * message because the server is probably not running.
        */
-      db = db_init_database(NULL, catalog->db_driver, catalog->db_name, 
+      db = db_init_database(NULL, catalog->db_driver, catalog->db_name,
               catalog->db_user,
               catalog->db_password, catalog->db_address,
               catalog->db_port, catalog->db_socket,
               catalog->db_ssl_mode, catalog->db_ssl_key,
               catalog->db_ssl_cert, catalog->db_ssl_ca,
-              catalog->db_ssl_capath, catalog->db_ssl_cipher,           
+              catalog->db_ssl_capath, catalog->db_ssl_cipher,
               true /* mult_db_connections */,     /* Avoid issue with counters during a reload */
               catalog->disable_batch_insert);
 
index 98a2be3fdff70b27fd60d97e9f0935f1b2ecd4bd..aa002ac7785930cde96d2d394479401827c0a0ea 100644 (file)
@@ -438,6 +438,8 @@ static RES_ITEM con_items[] = {
    {"TlsKey",               store_dir,       ITEM(res_con.tls_keyfile), 0, 0, 0},
    {"TlsDhFile",            store_dir,       ITEM(res_con.tls_dhfile), 0, 0, 0},
    {"TlsAllowedCn",         store_alist_str, ITEM(res_con.tls_allowed_cns), 0, 0, 0},
+   {"AuthenticationPlugin",   store_str,     ITEM(res_con.authenticationplugin), 0, 0, 0},
+   {"AuthorizationPlugin",    store_str,     ITEM(res_con.authorizationplugin), 0, 0, 0},
    {NULL, NULL, {0}, 0, 0, 0}
 };
 
@@ -941,6 +943,12 @@ void dump_resource(int type, RES *ares, void sendit(void *sock, const char *fmt,
       }
       sendit(sock, _("Console: name=%s SSL=%d PSK=%d\n"),
          res->res_con.hdr.name, res->res_con.tls_enable, res->res_con.tls_psk_enable);
+      if (res->res_con.authenticationplugin){
+         sendit(sock, _("      Authentication=%s\n"), res->res_con.authenticationplugin);
+      }
+      if (res->res_con.authorizationplugin){
+         sendit(sock, _(" (!)  Authorization=%s\n"), res->res_con.authorizationplugin);
+      }
       for (int acl=0; acl<Num_ACL; acl++) {
          if (res->res_con.ACL_lists[acl]==NULL) {
             continue;
@@ -1317,7 +1325,7 @@ void dump_resource(int type, RES *ares, void sendit(void *sock, const char *fmt,
          int i;
          RUN *run = res->res_sch.run;
          char buf[1000], num[30];
-         sendit(sock, _("Schedule: Name=%s Enabled=%d\n"), 
+         sendit(sock, _("Schedule: Name=%s Enabled=%d\n"),
             res->res_sch.hdr.name, res->res_sch.is_enabled());
          if (!run) {
             break;
@@ -1659,6 +1667,12 @@ void free_resource(RES *rres, int type)
             res->res_con.ACL_lists[i] = NULL;
          }
       }
+      if (res->res_con.authenticationplugin) {
+         free(res->res_con.authenticationplugin);
+      }
+      if (res->res_con.authorizationplugin) {
+         free(res->res_con.authorizationplugin);
+      }
       break;
    case R_CLIENT:
       if (res->res_client.client_address) {
@@ -2045,7 +2059,7 @@ bool save_resource(CONFIG *config, int type, RES_ITEM *items, int pass)
          res->res_store.shared_storage = res_all.res_store.shared_storage;
          res->res_store.autochanger = res_all.res_store.autochanger;
          /* The resource name is Autochanger instead of Storage
-          * so we force the Autochanger attributes 
+          * so we force the Autochanger attributes
           */
          if (strcasecmp(resources[rindex].name, "autochanger") == 0) {
             /* The Autochanger resource might be already defined */
index 8d0700c7400c23ced2b1ad196620728edb9a5cc3..dbb4e11801cb08df8c8a44ad6d4299e4183905cc 100644 (file)
@@ -198,21 +198,23 @@ enum {
 class CONRES {
 public:
    RES   hdr;
-   char *password;                    /* UA server password */
-   alist *ACL_lists[Num_ACL];         /* pointers to ACLs */
-   char *tls_ca_certfile;             /* TLS CA Certificate File */
-   char *tls_ca_certdir;              /* TLS CA Certificate Directory */
-   char *tls_certfile;                /* TLS Server Certificate File */
-   char *tls_keyfile;                 /* TLS Server Key File */
-   char *tls_dhfile;                  /* TLS Diffie-Hellman Parameters */
-   alist *tls_allowed_cns;            /* TLS Allowed Clients */
-   TLS_CONTEXT *tls_ctx;              /* Shared TLS Context */
-   TLS_CONTEXT *psk_ctx;              /* Shared TLS-PSK Context */
-   bool tls_authenticate;             /* Authenticated with TLS */
-   bool tls_enable;                   /* Enable TLS */
-   bool tls_psk_enable;               /* Enable TLS-PSK */
-   bool tls_require;                  /* Require TLS */
-   bool tls_verify_peer;              /* TLS Verify Client Certificate */
+   char *password;                     /* UA server password */
+   alist *ACL_lists[Num_ACL];          /* pointers to ACLs */
+   char *tls_ca_certfile;              /* TLS CA Certificate File */
+   char *tls_ca_certdir;               /* TLS CA Certificate Directory */
+   char *tls_certfile;                 /* TLS Server Certificate File */
+   char *tls_keyfile;                  /* TLS Server Key File */
+   char *tls_dhfile;                   /* TLS Diffie-Hellman Parameters */
+   alist *tls_allowed_cns;             /* TLS Allowed Clients */
+   TLS_CONTEXT *tls_ctx;               /* Shared TLS Context */
+   TLS_CONTEXT *psk_ctx;               /* Shared TLS-PSK Context */
+   bool tls_authenticate;              /* Authenticated with TLS */
+   bool tls_enable;                    /* Enable TLS */
+   bool tls_psk_enable;                /* Enable TLS-PSK */
+   bool tls_require;                   /* Require TLS */
+   bool tls_verify_peer;               /* TLS Verify Client Certificate */
+   char *authenticationplugin;         /* Authentication Plugin (user/password) */
+   char *authorizationplugin;          /* Authorization Plugin (permissions) */
 
    /* Methods */
    char *name() const;
index e67a32091c3a0a637ca032c267b69e8c21d8a87a..0e9519a39d3f6ac9554d8a09058c064437a7b8e8 100644 (file)
@@ -121,7 +121,7 @@ bool setup_job(JCR *jcr)
                               jcr->catalog->db_socket,
                               jcr->catalog->db_ssl_mode,
                               jcr->catalog->db_ssl_key, jcr->catalog->db_ssl_cert,
-                              jcr->catalog->db_ssl_ca, jcr->catalog->db_ssl_capath, 
+                              jcr->catalog->db_ssl_ca, jcr->catalog->db_ssl_capath,
                               jcr->catalog->db_ssl_cipher,
                               jcr->catalog->mult_db_connections,
                               jcr->catalog->disable_batch_insert);
@@ -291,7 +291,7 @@ static bool setup_resume_job(JCR *jcr, JOB_DBR *jr)
                               jcr->catalog->db_socket,
                               jcr->catalog->db_ssl_mode,
                               jcr->catalog->db_ssl_key, jcr->catalog->db_ssl_cert,
-                              jcr->catalog->db_ssl_ca, jcr->catalog->db_ssl_capath, 
+                              jcr->catalog->db_ssl_ca, jcr->catalog->db_ssl_capath,
                               jcr->catalog->db_ssl_cipher,
                               jcr->catalog->mult_db_connections,
                               jcr->catalog->disable_batch_insert);
@@ -713,7 +713,7 @@ cancel_job(UAContext *ua, JCR *jcr, int wait,  bool cancel)
                    "%s jobid=%s job=%s",
                    cancel?"cancel":"stop",
                    edit_uint64(jcr->JobId, ed1), jcr->Job);
-   
+
    if (!cancel) {               /* stop the job */
       if (!jcr->can_be_stopped()) {
          ua->error_msg(_("Cannot stop JobId %s, Job %s is not a regular Backup Job\n"),
index ea175fcd2e2631be609822c3c5ef5f87c859b68c..49189b4d8ffdd29f94490e91fd4142f4c6bb7a37 100644 (file)
@@ -390,3 +390,10 @@ bool catreq_get_pool_info(JCR *jcr, BSOCK *bs);
 # define mark_access_denied(a)
 #endif
 
+/* dedup_util.c */
+bool is_dedup_ref(DEV_RECORD *rec, bool lazy);
+
+/* dir_authplugins.c */
+void *dir_authplugin_getauthenticationData(JCR *jcr, const char *param);
+bRC dir_authplugin_do_interaction(JCR *jcr, BSOCK *bsock, const char *pluginname, void *data, bool pluginall = false);
+bRC dir_authplugin_authenticate(JCR *jcr, BSOCK *bsock, const char *pluginname);
index 7d892115a8df2c26c3adb335ff7eec84da6ab04e..17e18488bacdd0144609707060e59558cfba2d11 100644 (file)
@@ -130,6 +130,9 @@ static void *handle_UA_client_request(void *arg)
 
    jcr = new_control_jcr("-Console-", JT_CONSOLE);
 
+   // create plugins for authentication/authorization handling
+   new_plugins(jcr);
+
    ua = new_ua_context(jcr);
    ua->UA_sock = user;
    set_jcr_in_tsd(INVALID_JCR);
index 2a4bff0c4f21f44bc9819294e8883262040f2d20..768a49f2f31dc17c0731759b4dd1c8cc362a6738 100644 (file)
@@ -422,7 +422,6 @@ public:
    bool dummy_jobmedia;               /* Dummy JobMedia written */
 #endif /* DIRECTOR_DAEMON */
 
-
 #ifdef FILE_DAEMON
    /* File Daemon specific part of JCR */
    BSOCK *sd_calls_client_bsock;      /* Socket used by SDCallsClient feature */
index a876ac555963891e52b6927da6d8acfa006f2193..7bd13200c01c3c2e647bcb04119924c9471f0fb9 100644 (file)
@@ -67,7 +67,7 @@ AuthenticateBase::~AuthenticateBase()
 }
 
 /*
- * 
+ *
  * LOCAL                    REMOTE
  * {TLS,PSK}                {TLS,PSK}
  * --------------------------------------------------
@@ -161,7 +161,7 @@ AuthenticateBase::~AuthenticateBase()
  * {REQUIRED,REQUIRED}     {REQUIRED,OK}         => t
  * {REQUIRED,REQUIRED}     {REQUIRED,NONE}       => t
  * {REQUIRED,REQUIRED}     {REQUIRED,REQUIRED}   => t
- * 
+ *
  */
 
 /* OK
@@ -198,7 +198,7 @@ int AuthenticateBase::TestTLSRequirement()
     * {NONE,NONE}             {NONE,REQUIRED}       => F  -- Remote
     * {NONE,NONE}             {REQUIRED,OK}         => F  -- Remote
     * {NONE,NONE}             {REQUIRED,NONE}       => F  -- Remote
-    * {NONE,NONE}             {REQUIRED,REQUIRED}   => F  
+    * {NONE,NONE}             {REQUIRED,REQUIRED}   => F
     */
    if (tls_local_need == BNET_TLS_NONE && psk_local_need == BNET_TLS_NONE
        &&
@@ -387,12 +387,18 @@ bool AuthenticateBase::ClientEarlyTLS()
 /* DIR is calling, DIR is the client */
 bool AuthenticateBase::ClientCramMD5Authenticate(const char *password)
 {
-   int compatible = true;
-
    if (!ClientEarlyTLS()) {
       return false;
    }
 
+   return ClientCramMD5AuthenticateBase(password);
+}
+
+/* the *Base version works without startTLS feature */
+bool AuthenticateBase::ClientCramMD5AuthenticateBase(const char *password)
+{
+   int compatible = true;
+
    if (((local_class == dcSD && remote_class == dcSD) ||
         (local_class == dcFD && remote_class == dcSD))) {
       if (jcr && job_canceled(jcr)) {
@@ -460,7 +466,7 @@ bool AuthenticateBase::ServerEarlyTLS()
       /* If both can speak TLS or PSK then send the "starttls" even if the
        * local requirement is not full filled. The client need to
        * know the server requirements too. Both will terminate the connection
-       * in HandleTLS if the requirement are not full filled 
+       * in HandleTLS if the requirement are not full filled
        */
       if (!bsock->fsend("starttls tlspsk=%d\n", tlspsk_local_need)) {
 // TODO tweak the error message
index c8d4fee00757847043939cb076953e80521204ba..123829f93d0b6ac9a2818a1115eef4ad1deb4bd5 100644 (file)
@@ -29,6 +29,14 @@ enum {
    TLS_REQ_ERR_REMOTE
 };
 
+#define UA_VERSION_PLUGINAUTH          200
+#define UA_AUTH_INTERACTIVE            "auth interactive"
+#define UA_AUTH_INTERACTIVE_PLAIN      'P'
+#define UA_AUTH_INTERACTIVE_HIDDEN     'H'
+#define UA_AUTH_INTERACTIVE_MESSAGE    'M'
+#define UA_AUTH_INTERACTIVE_FINISH     'F'
+#define UA_AUTH_INTERACTIVE_RESPONSE   'R'
+
 class AuthenticateBase
 {
    static const char *dc_short_name[6];
@@ -87,6 +95,7 @@ public:
    bool ServerEarlyTLS();
 
    bool ClientCramMD5Authenticate(const char *password);
+   bool ClientCramMD5AuthenticateBase(const char *password);
    bool ServerCramMD5Authenticate(const char *password);
 
    bool HandleTLS();
index c20b204769e6d56a0c6667161534f8c42a788e9a..ab7c586393b8b4753d95e0de3cd0638b80d58861 100644 (file)
@@ -13,12 +13,15 @@ DIRDIR=../../dird
 SRCDIR=../..
 LIBDIR=../../lib
 
+topdir = @BUILD_DIR@
+thisdir = src/plugins/dir
+
 .SUFFIXES:    .c .o .lo
 
 .c.lo:
        $(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I${SRCDIR} -I${DIRDIR} -DTEST_PROGRAM -c $<
 
-all: example-plugin-dir.la
+all: dirpluglib.lo
 
 example-plugin-dir.lo: example-plugin-dir.c ${DIRDIR}/dir_plugins.h
        $(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CFLAGS) -I../.. -I${DIRDIR} -c example-plugin-dir.c
@@ -26,10 +29,20 @@ example-plugin-dir.lo: example-plugin-dir.c ${DIRDIR}/dir_plugins.h
 example-plugin-dir.la: Makefile example-plugin-dir.lo
        $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -shared example-plugin-dir.lo -o $@ -rpath $(plugindir) -module -export-dynamic -avoid-version
 
+test-authentication-api-dir.lo: test-authentication-api-dir.c ${DIRDIR}/dir_plugins.h
+       $(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CFLAGS) -I../.. -I${DIRDIR} -c test-authentication-api-dir.c
+
+test-authentication-api-dir.la: Makefile test-authentication-api-dir.lo dirpluglib.lo
+       @echo "Linking $(@:.la=.so) ..."
+       $(NO_ECHO)$(LIBTOOL_LINK) --silent $(CXX) $(LDFLAGS) -shared $^ -o $@ -rpath $(plugindir) -module -export-dynamic -avoid-version
+
 install: all
        $(MKDIR) $(DESTDIR)$(plugindir)
-       $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) example-plugin-dir.la $(DESTDIR)$(plugindir)
-       $(RMF) $(DESTDIR)$(plugindir)/example-plugin-dir.la
+
+# $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) example-plugin-dir.la $(DESTDIR)$(plugindir)
+# $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) test-authentication-api-dir.la $(DESTDIR)$(plugindir)
+# $(RMF) $(DESTDIR)$(plugindir)/example-plugin-dir.la
+# $(RMF) $(DESTDIR)$(plugindir)/test-authentication-api-dir.la
 
 libtool-clean:
        find . -name '*.lo' -print | xargs $(LIBTOOL_CLEAN) $(RMF)
@@ -45,6 +58,10 @@ distclean: clean
 libtool-uninstall:
        $(LIBTOOL_UNINSTALL) $(RMF) $(DESTDIR)$(plugindir)/example-plugin-dir.la
 
+Makefile: Makefile.in $(topdir)/config.status
+       cd $(topdir) \
+         && CONFIG_FILES=$(thisdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
 uninstall: @LIBTOOL_UNINSTALL_TARGET@
 
 depend:
diff --git a/bacula/src/plugins/dir/dirpluglib.c b/bacula/src/plugins/dir/dirpluglib.c
new file mode 100644 (file)
index 0000000..8132744
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Common definitions and utility functions for Inteos plugins.
+ * Functions defines a common framework used in our utilities and plugins.
+ * This a Director Plugins flavor.
+ *
+ * Author: RadosÅ‚aw Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#include "dirpluglib.h"
+
+/* Pointers to Bacula functions used in plugins */
+extern bDirFuncs *bfuncs;
+extern bDirInfo *binfo;
+
+/* Events that are passed to plugin
+typedef enum {
+  bDirEventJobStart                    = 1,
+  bDirEventJobEnd                      = 2,
+  bDirEventJobInit                     = 3,
+  bDirEventJobRun                      = 4,
+  bDirEventVolumePurged                = 5,
+  bDirEventNewVolume                   = 6,
+  bDirEventNeedVolume                  = 7,
+  bDirEventVolumeFull                  = 8,
+  bDirEventRecyle                      = 9,
+  bDirEventGetScratch                  = 10,
+  bDirEventAuthenticationParam         = 1000,     // *value is a char* to console resource param value
+  bDirEventAuthorizationACLParam       = 1001,     // *value is a char* to console resource param value
+  bDirEventAuthenticationQuestion      = 1002,     // *value is a bDirAuthValue struct allocated by Dir
+                                                   // to get return value from
+  bDirEventAuthenticationResponse      = 1003,     // *value is a char* to user response
+  bDirEventAuthenticate                = 1004,     // return bRC_OK when authenticate is successful
+} bDirEventsType;
+*/
+
+const char *eventtype2str(bDirEvent *event){
+   switch (event->eventType){
+      case bDirEventJobStart:
+         return "bDirEventJobStart";
+      case bDirEventJobEnd:
+         return "bDirEventJobEnd";
+      case bDirEventJobInit:
+         return "bDirEventJobInit";
+      case bDirEventJobRun:
+         return "bDirEventJobRun";
+      case bDirEventVolumePurged:
+         return "bDirEventVolumePurged";
+      case bDirEventNewVolume:
+         return "bDirEventNewVolume";
+      case bDirEventNeedVolume:
+         return "bDirEventNeedVolume";
+      case bDirEventVolumeFull:
+         return "bDirEventVolumeFull";
+      case bDirEventRecyle:
+         return "bDirEventRecyle";
+      case bDirEventGetScratch:
+         return "bDirEventGetScratch";
+      case bDirEventAuthenticationQuestion:
+         return "bDirEventAuthenticationQuestion";
+      case bDirEventAuthenticationResponse:
+         return "bDirEventAuthenticationResponse";
+      case bDirEventAuthenticate:
+         return "bDirEventAuthenticate";
+      default:
+         return "Unknown";
+   }
+}
+
+/*
+ * Return the real size of the disk based on the size suffix.
+ *
+ * in:
+ *    disksize - the numeric value of the disk size to compute
+ *    suff - the suffix for a disksize value
+ * out:
+ *    uint64_t - the size of the disk computed with suffix
+ */
+uint64_t pluglib_size_suffix(int disksize, char suff)
+{
+   uint64_t size;
+
+   switch (suff){
+      case 'G':
+         size = (uint64_t)disksize * 1024 * 1048576;
+         break;
+      case 'M':
+         size = (uint64_t)disksize * 1048576;
+         break;
+      case 'T':
+         size = (uint64_t)disksize * 1048576 * 1048576;
+         break;
+      case 'K':
+      case 'k':
+         size = (uint64_t)disksize * 1024;
+         break;
+      default:
+         size = disksize;
+   }
+   return size;
+}
+
+/*
+ * Return the real size of the disk based on the size suffix.
+ *    This version uses a floating point numbers (double) for computation.
+ *
+ * in:
+ *    disksize - the numeric value of the disk size to compute
+ *    suff - the suffix for a disksize value
+ * out:
+ *    uint64_t - the size of the disk computed with suffix
+ */
+uint64_t pluglib_size_suffix(double disksize, char suff)
+{
+   uint64_t size;
+
+   switch (suff){
+      case 'G':
+         size = disksize * 1024.0 * 1048576.0;
+         break;
+      case 'M':
+         size = disksize * 1048576.0;
+         break;
+      case 'T':
+         size = disksize * 1048576.0 * 1048576.0;
+         break;
+      case 'K':
+      case 'k':
+         size = disksize * 1024.0;
+         break;
+      default:
+         size = disksize;
+   }
+   return size;
+}
+
+/*
+ * Creates a path hierarchy on local FS.
+ *  It is used for local restore mode to create a required directory.
+ *  The functionality is similar to 'mkdir -p'.
+ *
+ * TODO: make a support for relative path
+ * TODO: check if we can use findlib/makepath implementation instead
+ *
+ * in:
+ *    bpContext - for Bacula debug and jobinfo messages
+ *    path - a full path to create, does not check if the path is relative,
+ *           could fail in this case
+ * out:
+ *    bRC_OK - path creation was successful
+ *    bRC_Error - on any error
+ */
+bRC pluglib_mkpath(bpContext* ctx, char* path, bool isfatal)
+{
+#ifdef PLUGINPREFIX
+#define _OLDPREFIX   PLUGINPREFIX
+#endif
+#define PLUGINPREFIX    "pluglibmkpath:"
+   struct stat statp;
+   POOL_MEM dir(PM_FNAME);
+   char *p, *q;
+
+   if (!path){
+      return bRC_Error;
+   }
+   if (stat(path, &statp) == 0){
+      if (S_ISDIR(statp.st_mode)){
+         return bRC_OK;
+      } else {
+         DMSG(ctx, DERROR, "Path %s is not directory\n", path);
+         JMSG(ctx, isfatal ? M_FATAL : M_ERROR, "Path %s is not directory\n", path);
+         return bRC_Error;
+      }
+   }
+   DMSG(ctx, DDEBUG, "mkpath verify dir: %s\n", path);
+   pm_strcpy(dir, path);
+   p = dir.addr() + 1;
+   while (*p && (q = strchr(p, (int)PathSeparator)) != NULL){
+      *q = 0;
+      DMSG(ctx, DDEBUG, "mkpath scanning(1): %s\n", dir.c_str());
+      if (stat(dir.c_str(), &statp) == 0){
+         *q = PathSeparator;
+         p = q + 1;
+         continue;
+      }
+      DMSG0(ctx, DDEBUG, "mkpath will create dir(1).\n");
+      if (mkdir(dir.c_str(), 0750) < 0){
+         /* error */
+         berrno be;
+         DMSG2(ctx, DERROR, "Cannot create directory %s Err=%s\n", dir.c_str(), be.bstrerror());
+         JMSG2(ctx, isfatal ? M_FATAL : M_ERROR, "Cannot create directory %s Err=%s\n", dir.c_str(), be.bstrerror());
+         return bRC_Error;
+      }
+      *q = PathSeparator;
+      p = q + 1;
+   }
+   DMSG0(ctx, DDEBUG, "mkpath will create dir(2).\n");
+   if (mkdir(path, 0750) < 0){
+      /* error */
+      berrno be;
+      DMSG2(ctx, DERROR, "Cannot create directory %s Err=%s\n", path, be.bstrerror());
+      JMSG2(ctx, isfatal ? M_FATAL : M_ERROR, "Cannot create directory %s Err=%s\n", path, be.bstrerror());
+      return bRC_Error;
+   }
+   DMSG0(ctx, DDEBUG, "mkpath finish.\n");
+#ifdef _OLDPREFIX
+#define PLUGINPREFIX    _OLDPREFIX
+#undef _OLDPREFIX
+#else
+#undef PLUGINPREFIX
+#endif
+   return bRC_OK;
+}
diff --git a/bacula/src/plugins/dir/dirpluglib.h b/bacula/src/plugins/dir/dirpluglib.h
new file mode 100644 (file)
index 0000000..41da06c
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Common definitions and utility functions for Inteos plugins.
+ * Functions defines a common framework used in our utilities and plugins.
+ *
+ * Author: RadosÅ‚aw Korzeniewski, MMXX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#ifndef _PLUGLIB_H_
+#define _PLUGLIB_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "bacula.h"
+#include "dir_plugins.h"
+
+/* definitions */
+/* size of different string or query buffers */
+#define BUFLEN       4096
+#define BIGBUFLEN    65536
+
+/* debug and messages functions */
+#define JMSG0(ctx,type,msg) \
+      if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg );
+
+#define JMSG1 JMSG
+#define JMSG(ctx,type,msg,var) \
+      if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var );
+
+#define JMSG2(ctx,type,msg,var1,var2) \
+      if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var1, var2 );
+
+#define JMSG3(ctx,type,msg,var1,var2,var3) \
+      if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var1, var2, var3 );
+
+#define JMSG4(ctx,type,msg,var1,var2,var3,var4) \
+      if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var1, var2, var3, var4 );
+
+#define DMSG0(ctx,level,msg) \
+      if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg );
+
+#define DMSG1 DMSG
+#define DMSG(ctx,level,msg,var) \
+      if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var );
+
+#define DMSG2(ctx,level,msg,var1,var2) \
+      if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2 );
+
+#define DMSG3(ctx,level,msg,var1,var2,var3) \
+      if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2, var3 );
+
+#define DMSG4(ctx,level,msg,var1,var2,var3,var4) \
+      if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2, var3, var4 );
+
+#define DMSG6(ctx,level,msg,var1,var2,var3,var4,var5,var6) \
+      if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2, var3, var4, var5, var6 );
+
+/* fixed debug level definitions */
+#define D1  1                    /* debug for every error */
+#define DERROR D1
+#define D2  10                   /* debug only important stuff */
+#define DINFO  D2
+#define D3  200                  /* debug for information only */
+#define DDEBUG D3
+#define D4  800                  /* debug for detailed information only */
+#define DVDEBUG D4
+
+#define getBaculaVar(bvar,val)  bfuncs->getBaculaValue(ctx, bvar, val);
+
+/* used for sanity check in plugin functions */
+#define ASSERT_CTX \
+    if (!ctx || !ctx->pContext || !bfuncs) \
+    { \
+        return bRC_Error; \
+    }
+
+/* defines for handleEvent */
+#define DMSG_EVENT_STR(event,value)       DMSG2(ctx, DINFO, "%s value=%s\n", eventtype2str(event), NPRT((char *)value));
+#define DMSG_EVENT_CHAR(event,value)      DMSG2(ctx, DINFO, "%s value='%c'\n", eventtype2str(event), (char)value);
+#define DMSG_EVENT_LONG(event,value)      DMSG2(ctx, DINFO, "%s value=%ld\n", eventtype2str(event), (intptr_t)value);
+#define DMSG_EVENT_PTR(event,value)       DMSG2(ctx, DINFO, "%s value=%p\n", eventtype2str(event), value);
+
+const char *eventtype2str(bDirEvent *event);
+uint64_t pluglib_size_suffix(int disksize, char suff);
+uint64_t pluglib_size_suffix(double disksize, char suff);
+bRC pluglib_mkpath(bpContext* ctx, char* path, bool isfatal);
+
+/*
+ * Checks if plugin command points to our Plugin
+ *
+ * in:
+ *    command - the plugin command used for backup/restore
+ * out:
+ *    True - if it is our plugin command
+ *    False - the other plugin command
+ */
+inline bool isourplugincommand(const char *pluginprefix, const char *command)
+{
+   /* check if it is our Plugin command */
+   if (strncmp(pluginprefix, command, strlen(pluginprefix)) == 0){
+      /* it is not our plugin prefix */
+      return true;
+   }
+   return false;
+}
+
+#endif                           /* _PLUGLIB_H_ */
diff --git a/bacula/src/plugins/dir/ldap/src/.gitignore b/bacula/src/plugins/dir/ldap/src/.gitignore
new file mode 100644 (file)
index 0000000..1513d9a
--- /dev/null
@@ -0,0 +1,2 @@
+.libs
+ldaptest
\ No newline at end of file
diff --git a/bacula/src/plugins/dir/ldap/src/Makefile.in b/bacula/src/plugins/dir/ldap/src/Makefile.in
new file mode 100644 (file)
index 0000000..fd62206
--- /dev/null
@@ -0,0 +1,90 @@
+#
+# Simple Makefile for building test FD plugins for Bacula
+#
+# Copyright (C) 2000-2015 by Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+#
+@MCOMMON@
+
+# No optimization for now for easy debugging
+
+DIRDIR=../../../../dird
+SRCDIR=../../../..
+LIBDIR=../../../../lib
+DIRPLUGDIR=../..
+
+topdir = @BUILD_DIR@
+working_dir=@working_dir@
+thisdir = src/plugins/dir/ldap/src
+
+LDAP_LIBS=@LDAP_LIBS@
+LDAP_LDFLAGS=@LDAP_LDFLAGS@
+LDAP_INC=@LDAP_INC@
+BPAM_LDAP_TARGET = @BPAM_LDAP_TARGET@
+BPAM_LDAP_TARGET_INSTALL = @BPAM_LDAP_TARGET_INSTALL@
+
+LDAPSRC = ldap-dir.c
+LDAPTESTSRC = ldaptest.c
+LDAPOBJ = $(LDAPSRC:.c=.lo)
+LDAPTESTOBJ = $(LDAPTESTSRC:.c=.lo)
+
+.SUFFIXES:    .c .lo
+
+# ldap-dir.la ldaptest
+all: $(BPAM_LDAP_TARGET)
+
+.c.lo:
+       @echo "Compiling $< ..."
+       $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) -I${SRCDIR} -I${DIRDIR} -I${DIRPLUGDIR} $(LDAP_INC) -c $<
+
+%.lo: %.c %.h Makefile
+       @echo "Compiling $< ..."
+       $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I${SRCDIR} -I${DIRDIR} -I${DIRPLUGDIR} $(LDAP_INC) -c $(@:.lo=.c)
+
+ldap-dir.la: Makefile $(LDAPOBJ) $(COMMONOBJ) $(DIRPLUGDIR)/dirpluglib.lo
+       @echo "Linking $(@:.la=.so) ..."
+       $(NO_ECHO)$(LIBTOOL_LINK) --silent $(CXX) $(LDFLAGS) $(LDAP_LDFLAGS) $(LDAP_LIBS) -shared $^ -o $@ -rpath $(plugindir) -module -export-dynamic -avoid-version
+
+$(DIRPLUGDIR)/dirpluglib.lo:
+       $(MAKE) -C $(DIRPLUGDIR) dirpluglib.lo
+
+ldaptest: $(LDAPTESTOBJ) Makefile
+       @echo "Building ldaptest ..."
+       $(NO_ECHO)$(LIBTOOL_LINK) --silent $(CXX) $(LDFLAGS) $(LDAP_LDFLAGS) $(LDAP_LIBS) ldaptest.lo -o $@
+
+install: $(BPAM_LDAP_TARGET_INSTALL)
+
+install-ldap: ldap-dir.la
+       @echo "Installing plugin ... $(^:.la=.so)"
+       $(MKDIR) $(DESTDIR)$(plugindir)
+       $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) ldap-dir.la $(DESTDIR)$(plugindir)
+       $(NO_ECHO)$(RMF) $(DESTDIR)$(plugindir)/ldap-dir.la
+
+install-ldaptest: ldaptest
+       @echo "Installing ldaptest ..."
+       $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) ldaptest $(DESTDIR)$(sbindir)
+
+Makefile: Makefile.in $(topdir)/config.status
+       cd $(topdir) \
+         && CONFIG_FILES=$(thisdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+libtool-clean:
+       @find . -name '*.lo' -print | xargs $(LIBTOOL_CLEAN) $(RMF)
+       @$(RMF) *.la
+       @$(RMF) -r .libs _libs
+
+clean: libtool-clean
+       @rm -f main ldaptest *.so *.o
+
+distclean: clean
+       @rm -f Makefile *.la *.lo
+       @rm -rf .libs
+
+libtool-uninstall:
+       $(LIBTOOL_UNINSTALL) $(RMF) $(DESTDIR)$(plugindir)/ldap-dir.so
+       $(LIBTOOL_UNINSTALL) $(RMF) $(DESTDIR)$(plugindir)/msad-fd.so
+
+uninstall: @LIBTOOL_UNINSTALL_TARGET@
+
+depend:
\ No newline at end of file
diff --git a/bacula/src/plugins/dir/ldap/src/ldap-dir.c b/bacula/src/plugins/dir/ldap/src/ldap-dir.c
new file mode 100644 (file)
index 0000000..68b4570
--- /dev/null
@@ -0,0 +1,595 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Bacula Pluggable Authentication Modules - LDAP Plugin
+ *
+ * Author: RadosÅ‚aw Korzeniewski, radoslaw@korzeniewski.net, Inteos Sp. z o.o.
+ */
+
+#include "ldap-dir.h"
+
+/*
+ * This is a BPAM (Bacula Pluggable Authentication Modules) which authenticate users
+ * found in any LDAP Directory Server. It support OpenLDAP and Microsoft Active Directory
+ * among others standard LDAPs.
+ */
+
+
+// a list of authentication operations we expect and handle before final authenticate
+static bDirAuthenticationData bpamldapquestions[] =
+{
+   // operation; question; seqdata;
+   {bDirAuthenticationOperationLogin, "Username:", 0},
+   {bDirAuthenticationOperationPassword, "Password:", 1},
+};
+
+#define LDAP_WELCOME       "LDAP Authentication"
+
+// plugin authentication registration struct
+static bDirAuthenticationRegister bpamldapregister =
+{
+#if __cplusplus > 201103L
+   .name = PLUGIN_NAME,
+   .welcome = LDAP_WELCOME,            // our plugin name which should correspond to plugin filename
+   .num = 2,                           // it shows the number of defined auth operations
+   .data = bpamldapquestions,          // our auth operations list
+   .nsTTL = 0,                         // future usage
+#else
+   PLUGIN_NAME, LDAP_WELCOME, 2, bpamldapquestions, 0
+#endif
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Forward referenced functions */
+static bRC newPlugin(bpContext *ctx);
+static bRC freePlugin(bpContext *ctx);
+static bRC handlePluginEvent(bpContext *ctx, bDirEvent *event, void *value);
+static bRC getAuthenticationData(bpContext *ctx, const char *param, void **data);
+static bRC getAuthorizationData(bpContext *ctx, const char *param, void **data);
+
+/* Pointers to Bacula functions */
+bDirFuncs *bfuncs = NULL;
+bDirInfo  *binfo = NULL;
+
+static pDirInfo pluginInfo = {
+   sizeof(pluginInfo),
+   DIR_PLUGIN_INTERFACE_VERSION,
+   DIR_PLUGIN_MAGIC,
+   PLUGIN_LICENSE,
+   PLUGIN_AUTHOR,
+   PLUGIN_DATE,
+   PLUGIN_VERSION,
+   PLUGIN_DESCRIPTION
+};
+
+static pDirFuncs pluginFuncs = {
+   sizeof(pluginFuncs),
+   DIR_PLUGIN_INTERFACE_VERSION,
+
+   /* Entry points into plugin */
+   newPlugin,                          /* new plugin instance */
+   freePlugin,                         /* free plugin instance */
+   NULL,                               // we do not provide this callback
+   NULL,                               // we do not provide this callback
+   handlePluginEvent,
+   getAuthenticationData,
+   getAuthorizationData,
+};
+
+bRC loadPlugin(bDirInfo *lbinfo, bDirFuncs *lbfuncs, pDirInfo **pinfo, pDirFuncs **pfuncs)
+{
+   bfuncs = lbfuncs;                  /* set Bacula funct pointers */
+   binfo  = lbinfo;
+   Dmsg2(DINFO, "Loaded: size=%d version=%d\n", bfuncs->size, bfuncs->version);
+
+   *pinfo  = &pluginInfo;             /* return pointer to our info */
+   *pfuncs = &pluginFuncs;            /* return pointer to our functions */
+
+   return bRC_OK;
+}
+
+bRC unloadPlugin()
+{
+   Dmsg0(DINFO, "plugin: Unloaded\n");
+   return bRC_OK;
+}
+
+static bRC newPlugin(bpContext *ctx)
+{
+   BPAMLDAP *self = New (BPAMLDAP(ctx));
+   DMSG0(ctx, DINFO, "newPlugin\n");
+   ctx->pContext = (void*)self;
+
+   return bRC_OK;
+}
+
+static bRC freePlugin(bpContext *ctx)
+{
+   DMSG0(ctx, DINFO, "freePlugin\n");
+   if (!ctx){
+      return bRC_Error;
+   }
+
+   BPAMLDAP *self = (BPAMLDAP*)ctx->pContext;
+
+   if (!self){
+      return bRC_Error;
+   }
+   delete self;
+
+   return bRC_OK;
+}
+
+static bRC handlePluginEvent(bpContext *ctx, bDirEvent *event, void *value)
+{
+   DMSG(ctx, D1, "handlePluginEvent (%i)\n", event->eventType);
+   BPAMLDAP *self = (BPAMLDAP*)ctx->pContext;
+   return self->handlePluginEvent(event, value);
+}
+
+static bRC getAuthenticationData(bpContext *ctx, const char *param, void **data)
+{
+   DMSG(ctx, D1, "getAuthenticationData (%s)\n", param);
+   BPAMLDAP *self = (BPAMLDAP*)ctx->pContext;
+   return self->getAuthenticationData(param, data);
+}
+
+static bRC getAuthorizationData(bpContext *ctx, const char *param, void **data)
+{
+   DMSG(ctx, D1, "getAuthorizationData (%s)\n", param);
+   BPAMLDAP *self = (BPAMLDAP*)ctx->pContext;
+   return self->getAuthorizationData(param, data);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * @brief display common connection error message and diagnose
+ *
+ * @param ret the return code from ldap_* functions to analize and display
+ */
+void BPAMLDAP::ldapserverconnectionerror(int ret)
+{
+   char *errmsg = NULL;
+
+   ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+   DMSG1(ctx, D1, "LDAP Server connection error: %s\n", ldap_err2string(ret));
+   if (errmsg){
+      DMSG1(ctx, D1, "diagnose error: %s\n", errmsg);
+   }
+   ldap_memfree(errmsg);
+}
+
+/**
+ * @brief connect to LDAP service
+ *
+ * @return bRC on success bRC_OK, on error bRC_Error
+ */
+bRC BPAMLDAP::ldapconnect()
+{
+   const int desired_version = LDAP_VERSION3;
+   POOL_MEM tmp(PM_FNAME);
+   int ret;
+   struct berval cred;
+   struct berval *msgidp = NULL;
+   bool starttls = false;
+   bool starttlsforce = false;
+
+   // first initialize ldap connection context `LDAP`
+#ifdef __sun__
+   // In Solaris our `url` parameter is a host name only and not a real URL.
+   // It should be notice in documentation
+   if ((ld = ldap_init(url.c_str(), LDAP_PORT)) == NULL) {
+      return bRC_Error;
+   }
+#else
+   if (ldap_initialize(&ld, url.c_str()) != LDAP_SUCCESS) {
+      return bRC_Error;
+   }
+#endif
+
+   // required a desired LDAP protocol version
+#ifdef __sun__
+   if ((ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &desired_version)) != LDAP_SUCCESS)
+#else
+   if ((ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &desired_version)) != LDAP_OPT_SUCCESS)
+#endif
+   {
+      ldapserverconnectionerror(ret);
+      return bRC_Error;
+   }
+
+   // disable referals return in queries - we do not support it
+#ifdef __sun__
+   if ((ret = ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)) != LDAP_SUCCESS)
+#else
+   if ((ret = ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)) != LDAP_OPT_SUCCESS)
+#endif
+   {
+      ldapserverconnectionerror(ret);
+      return bRC_Error;
+   }
+
+   // handle starttls if requested in configuration
+#ifdef HAVE_LDAP_START_TLS
+   //
+   // Start TLS support
+   //
+   // To enable StartTLS for LDAP connection you have to setup 'starttls' or 'starttlsforce' plugin options
+   // where 'starttlsforce' will fail connection if server do not support it.
+   //
+   if (starttls || starttlsforce){
+      DMSG0(ctx, D2, "executing ldap_start_tls_s\n");
+      if ((ret = ldap_start_tls_s(ld, NULL, NULL)) != LDAP_SUCCESS)
+      {
+         ldapserverconnectionerror(ret);
+         if (starttlsforce)
+         {
+            DMSG0(ctx, D1, "STARTTLSFORCE set, cannot continue!\n");
+            return bRC_Error;
+         }
+      }
+   }
+#endif   /* HAVE_LDAP_START_TLS */
+
+   DMSG2(ctx, D1, "credentials to connect: binddn='%s' pass='%s'\n", binddn.c_str(), bindpass.c_str());
+
+   cred.bv_val = bindpass.c_str();
+   cred.bv_len = strlen(cred.bv_val);
+
+   if ((ret = ldap_sasl_bind_s(ld, binddn.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, &msgidp)) != LDAP_SUCCESS)
+   {
+      ldapserverconnectionerror(ret);
+      if (strcasestr(url.c_str(), "ldaps://") != NULL || starttls || starttlsforce){
+         DMSG0(ctx, M_INFO, "Using TLS/SSL for LDAP service require CA certificate configuration on the backup server.\n");
+         DMSG0(ctx, M_INFO, "If it is not configured properly a connection over TLS/SSL will fail.\n");
+      }
+      return bRC_Error;
+   }
+
+   DMSG0(ctx, DDEBUG, "LDAP connection successful\n");
+   return bRC_OK;
+}
+
+/**
+ * @brief disconnect from LDAP service
+ *
+ * @return bRC on success bRC_OK, on error bRC_Error
+ */
+bRC BPAMLDAP::ldapdisconnect()
+{
+   int rc;
+
+   rc = ldap_unbind_ext(ld, NULL, NULL);
+   if (rc != LDAP_SUCCESS) {
+      return bRC_Error;
+   }
+
+   return bRC_OK;
+}
+
+/**
+ * @brief check if supplied dn exist in directory
+ *
+ * @param dn checked dn
+ * @return true dn exist in directory
+ * @return false dn does not exist
+ */
+bool BPAMLDAP::ldapsearchonedn()
+{
+   int rc;
+   bool ret = false;
+   int type;
+   const char *attrs[] = { "cn", NULL };
+   LDAPMessage *msg = NULL;
+   const char *dn;
+
+   DMSG2(ctx, D3, "ldapsearchonedn for: %s and filter: %s\n", basedn.c_str(), filter.c_str());
+
+   rc = ldap_search_ext_s(ld, basedn.c_str(), LDAP_SCOPE_SUB, filter.c_str(), (char **)attrs,
+           0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg);
+
+   switch (rc){
+      case LDAP_NO_SUCH_OBJECT:     /* object not found */
+      case LDAP_REFERRAL:           /* for LDAP we reached an out of scope query */
+         DMSG0(ctx, DDEBUG, "no such object or referral found\n");
+         break;
+      case LDAP_SUCCESS:
+         type = ldap_msgtype(msg);
+         DMSG(ctx, D3, "ldapsearchonedn resulting msgtype: %i\n", type);
+         if (type == LDAP_RES_SEARCH_ENTRY){
+            ret = true;
+            dn = ldap_get_dn(ld, msg);
+            if (!dn){
+               DMSG0(ctx, DERROR, "ldapsearchonedn cannot get entry DN!\n");
+               ret = false;
+            } else {
+               DMSG1(ctx, DDEBUG, "ldapsearchonedn get DN: %s\n", dn);
+               pm_strcpy(userdn, dn);
+            }
+         }
+         break;
+      default:
+         /* we reach some error */
+         DMSG2(ctx, D1, "ldapsearchonedn search error: %s for: %s\n", ldap_err2string(rc), basedn.c_str());
+         break;
+   }
+
+   ldap_msgfree(msg);
+
+   return ret;
+}
+
+/**
+ * @brief This method perform a variable substitute in filter expression.
+ *    Substituted variables are:
+ *    * %p - for user password response
+ *    * %u - for username response
+ *    Any single `%` character without next `p` or `u` will be simple rewritten without change.
+ *    If you want to rewrite '%p' or '%u' without substituting it with predefined variables then
+ *    you have to write it as: '%%p' or '%%u' respectively.
+ */
+void BPAMLDAP::substitute_filter_parameters()
+{
+   if (strlen(filter.c_str()) > 0){
+      POOL_MEM tmp(PM_MESSAGE);
+      char *p;
+      char *s;
+      const char *c = NULL;
+      char *q = s = filter.c_str();
+      tmp.c_str()[0] = '\0';
+      while ((p = strchr(q, '%')) != NULL)
+      {
+         switch (p[1]){
+            case 'u':
+               *p = '\0';
+               c = username.c_str();
+               p++;
+               break;
+
+            case 'p':
+               *p = '\0';
+               c = password.c_str();
+               p++;
+               break;
+
+            case '%':
+               p[1] = '\0';
+               p++;
+               c = "";
+               break;
+            default:
+               c = NULL;
+               break;
+         }
+         q = p + 1;
+         if (c){
+            pm_strcat(tmp, s);
+            pm_strcat(tmp, c);
+            s = q;
+         }
+      }
+      if (s){
+         pm_strcat(tmp, s);
+      }
+      pm_strcpy(filter, tmp.c_str());
+      DMSG1(ctx, DINFO, "filter after substitute: %s\n", filter.c_str());
+   }
+}
+
+/**
+ * @brief Perform a standard LDAP authentication using authentication parameters and user credentials.
+ *        The The LDAP authentication procedure is similar to the one implemented by Apache mod_authnz_ldap
+ *        (check: https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html) which is known as "search/bind".
+ *        In this case Plugin first connects to LDAP Service to search for authenticated user definition
+ *        (using binddn and bindpass) as it requires a proper user DN to do the second part of authentication.
+ *        If found then it will bind to LDAP on behalf of the user with user credentials from bconsole
+ *        interactive query/response session.
+ *
+ * @return bRC bRC_OK on successful authentication, bRC_Error when not
+ */
+bRC BPAMLDAP::do_ldap_authenticate()
+{
+   // prepare search filter based on response variables
+   substitute_filter_parameters();
+
+   // connect using bind credentials
+   // TODO: we should handle first search without credentials
+   if (ldapconnect() != bRC_OK){
+      return bRC_Error;
+   }
+
+   // check if user query rerurn required object
+   if (!ldapsearchonedn()){
+      return bRC_Error;
+   }
+
+   // its all with bind credentials
+   if (ldapdisconnect() != bRC_OK){
+      return bRC_Error;
+   }
+
+   // now we should connect to LDAP using user credentials
+   pm_strcpy(binddn, userdn.c_str());
+   pm_strcpy(bindpass, password.c_str());
+   // login as user
+   if (ldapconnect() != bRC_OK){
+      return bRC_Error;
+   }
+   // disconnect
+   if (ldapdisconnect() != bRC_OK){
+      return bRC_Error;
+   }
+
+   // now we should report login success!
+   return bRC_OK;
+}
+
+/**
+ * @brief Hndles plugin events defined in BPAM API.
+ *
+ * @param event the event to handle
+ * @param value a pointer to opional value for selected event
+ * @return bRC bRC_OK on success, bRC_Error on any error
+ */
+bRC BPAMLDAP::handlePluginEvent(bDirEvent *event, void *value)
+{
+   bDirAuthValue *pvalue;
+
+   switch (event->eventType) {
+   case bDirEventAuthenticationQuestion:
+      break;
+
+   case bDirEventAuthenticationResponse:
+      pvalue = (bDirAuthValue *)value;
+      DMSG_EVENT_STR(event, pvalue->response);
+      switch (pvalue->seqdata){
+         case 0:     // username
+            pm_strcpy(username, pvalue->response);
+            break;
+         case 1:     // password
+            pm_strcpy(password, pvalue->response);
+            break;
+         default:
+            return bRC_Error;
+         }
+      break;
+
+   case bDirEventAuthenticate:
+      DMSG_EVENT_PTR(event, value);
+      return do_ldap_authenticate();
+
+   default:
+      break;
+   }
+
+   return bRC_OK;
+}
+
+/**
+ * @brief Handles Plugin parameters defined in "Authentication Plugin = ..." Console resource parameter.
+ *
+ * @param param a string from "Authentication Plugin = ..." Console resource parameter
+ * @return bRC bRC_OK when all parameters are valid and parsed, bRC_Error on any error
+ */
+bRC BPAMLDAP::parse_plugin_params(const char *param)
+{
+   cmd_parser parser;
+
+   if (!param){
+      return bRC_Error;
+   }
+
+   // and parse command
+   if (parser.parse_cmd(param) != bRC_OK) {
+      DMSG0(ctx, DERROR, "Unable to parse Plugin parameters.\n");
+      return bRC_Error;
+   }
+
+   // the first (zero) parameter is a plugin name
+   if (!bstrcmp(parser.argk[0], PLUGIN_NAME)){
+      return bRC_Error;
+   }
+
+   // iterate over next parameters
+   for (int i = 1; i < parser.argc; i++) {
+      if (bstrcmp(parser.argk[i], "url")){
+         pm_strcpy(url, parser.argv[i]);
+         DMSG1(ctx, DDEBUG, "parsed url: %s\n", url.c_str());
+         continue;
+      } else
+      if (bstrcmp(parser.argk[i], "binddn")){
+         pm_strcpy(binddn, parser.argv[i]);
+         DMSG1(ctx, DDEBUG, "parsed binddn: %s\n", binddn.c_str());
+         continue;
+      } else
+      if (bstrcmp(parser.argk[i], "bindpass")) {
+         pm_strcpy(bindpass, parser.argv[i]);
+         DMSG1(ctx, DDEBUG, "parsed bindpass: %s\n", bindpass.c_str());
+         continue;
+      } else
+      if (bstrcmp(parser.argk[i], "query")) {
+         POOL_MEM tmp(PM_MESSAGE);
+         pm_strcpy(tmp, parser.argv[i]);
+         char *d = strchr(tmp.c_str(), '/');
+         if (!d){
+            DMSG1(ctx, DERROR, "Cannot find basedn delimiter in query=%s\n", tmp.c_str());
+            return bRC_Error;
+         }
+         // separate basedn and filter
+         *d = '\0';
+         pm_strcpy(basedn, tmp.c_str());
+         pm_strcpy(filter, d + 1);
+         DMSG2(ctx, DDEBUG, "parsed query - basedn:%s filter:%s \n", basedn.c_str(), filter.c_str());
+         continue;
+      } else
+      if (bstrcmp(parser.argk[i], "starttls")) {
+         starttls = true;
+         DMSG0(ctx, DDEBUG, "parsed starttls\n");
+         continue;
+      } else
+      if (bstrcmp(parser.argk[i], "starttlsforce")) {
+         starttlsforce = true;
+         DMSG0(ctx, DDEBUG, "parsed starttlsforce\n");
+         continue;
+      } else {
+         DMSG1(ctx, DERROR, "unknown parameter: %s\n", parser.argk[i]);
+         return bRC_Error;
+      }
+   }
+
+   return bRC_OK;
+}
+
+/**
+ * @brief Register BPAM LDAP Plugin authentication operations.
+ *
+ * @param param a string from "Authentication Plugin = ..." Console resource parameter
+ * @param data a double pointer used for return value
+ * @return bRC bRC_OK on success, bRC_Error on any error
+ */
+bRC BPAMLDAP::getAuthenticationData(const char *param, void **data)
+{
+   bDirAuthenticationRegister **padata = (bDirAuthenticationRegister **)data;
+
+   DMSG1(ctx, DINFO, "registering with: %s\n", NPRT(param));
+   if (parse_plugin_params(param) != bRC_OK){
+      return bRC_Error;
+   }
+   *padata = &bpamldapregister;
+
+   return bRC_OK;
+}
+
+/**
+ * @brief Unimplemented
+ *
+ * @param param Unimplemented
+ * @param data Unimplemented
+ * @return bRC Unimplemented
+ */
+bRC BPAMLDAP::getAuthorizationData(const char *param, void **data)
+{
+   return bRC_OK;
+}
diff --git a/bacula/src/plugins/dir/ldap/src/ldap-dir.h b/bacula/src/plugins/dir/ldap/src/ldap-dir.h
new file mode 100644 (file)
index 0000000..f6c0f41
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for making a backup and restore of individual LDAP objects.
+ * Author: RadosÅ‚aw Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#ifndef _LDAP_DIR_H_
+#define _LDAP_DIR_H_
+
+#include "bacula.h"
+#include "dir_plugins.h"
+#include "dir_authplugin.h"
+// #include "lib/ini.h"
+#include "lib/cmd_parser.h"
+
+/* it is an LDAP authentication plugin, so we need a libldap library */
+#ifdef __WIN32__
+#include <winldap.h>
+#else
+#include <ldap.h>
+#endif
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "dirpluglib.h"
+#include "ldap-util.h"
+
+/*
+ * libbac uses its own sscanf implementation which is not compatible with
+ * libc implementation, unfortunately. usage of bsscanf require format string rewriting.
+ */
+#ifdef sscanf
+#undef sscanf
+#endif
+
+/* Plugin compile time variables */
+#define PLUGIN_LICENSE              "Bacula AGPLv3"
+#define PLUGIN_AUTHOR               "Inteos Sp. z o.o."
+#define PLUGIN_DATE                 "September 2020"
+#define PLUGIN_VERSION              "0.1.0"
+#define PLUGIN_DESCRIPTION          "BPAM LDAP plugin (c) Inteos"
+#define PLUGIN_NAME                 "ldap"
+
+class BPAMLDAP : public SMARTALLOC
+{
+private:
+   POOL_MEM url;                // This is our LDAP server url
+   POOL_MEM binddn;             // This is a binddn (user) to make queries at LDAP server
+   POOL_MEM bindpass;            // This is a binddn (user) password
+   POOL_MEM basedn;             // This is a LDAP basedn query starting point
+   POOL_MEM filter;              // This is a LDAP query string for selecting required username
+   bool starttls;
+   bool starttlsforce;
+
+   LDAP *ld;                        // This is out connection to LDAP server
+   POOL_MEM userdn;
+
+   POOL_MEM username;            // here we will save user response to our queries
+   POOL_MEM password;            // here we will save user response to our queries
+
+   bpContext *ctx;
+
+   bRC ldapconnect();
+   bRC ldapdisconnect();
+   bool ldapsearchonedn();
+   void ldapserverconnectionerror(int ret);
+
+   bRC parse_plugin_params(const char *param);
+   bRC do_ldap_authenticate();
+   void substitute_filter_parameters();
+
+public:
+#if __cplusplus > 201103L
+   BPAMLDAP() = delete;
+   ~BPAMLDAP() = default;
+#else
+   BPAMLDAP() {};
+   ~BPAMLDAP() {};
+#endif
+   BPAMLDAP(bpContext *pctx) :
+      url(PM_FNAME),
+      binddn(PM_FNAME),
+      bindpass(PM_NAME),
+      basedn(PM_FNAME),
+      filter(PM_FNAME),
+      starttls(false),
+      starttlsforce(false),
+      ld(NULL),
+      userdn(PM_NAME),
+      username(PM_NAME),
+      password(PM_NAME),
+      ctx(pctx)
+   {};
+
+
+   bRC handlePluginEvent(bDirEvent *event, void *value);
+   bRC getAuthenticationData(const char *param, void **data);
+   bRC getAuthorizationData(const char *param, void **data);
+};
+
+#endif /* _LDAP_FD_H_ */
diff --git a/bacula/src/plugins/dir/ldap/src/ldap-util.c b/bacula/src/plugins/dir/ldap/src/ldap-util.c
new file mode 100644 (file)
index 0000000..f5ffcba
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Common definitions and utility functions for Inteos plugins.
+ * Functions defines a common framework used in our utilities and plugins.
+ * LDAP plugin specific functions.
+ *
+ * Author: RadosÅ‚aw Korzeniewski, radoslaw@korzeniewski.net, Inteos Sp. z o.o.
+ */
+
+#include "ldap-util.h"
+#include "dirpluglib.h"
+
+/*
+ * libbac uses its own sscanf implementation which is not compatible with
+ * libc implementation, unfortunately. usage of bsscanf require format string rewriting.
+ */
+#ifdef sscanf
+#undef sscanf
+#endif
+
+/* Pointers to Bacula functions used in plugins */
+extern bDirFuncs *bfuncs;
+extern bDirInfo *binfo;
+
+/*
+ * display common connection error message and diagnose
+ */
+void ldapserverconnectionerror(bpContext * ctx, LDAP *ldapconn, int ret)
+{
+   char *errmsg = NULL;
+
+   ldap_get_option(ldapconn, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+   DMSG1(ctx, D1, "LDAP Server connection error: %s\n", ldap_err2string(ret));
+   if (errmsg){
+      DMSG1(ctx, D1, "diagnose error: %s\n", errmsg);
+   }
+   JMSG1(ctx, M_WARNING, "LDAP Server connection error: %s\n", ldap_err2string(ret));
+   if (errmsg){
+      JMSG1(ctx, M_WARNING, "diagnose error: %s\n", errmsg);
+   }
+   ldap_memfree(errmsg);
+}
+
+/*
+ * connect to LDAP service
+ *
+ * in:
+ *    paramlist - list of parameters from config file
+ *       required params: LDAPURI, BINDDN, BINDPASS, BINDMODE
+ * out:
+ *    on success: LDAP * - working LDAP connection
+ *    on error:   NULL
+ */
+LDAP *ldapconnect(bpContext *ctx, ldap_paramlist *paramlist)
+{
+   LDAP *ldapconn = NULL;
+   const char *ldapuri;
+   const int desired_version = LDAP_VERSION3;
+   const char *binddn;
+   char *bindpass;
+   POOLMEM *tmp = NULL;
+   int ret;
+   struct berval cred;
+   struct berval *msgidp = NULL;
+   bool starttls = false;
+   bool starttlsforce = false;
+
+   ldapuri = paramlist->ldapuri;
+
+#ifdef __sun__
+/* TODO: change ldapuri to ldaphost + ldapport */
+   if ((ldapconn = ldap_init(ldapuri, LDAP_PORT)) == NULL) {
+      return NULL;
+   }
+#else
+   if (ldap_initialize(&ldapconn, ldapuri) != LDAP_SUCCESS) {
+      return NULL;
+   }
+#endif
+
+#ifdef __sun__
+   if ((ret = ldap_set_option(ldapconn, LDAP_OPT_PROTOCOL_VERSION, &desired_version)) != LDAP_SUCCESS)
+#else
+   if ((ret = ldap_set_option(ldapconn, LDAP_OPT_PROTOCOL_VERSION, &desired_version)) != LDAP_OPT_SUCCESS)
+#endif
+   {
+      ldapserverconnectionerror(ctx, ldapconn, ret);
+      return NULL;
+   }
+#ifdef __sun__
+   if ((ret = ldap_set_option(ldapconn, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)) != LDAP_SUCCESS)
+#else
+   if ((ret = ldap_set_option(ldapconn, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)) != LDAP_OPT_SUCCESS)
+#endif
+   {
+      ldapserverconnectionerror(ctx, ldapconn, ret);
+      return NULL;
+   }
+
+#ifdef HAVE_LDAP_START_TLS
+   /*
+    * Start TLS support
+    *
+    * To enable StartTLS for LDAP connection you have to setup 'starttls'
+    *    or 'starttlsforce' plugin options
+    *    where 'starttlsforce' will fail connection if server do not support it.
+    */
+   starttls = paramlist->starttls;
+   starttlsforce = paramlist->starttlsforce;
+
+   if (starttls || starttlsforce){
+      DMSG0(ctx, D2, "executing ldap_start_tls_s\n");
+      if ((ret = ldap_start_tls_s(ldapconn, NULL, NULL)) != LDAP_SUCCESS)
+      {
+         ldapserverconnectionerror(ctx, ldapconn, ret);
+         if (starttlsforce)
+         {
+            DMSG0(ctx, D1, "STARTTLSFORCE set, cannot continue!\n");
+            JMSG0(ctx, M_ERROR, "STARTTLSFORCE set, cannot continue!\n");
+            return NULL;
+         }
+      }
+   }
+#endif   /* HAVE_LDAP_START_TLS */
+
+   binddn = paramlist->binddn;
+
+   /* The password might be obfuscated */
+   bindpass = paramlist->bindpass;
+
+   DMSG2(ctx, D1, "credentials to connect: binddn='%s' pass='%s'\n", binddn, bindpass);
+
+   cred.bv_val = bindpass;
+   cred.bv_len = strlen(cred.bv_val);
+
+   if ((ret = ldap_sasl_bind_s(ldapconn, binddn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, &msgidp)) != LDAP_SUCCESS)
+   {
+      free_and_null_pool_memory(tmp);
+      ldapserverconnectionerror(ctx, ldapconn, ret);
+      if (strcasestr(ldapuri, "ldaps://") != NULL || starttls || starttlsforce){
+         JMSG0(ctx, M_INFO, "Using TLS/SSL for LDAP service require CA certificate configuration on the backup server.\n");
+         JMSG0(ctx, M_INFO, "If it is not configured properly to do a job over TLS/SSL it will fail.\n");
+      }
+      return NULL;
+   }
+
+   free_and_null_pool_memory(tmp);
+   return ldapconn;
+}
+
+/*
+ * disconnect from LDAP service
+ *
+ * in:
+ *    ld - ldap connection handle to unbind
+ * out:
+ *    0 - on success
+ *    1 - on error
+ */
+int ldapdisconnect(bpContext * ctx, LDAP * ld)
+{
+   int rc;
+
+   rc = ldap_unbind_ext(ld, NULL, NULL);
+   if (rc != LDAP_SUCCESS) {
+      return 1;
+   }
+
+   return 0;
+}
+
+/*
+ * check if supplied dn exist in directory
+ *
+ * in:
+ *    ld - ldap connection
+ *    dn - checked dn
+ * out:
+ *    True - dn exist in directory
+ *    False - dn does not exist
+ */
+bool ldapcheckonedn(bpContext * ctx, LDAP * ld, char *dn)
+{
+   int rc;
+   bool ret = false;
+   int type;
+   LDAPMessage *msg = NULL;
+
+   DMSG(ctx, D3, "ldapcheckonedn for: %s\n", dn);
+
+   rc = ldap_search_ext_s(ld, dn, LDAP_SCOPE_BASE, NULL, NULL,
+           0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg);
+
+   switch (rc){
+      case LDAP_NO_SUCH_OBJECT:     /* object not found */
+      case LDAP_REFERRAL:           /* for LDAP we reached an out of scope query */
+         break;
+      case LDAP_SUCCESS:
+         type = ldap_msgtype(msg);
+         DMSG(ctx, D3, "ldapcheckonedn: resulting msgtype: %i\n", type);
+         ret = type == LDAP_RES_SEARCH_ENTRY;
+         break;
+      default:
+         /* we reach some error */
+         JMSG2(ctx, M_ERROR, "ldapcheckonedn: search error: %s for: %s\n", ldap_err2string(rc),dn);
+         DMSG2(ctx, D1, "ldapcheckonedn: search error: %s for: %s\n", ldap_err2string(rc),dn);
+   }
+
+   ldap_msgfree(msg);
+
+   return ret;
+}
diff --git a/bacula/src/plugins/dir/ldap/src/ldap-util.h b/bacula/src/plugins/dir/ldap/src/ldap-util.h
new file mode 100644 (file)
index 0000000..2e8775b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Common definitions and utility functions for Inteos plugins.
+ * Functions defines a common framework used in our utilities and plugins.
+ * LDAP plugin specific functions.
+ *
+ * Author: RadosÅ‚aw Korzeniewski, radoslaw@korzeniewski.net, Inteos Sp. z o.o.
+ */
+
+#ifndef _LDAP_UTIL_H_
+#define _LDAP_UTIL_H_
+
+#include "bacula.h"
+#include "dir_plugins.h"
+
+#ifdef __WIN32__
+#include <winldap.h>
+#else
+#include <ldap.h>
+#endif
+
+/* Plugin compile time variables */
+#define PLUGINPREFIX                "ldap:"
+#define PLUGINNAME                  "LDAP"
+
+typedef struct s_ldap_paramlist {
+   const char *ldapuri;
+   const char *binddn;
+   char *bindpass;
+   bool starttls;
+   bool starttlsforce;
+} ldap_paramlist;
+
+/* util definitions */
+LDAP * ldapconnect(bpContext * ctx, ldap_paramlist * paramlist);
+int ldapdisconnect(bpContext * ctx, LDAP * ld);
+bool ldapcheckonedn(bpContext * ctx, LDAP * ld, char *dn);
+
+#endif               /* _LDAP_UTIL_H_ */
diff --git a/bacula/src/plugins/dir/ldap/src/ldaptest.c b/bacula/src/plugins/dir/ldap/src/ldaptest.c
new file mode 100644 (file)
index 0000000..1f7c7f4
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#include <ldap.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "config.h"
+
+/*
+  gcc/g++ -Wall ldaptest.c -o ldaptest -lldap
+  ./ldaptest "<uri>" "<binddn>" "<bindpass>"
+*/
+
+const char *extensionStartTLS_OID   = "1.3.6.1.4.1.1466.20037";
+const char *extensionTLSattrib      = "supportedExtension";
+const char *extensionSASLattrib     = "supportedSASLMechanisms";
+
+/*
+ * connect to LDAP service
+ */
+LDAP *ldapconnect(char *ldapuri, char *binddn, char *bindpass, bool starttls)
+{
+   LDAP *ldapconn = NULL;
+   int desired_version = LDAP_VERSION3;
+   int ret;
+   char *errmsg = NULL;
+   int debug = 0xffff;
+   struct berval cred;
+   struct berval *msgidp = NULL;
+
+   if ((ret = ldap_initialize(&ldapconn, ldapuri)) != LDAP_SUCCESS)
+   {
+      ldap_get_option(ldapconn, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+      printf("LDAP initialize error %s\n", ldap_err2string(ret));
+      printf("diagnose error: %s\n", errmsg);
+      ldap_memfree(errmsg);
+      return NULL;
+   }
+   if ((ret = ldap_set_option(ldapconn, LDAP_OPT_PROTOCOL_VERSION, &desired_version)) != LDAP_OPT_SUCCESS)
+   {
+      ldap_get_option(ldapconn, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+      printf("LDAP set option error %s\n", ldap_err2string(ret));
+      printf("diagnose error: %s\n", errmsg);
+      ldap_memfree(errmsg);
+      return NULL;
+   }
+   if ((ret = ldap_set_option(ldapconn, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)) != LDAP_OPT_SUCCESS)
+   {
+      ldap_get_option(ldapconn, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+      printf("LDAP set option error %s\n", ldap_err2string(ret));
+      printf("diagnose error: %s\n", errmsg);
+      ldap_memfree(errmsg);
+      return NULL;
+   }
+
+#ifdef HAVE_LDAP_START_TLS
+   if (starttls){
+      if ((ret = ldap_start_tls_s(ldapconn, NULL, NULL)) != LDAP_SUCCESS)
+      {
+         ldap_get_option(ldapconn, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+         printf("LDAP start TLS error %s\n", ldap_err2string(ret));
+         printf("diagnose error: %s\n", errmsg);
+         ldap_memfree(errmsg);
+         return NULL;
+      }
+   }
+#endif
+
+   if( ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug) != LDAP_OPT_SUCCESS)
+   {
+      ldap_get_option(ldapconn, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+      printf("LDAP could not set LDAP_OPT_DEBUG_LEVEL %d\n", debug);
+      printf("diagnose error: %s\n", errmsg);
+      ldap_memfree(errmsg);
+   }
+
+   cred.bv_val = bindpass;
+   cred.bv_len = strlen(cred.bv_val);
+
+   if ((ret = ldap_sasl_bind_s(ldapconn, binddn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, &msgidp) != LDAP_SUCCESS))
+   {
+      ldap_get_option(ldapconn, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
+      printf("LDAP Server bind error: %s\n", ldap_err2string(ret));
+      printf("diagnose error: %s\n", errmsg);
+      ldap_memfree(errmsg);
+      return NULL;
+   }
+   printf("LDAP Server connection OK\n");
+   return ldapconn;
+}
+
+/*
+ * disconnect from LDAP service
+ *
+ * in:
+ *    ld - ldap connection handle to unbind
+ * out:
+ *    0 - on success
+ *    1 - on error
+ */
+int ldapdisconnect(LDAP * ld)
+{
+
+   int rc;
+
+   rc = ldap_unbind_ext(ld, NULL, NULL);
+   if (rc != LDAP_SUCCESS) {
+      return 1;
+   }
+
+   return 0;
+}
+
+int main(int argc, char **argv)
+{
+   LDAP *ld;
+   /* all and extended attributes required */
+   const char *attrs[] = { "+", "*", extensionTLSattrib, extensionSASLattrib, NULL };
+   const char *filter = "(objectClass=*)";
+   LDAPMessage *msg = NULL;
+   LDAPMessage *entry;
+   BerElement *berdata;
+   struct berval **values;
+   char *attr;
+   int rc, i;
+   bool have_start_tls = false;
+   bool test_start_tls = false;
+
+   if (argc < 4) {
+      printf("Usage: %s <uri> <binddn> <bindpass> ", argv[0]);
+#ifdef HAVE_LDAP_START_TLS
+      printf ("[starttls]");
+#endif
+      printf ("\n");
+      exit(1);
+   }
+
+#ifdef HAVE_LDAP_START_TLS
+   if (argc > 4 && strcmp(argv[4], "starttls") == 0) {
+      printf ("> Will test with start tls option.\n");
+      test_start_tls = true;
+   }
+#endif
+
+   ld = ldapconnect(argv[1], argv[2], argv[3], test_start_tls);
+   if (ld) {
+      i = 0;
+      ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &i);
+      rc = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, filter, (char **)attrs,
+            0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg);
+
+      switch (rc){
+         case LDAP_SUCCESS:
+            entry = ldap_first_entry(ld, msg);
+            if (entry != NULL) {
+               printf("\nRootDSE:\n");
+               for (attr = ldap_first_attribute(ld, entry, &berdata);
+                     attr;
+                     attr = ldap_next_attribute(ld, entry, berdata)){
+                  if ((values = ldap_get_values_len(ld, entry, attr)) != NULL) {
+                     for (i = 0; values[i] != NULL; i++) {
+                        printf ("\t %s: %s\n", attr, values[i]->bv_val);
+                        if (strcmp(values[i]->bv_val, extensionStartTLS_OID) == 0 && strcmp(attr, extensionTLSattrib) == 0)
+                        {
+                           have_start_tls = true;
+                        }
+                     }
+                  }
+                  ldap_value_free_len(values);
+                  ldap_memfree(attr);
+               }
+            }
+         default:
+            ldap_msgfree(msg);
+      }
+
+      ldapdisconnect(ld);
+      printf ("\nTest successful!\n");
+#ifdef HAVE_LDAP_START_TLS
+      if (have_start_tls && !test_start_tls)
+      {
+         printf ("\nIt seems your LDAP server supports Start TLS Extension, so you could use 'starttls'\n");
+         printf ("parameter to verify if you can use it in your configuration.\n\n");
+      }
+#endif
+   } else {
+      printf("\nTest failed. Please check the log above!\n");
+   }
+   exit(0);
+}
diff --git a/bacula/src/plugins/dir/test-authentication-api-dir.c b/bacula/src/plugins/dir/test-authentication-api-dir.c
new file mode 100644 (file)
index 0000000..1deac0b
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2020 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Sample Plugin program
+ *
+ *  Kern Sibbald, October 2007
+ */
+#include "bacula.h"
+#include "dir_plugins.h"
+#include "dir_authplugin.h"
+#include "dirpluglib.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PLUGIN_LICENSE      "AGPLv3"
+#define PLUGIN_AUTHOR       "RadosÅ‚aw Korzeniewski"
+#define PLUGIN_DATE         "September 2020"
+#define PLUGIN_VERSION      "1"
+#define PLUGIN_DESCRIPTION  "Test Director Auth API Plugin"
+
+/* Forward referenced functions */
+static bRC newPlugin(bpContext *ctx);
+static bRC freePlugin(bpContext *ctx);
+static bRC getPluginValue(bpContext *ctx, pDirVariable var, void *value);
+static bRC setPluginValue(bpContext *ctx, pDirVariable var, void *value);
+static bRC handlePluginEvent(bpContext *ctx, bDirEvent *event, void *value);
+static bRC getAuthenticationData(bpContext *ctx, const char *param, void **data);
+static bRC getAuthorizationData(bpContext *ctx, const char *param, void **data);
+
+/* Plugin compile time variables */
+#define PLUGINPREFIX                "authapi:"
+#define PLUGIN_NAME                 "test-authentication-api"
+
+/* Pointers to Bacula functions */
+bDirFuncs *bfuncs = NULL;
+bDirInfo  *binfo = NULL;
+
+static pDirInfo pluginInfo = {
+   sizeof(pluginInfo),
+   DIR_PLUGIN_INTERFACE_VERSION,
+   DIR_PLUGIN_MAGIC,
+   PLUGIN_LICENSE,
+   PLUGIN_AUTHOR,
+   PLUGIN_DATE,
+   PLUGIN_VERSION,
+   PLUGIN_DESCRIPTION
+};
+
+static pDirFuncs pluginFuncs = {
+   sizeof(pluginFuncs),
+   DIR_PLUGIN_INTERFACE_VERSION,
+
+   /* Entry points into plugin */
+   newPlugin,                         /* new plugin instance */
+   freePlugin,                        /* free plugin instance */
+   getPluginValue,
+   setPluginValue,
+   handlePluginEvent,
+   getAuthenticationData,
+   getAuthorizationData,
+};
+
+static bDirAuthenticationData testquestions0[] =
+{
+   // operation; question; data;
+   {bDirAuthenticationOperationLogin, "Username:", 0},
+   {bDirAuthenticationOperationPassword, "Password:", 1},
+};
+
+static bDirAuthenticationRegister testregister0 =
+{
+   .name = PLUGIN_NAME,
+   .welcome = "This is a test authplugin API Plugin. Use root/root to login.",
+   .num = 2,
+   .data = testquestions0,
+   .nsTTL = 0,
+};
+
+static bDirAuthenticationData testquestions1[] =
+{
+   // operation; question; data;
+   {bDirAuthenticationOperationLogin, "Username:", 0},
+   {bDirAuthenticationOperationPlugin, NULL, 1},
+   {bDirAuthenticationOperationPlain, "Response:", 2},
+};
+
+static bDirAuthenticationData testquestions1_msg =
+{
+   bDirAuthenticationOperationMessage, NULL, 0
+};
+
+static bDirAuthenticationRegister testregister1 =
+{
+   .name = PLUGIN_NAME,
+   .welcome = "This is a test authplugin API Plugin. Use bacula username to login.",
+   .num = 3,
+   .data = testquestions1,
+   .nsTTL = 0,
+};
+
+struct test_api : public SMARTALLOC {
+   POOL_MEM username{PM_NAME};
+   POOL_MEM password{PM_NAME};
+   POOL_MEM challenge{PM_NAME};
+   POOL_MEM challenge_str{PM_NAME};
+   int mode{0};
+};
+
+bRC loadPlugin(bDirInfo *lbinfo, bDirFuncs *lbfuncs, pDirInfo **pinfo, pDirFuncs **pfuncs)
+{
+   bfuncs = lbfuncs;                  /* set Bacula funct pointers */
+   binfo  = lbinfo;
+   Dmsg2(DINFO, "Loaded: size=%d version=%d\n", bfuncs->size, bfuncs->version);
+
+   *pinfo  = &pluginInfo;             /* return pointer to our info */
+   *pfuncs = &pluginFuncs;            /* return pointer to our functions */
+
+   return bRC_OK;
+}
+
+bRC unloadPlugin()
+{
+   Dmsg0(DINFO, "plugin: Unloaded\n");
+   return bRC_OK;
+}
+
+static bRC newPlugin(bpContext *ctx)
+{
+   test_api *self = New (test_api);
+   DMSG0(ctx, DINFO, "newPlugin\n");
+   ctx->pContext = self;
+   return bRC_OK;
+}
+
+static bRC freePlugin(bpContext *ctx)
+{
+   DMSG0(ctx, DINFO, "freePlugin\n");
+   if (ctx->pContext){
+      test_api *self = (test_api *)ctx->pContext;
+      delete self;
+   }
+   return bRC_OK;
+}
+
+static bRC getPluginValue(bpContext *ctx, pDirVariable var, void *value)
+{
+   DMSG1(ctx, DINFO, "plugin: getPluginValue var=%d\n", var);
+   return bRC_OK;
+}
+
+static bRC setPluginValue(bpContext *ctx, pDirVariable var, void *value)
+{
+   DMSG1(ctx, DINFO, "plugin: setPluginValue var=%d\n", var);
+   return bRC_OK;
+}
+
+static bRC handlePluginEvent(bpContext *ctx, bDirEvent *event, void *value)
+{
+   bDirAuthValue *pvalue;
+   test_api *self = (test_api *)ctx->pContext;
+   int r;
+
+   switch (event->eventType) {
+   case bDirEventAuthenticationQuestion:
+      pvalue = (bDirAuthValue *)value;
+      switch (self->mode)
+      {
+      case 1:
+         r = rand();
+         Mmsg(self->challenge, "%d", r);
+         Mmsg(self->challenge_str, "You should use %s as a response.", self->challenge.c_str());
+         testquestions1_msg.question = self->challenge_str.c_str();
+         pvalue->authdata = &testquestions1_msg;
+         break;
+      default:
+         break;
+      }
+      break;
+
+   case bDirEventAuthenticationResponse:
+      pvalue = (bDirAuthValue *)value;
+      DMSG_EVENT_STR(event, pvalue->response);
+      switch (self->mode)
+      {
+      case 0:
+         switch (pvalue->seqdata)
+         {
+         case 0:
+            pm_strcpy(self->username, pvalue->response);
+            break;
+         case 1:
+            pm_strcpy(self->password, pvalue->response);
+            break;
+         default:
+            break;
+         }
+      case 1:
+         switch (pvalue->seqdata)
+         {
+         case 0:
+            pm_strcpy(self->username, pvalue->response);
+            break;
+         case 2:
+            pm_strcpy(self->password, pvalue->response);
+            break;
+         default:
+            break;
+         }
+      }
+      break;
+
+   case bDirEventAuthenticate:
+      DMSG_EVENT_PTR(event, value);
+      switch (self->mode)
+      {
+      case 0:
+         if (!bstrcmp(self->username.c_str(), "root") || !bstrcmp(self->password.c_str(), "root"))
+         {
+            return bRC_Error;
+         }
+         break;
+      case 1:
+         if (!bstrcmp(self->username.c_str(), "bacula") || !bstrcmp(self->password.c_str(), self->challenge.c_str()))
+         {
+            return bRC_Error;
+         }
+         break;
+      default:
+         return bRC_Error;
+      }
+      break;
+
+   default:
+      break;
+   }
+   return bRC_OK;
+}
+
+static bRC getAuthenticationData(bpContext *ctx, const char *param, void **data)
+{
+   test_api *self = (test_api *)ctx->pContext;
+   bDirAuthenticationRegister **padata = (bDirAuthenticationRegister **)data;
+
+   self->mode = 0;   // this is a default
+
+   DMSG1(ctx, DINFO, "registering with: %s\n", NPRT(param));
+   sscanf(param, PLUGIN_NAME ":%d", &self->mode);
+   switch (self->mode){
+      case 1:
+         DMSG0(ctx, DINFO, "testregister1\n");
+         *padata = &testregister1;
+         break;
+      default:
+         DMSG0(ctx, DINFO, "testregister0\n");
+         *padata = &testregister0;
+         break;
+   }
+
+   return bRC_OK;
+}
+
+static bRC getAuthorizationData(bpContext *ctx, const char *param, void **data)
+{
+
+   return bRC_OK;
+}
+
+#ifdef __cplusplus
+}
+#endif
index 36612f31bd58ab38e9f5a8cede824d8e2554b971..5f756b10ba41a4b6ad623c07e66a88fe38d4d098 100644 (file)
 
 #include "pluglib.h"
 
+/* Pointers to Bacula functions used in plugins */
+extern bFuncs *bfuncs;
+extern bInfo *binfo;
+
 /* Events that are passed to plugin
 typedef enum {
   bEventJobStart                        = 1,
index 63acffb2a7bf85ce3e6fb716c64d22af0a632fde..d7badec8e7c9ac0c3c03ed551cb53dd2296eef7b 100644 (file)
 #include "bacula.h"
 #include "fd_plugins.h"
 
-/* Pointers to Bacula functions used in plugins */
-extern bFuncs *bfuncs;
-extern bInfo *binfo;
-
 /* definitions */
 /* size of different string or query buffers */
 #define BUFLEN       4096