@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
* 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, ...);
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);
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
*/
int dir_version = 0;
char bashed_name[MAX_NAME_LENGTH];
bool skip_msg = false;
+
/*
* Send my name to the Director then do authentication
*/
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;
#
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 \
#include "bacula.h"
#include "dird.h"
+#include "dir_authplugin.h"
static const int dbglvl = 50;
}
int authenticate_user_agent();
+ bool authenticate_with_plugin(CONRES * cons);
};
int authenticate_user_agent(UAContext *uac)
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(),
}
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();
}
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
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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 */
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;
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 {
const char *plugin_description;
} pDirInfo;
-typedef struct s_dirpluginFuncs {
+typedef struct s_dirpluginFuncs
+{
uint32_t size;
uint32_t version;
bRC (*newPlugin)(bpContext *ctx);
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))
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);
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)
{
if (!jcr || !jcr->db) {
db_close_database(jcr, db); /* It was open just for us */
}
- return ret;
-}
+ return ret;
+}
static void usage()
{
/* 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());
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;
}
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;
}
}
-/* 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.
*/
* 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);
{"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}
};
}
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;
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;
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) {
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 */
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;
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);
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);
"%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"),
# 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);
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);
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 */
}
/*
- *
+ *
* LOCAL REMOTE
* {TLS,PSK} {TLS,PSK}
* --------------------------------------------------
* {REQUIRED,REQUIRED} {REQUIRED,OK} => t
* {REQUIRED,REQUIRED} {REQUIRED,NONE} => t
* {REQUIRED,REQUIRED} {REQUIRED,REQUIRED} => t
- *
+ *
*/
/* OK
* {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
&&
/* 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)) {
/* 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
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];
bool ServerEarlyTLS();
bool ClientCramMD5Authenticate(const char *password);
+ bool ClientCramMD5AuthenticateBase(const char *password);
bool ServerCramMD5Authenticate(const char *password);
bool HandleTLS();
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
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)
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:
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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_ */
--- /dev/null
+.libs
+ldaptest
\ No newline at end of file
--- /dev/null
+#
+# 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
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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_ */
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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_ */
--- /dev/null
+/*
+ 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);
+}
--- /dev/null
+/*
+ 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
#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,
#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