Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
authentication.cc
Go to the documentation of this file.
1 // Copyright 2012 Cloudera Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "rpc/authentication.h"
16 
17 #include <stdio.h>
18 #include <signal.h>
19 #include <boost/algorithm/string.hpp>
20 #include <boost/thread/thread.hpp>
21 #include <boost/scoped_ptr.hpp>
22 #include <boost/random/mersenne_twister.hpp>
23 #include <boost/random/uniform_int.hpp>
24 #include <boost/filesystem.hpp>
25 #include <gutil/strings/substitute.h>
26 #include <string>
27 #include <vector>
28 #include <thrift/Thrift.h>
29 #include <transport/TSasl.h>
31 #include <glog/logging.h>
32 #include <gflags/gflags.h>
33 
34 #include <ldap.h>
35 
36 #include "rpc/auth-provider.h"
38 #include "util/debug-util.h"
39 #include "util/error-util.h"
40 #include "util/network-util.h"
41 #include "util/os-util.h"
42 #include "util/promise.h"
43 #include "util/thread.h"
44 #include "util/time.h"
45 
46 #include <sys/types.h> // for stat system call
47 #include <sys/stat.h> // for stat system call
48 #include <unistd.h> // for stat system call
49 
50 #include "common/names.h"
51 
52 using boost::algorithm::is_any_of;
53 using boost::algorithm::replace_all;
54 using boost::algorithm::split;
55 using boost::mt19937;
56 using boost::uniform_int;
57 using namespace apache::thrift;
58 using namespace boost::filesystem; // for is_regular()
59 using namespace strings;
60 
61 DECLARE_string(keytab_file);
62 DECLARE_string(principal);
63 DECLARE_string(be_principal);
64 DECLARE_string(krb5_conf);
65 DECLARE_string(krb5_debug_file);
66 
67 DEFINE_int32(kerberos_reinit_interval, 60,
68  "Interval, in minutes, between kerberos ticket renewals. Each renewal will request "
69  "a ticket with a lifetime that is at least 2x the renewal interval.");
70 DEFINE_string(sasl_path, "/usr/lib/sasl2:/usr/lib64/sasl2:/usr/local/lib/sasl2:"
71  "/usr/lib/x86_64-linux-gnu/sasl2", "Colon separated list of paths to look for SASL "
72  "security library plugins.");
73 DEFINE_bool(enable_ldap_auth, false,
74  "If true, use LDAP authentication for client connections");
75 
76 DEFINE_string(ldap_uri, "", "The URI of the LDAP server to authenticate users against");
77 DEFINE_bool(ldap_tls, false, "If true, use the secure TLS protocol to connect to the LDAP"
78  " server");
79 DEFINE_string(ldap_ca_certificate, "", "The full path to the certificate file used to"
80  " authenticate the LDAP server's certificate for SSL / TLS connections.");
81 DEFINE_bool(ldap_passwords_in_clear_ok, false, "If set, will allow LDAP passwords "
82  "to be sent in the clear (without TLS/SSL) over the network. This option should not "
83  "be used in production environments" );
84 DEFINE_bool(ldap_allow_anonymous_binds, false, "(Advanced) If true, LDAP authentication "
85  "with a blank password (an 'anonymous bind') is allowed by Impala.");
86 DEFINE_bool(ldap_manual_config, false, "Obsolete; Ignored");
87 DEFINE_string(ldap_domain, "", "If set, Impala will try to bind to LDAP with a name of "
88  "the form <userid>@<ldap_domain>");
89 DEFINE_string(ldap_baseDN, "", "If set, Impala will try to bind to LDAP with a name of "
90  "the form uid=<userid>,<ldap_baseDN>");
91 DEFINE_string(ldap_bind_pattern, "", "If set, Impala will try to bind to LDAP with a name"
92  " of <ldap_bind_pattern>, but where the string #UID is replaced by the user ID. Use"
93  " to control the bind name precisely; do not set --ldap_domain or --ldap_baseDN with"
94  " this option");
95 
96 namespace impala {
97 
98 // Sasl callbacks. Why are these here? Well, Sasl isn't that bright, and
99 // instead of copying the callbacks, it just saves a pointer to them. If
100 // they're on the stack, this means that they *go away* when the function
101 // exits... so make these global and static here, and fill them in below.
102 // Vectors are used for the three latter items because that's what the thrift
103 // interface expects.
104 //
105 // The way the callbacks work is that the Sasl code will look for a registered
106 // callback in the connection-specific callback list (one of the latter three),
107 // and if not found, then look for a registered callback in the
108 // "GENERAL_CALLBACKS" list.
109 static sasl_callback_t GENERAL_CALLBACKS[5]; // Applies to all connections
110 static vector<sasl_callback_t> KERB_INT_CALLBACKS; // Internal kerberos connections
111 static vector<sasl_callback_t> KERB_EXT_CALLBACKS; // External kerberos connections
112 static vector<sasl_callback_t> LDAP_EXT_CALLBACKS; // External LDAP connections
113 
114 // Pattern for hostname substitution.
115 static const string HOSTNAME_PATTERN = "_HOST";
116 
117 // Constants for the two Sasl mechanisms we support
118 static const string KERBEROS_MECHANISM = "GSSAPI";
119 static const string PLAIN_MECHANISM = "PLAIN";
120 
121 // Required prefixes for ldap URIs:
122 static const string LDAP_URI_PREFIX = "ldap://";
123 static const string LDAPS_URI_PREFIX = "ldaps://";
124 
125 // We implement an "auxprop" plugin for the Sasl layer in order to have a hook in which
126 // to log messages about the start of authentication. This is that plugin's name.
127 static const string IMPALA_AUXPROP_PLUGIN = "impala-auxprop";
128 
129 bool SaslAuthProvider::env_setup_complete_ = false;
130 AuthManager* AuthManager::auth_manager_ = new AuthManager();
131 
132 // This Sasl callback is called when the underlying cyrus-sasl layer has
133 // something that it would like to say. We catch it and turn it into the
134 // appropriate LOG() call.
135 //
136 // context: Passed a (char *) that comes from the initialization, used
137 // to describe the kerb|ldap internal|external context
138 // level: The SASL_LOG_ level
139 // message: The message to log
140 // Return: Always SASL_OK, unless message is NULL, then it's SASL_BADPARAM.
141 static int SaslLogCallback(void* context, int level, const char* message) {
142  if (message == NULL) return SASL_BADPARAM;
143  const char* authctx = (context == NULL) ? "Unknown" :
144  reinterpret_cast<const char*>(context);
145 
146  switch (level) {
147  case SASL_LOG_NONE: // "Don't log anything"
148  case SASL_LOG_PASS: // "Traces... including passwords" - don't log!
149  break;
150  case SASL_LOG_ERR: // "Unusual errors"
151  case SASL_LOG_FAIL: // "Authentication failures"
152  LOG(ERROR) << "SASL message (" << authctx << "): " << message;
153  break;
154  case SASL_LOG_WARN: // "Non-fatal warnings"
155  LOG(WARNING) << "SASL message (" << authctx << "): " << message;
156  break;
157  case SASL_LOG_NOTE: // "More verbose than WARN"
158  LOG(INFO) << "SASL message (" << authctx << "): " << message;
159  break;
160  case SASL_LOG_DEBUG: // "More verbose than NOTE"
161  VLOG(1) << "SASL message (" << authctx << "): " << message;
162  break;
163  case SASL_LOG_TRACE: // "Traces of internal protocols"
164  default:
165  VLOG(3) << "SASL message (" << authctx << "): " << message;
166  break;
167  }
168 
169  return SASL_OK;
170 }
171 
172 // This callback is only called when we're providing LDAP authentication. This "check
173 // pass" callback is our hook to ask the real LDAP server if we're allowed to log in or
174 // not. We can be thought of as a proxy for LDAP logins - the user gives their password
175 // to us, and we pass it to the real LDAP server.
176 //
177 // Note that this method uses ldap_sasl_bind_s(), which does *not* provide any security
178 // to the connection between Impala and the LDAP server. You must either set --ldap_tls,
179 // or have a URI which has "ldaps://" as the scheme in order to get a secure connection.
180 // Use --ldap_ca_certificate to specify the location of the certificate used to confirm
181 // the authenticity of the LDAP server certificate.
182 //
183 // conn: The Sasl connection struct, which we ignore
184 // context: Ignored; always NULL
185 // user: The username to authenticate
186 // pass: The password to use
187 // passlen: The length of pass
188 // propctx: Ignored - properties requested
189 // Return: SASL_OK on success, SASL_FAIL otherwise
190 int SaslLdapCheckPass(sasl_conn_t* conn, void* context, const char* user,
191  const char* pass, unsigned passlen, struct propctx* propctx) {
192  if (passlen == 0 && !FLAGS_ldap_allow_anonymous_binds) {
193  // Disable anonymous binds.
194  return SASL_FAIL;
195  }
196 
197  LDAP* ld;
198  int rc = ldap_initialize(&ld, FLAGS_ldap_uri.c_str());
199  if (rc != LDAP_SUCCESS) {
200  LOG(WARNING) << "Could not initialize connection with LDAP server ("
201  << FLAGS_ldap_uri << "). Error: " << ldap_err2string(rc);
202  return SASL_FAIL;
203  }
204 
205  // Force the LDAP version to 3 to make sure TLS is supported.
206  int ldap_ver = 3;
207  ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_ver);
208 
209  // If -ldap_tls is turned on, and the URI is ldap://, issue a STARTTLS operation.
210  // Note that we'll ignore -ldap_tls when using ldaps:// because we've already
211  // got a secure connection (and the LDAP server will reject the STARTTLS).
212  if (FLAGS_ldap_tls && (FLAGS_ldap_uri.find(LDAP_URI_PREFIX) == 0)) {
213  int tls_rc = ldap_start_tls_s(ld, NULL, NULL);
214  if (tls_rc != LDAP_SUCCESS) {
215  LOG(WARNING) << "Could not start TLS secure connection to LDAP server ("
216  << FLAGS_ldap_uri << "). Error: " << ldap_err2string(tls_rc);
217  ldap_unbind_ext(ld, NULL, NULL);
218  return SASL_FAIL;
219  }
220  VLOG(2) << "Started TLS connection with LDAP server: " << FLAGS_ldap_uri;
221  }
222 
223  // Map the user string into an acceptable LDAP "DN" (distinguished name)
224  string user_str = user;
225  if (!FLAGS_ldap_domain.empty()) {
226  // Append @domain if there isn't already an @ in the user string.
227  if (user_str.find("@") == string::npos) {
228  user_str = Substitute("$0@$1", user_str, FLAGS_ldap_domain);
229  }
230  } else if (!FLAGS_ldap_baseDN.empty()) {
231  user_str = Substitute("uid=$0,$1", user_str, FLAGS_ldap_baseDN);
232  } else if (!FLAGS_ldap_bind_pattern.empty()) {
233  user_str = FLAGS_ldap_bind_pattern;
234  replace_all(user_str, "#UID", user);
235  }
236 
237  // Map the password into a credentials structure
238  struct berval cred;
239  cred.bv_val = const_cast<char*>(pass);
240  cred.bv_len = passlen;
241 
242  VLOG_QUERY << "Trying simple LDAP bind for: " << user_str;
243 
244  rc = ldap_sasl_bind_s(ld, user_str.c_str(), LDAP_SASL_SIMPLE, &cred,
245  NULL, NULL, NULL);
246  // Free ld
247  ldap_unbind_ext(ld, NULL, NULL);
248  if (rc != LDAP_SUCCESS) {
249  LOG(WARNING) << "LDAP authentication failure for " << user_str
250  << " : " << ldap_err2string(rc);
251  return SASL_FAIL;
252  }
253 
254  VLOG_QUERY << "LDAP bind successful";
255 
256  return SASL_OK;
257 }
258 
259 // Sasl wants a way to ask us about some options, this function provides
260 // answers. Currently we only support telling Sasl about the log_level; other
261 // items are printed out for curiosity's sake, but otherwise ignored.
262 //
263 // context: Ignored, always NULL
264 // plugin_name: If applicable, the name of the plugin making the
265 // request. NULL if it's a general option.
266 // option: The name of the configurable parameter
267 // result: A char * array to hold our answer
268 // len: Bytes at result
269 // Return: SASL_OK for things we deal with; SASL_FAIL otherwise. The
270 // cyrus-sasl code rarely checks the return value; it's more
271 // interested in whether we fill in *result or not.
272 static int SaslGetOption(void* context, const char* plugin_name, const char* option,
273  const char** result, unsigned* len) {
274 
275  if (plugin_name == NULL) {
276  if (strcmp("log_level", option) == 0) {
277  int level = SASL_LOG_WARN;
278  if (VLOG_CONNECTION_IS_ON) {
279  level = SASL_LOG_DEBUG;
280  } else if (VLOG_ROW_IS_ON) {
281  level = SASL_LOG_TRACE;
282  }
283  static char buf[4];
284  snprintf(buf, 4, "%d", level);
285  *result = buf;
286  if (len != NULL) *len = strlen(buf);
287  return SASL_OK;
288  } else if (strcmp("auxprop_plugin", option) == 0) {
289  *result = IMPALA_AUXPROP_PLUGIN.c_str();
290  if (len != NULL) *len = strlen(*result);
291  return SASL_OK;
292  }
293 
294  VLOG(3) << "Sasl general option " << option << " requested";
295  return SASL_FAIL;
296  }
297 
298  VLOG(3) << "Sasl option " << plugin_name << " : "
299  << option << " requested";
300  return SASL_FAIL;
301 }
302 
303 // The "auxprop" plugin interface was intended to be a database service for the "glue"
304 // layer between the mechanisms and applications. We, however, hijack this interface
305 // simply in order to provide an audit message prior to that start of authentication.
306 static void ImpalaAuxpropLookup(void* glob_context, sasl_server_params_t* sparams,
307  unsigned int flags, const char* user, unsigned ulen) {
308  // This callback is called twice, once with this flag clear, and once with
309  // this flag set. We only want to log this message once, so only log it when
310  // the flag is clear.
311  if ((flags & SASL_AUXPROP_AUTHZID) == 0) {
312  string ustr(user, ulen);
313  VLOG(2) << "Attempting to authenticate user \"" << ustr << "\"";
314  }
315 }
316 
317 // Singleton structure used to register our auxprop plugin with Sasl
318 static sasl_auxprop_plug_t impala_auxprop_plugin = {
319  0, // feature flag
320  0, // 'spare'
321  NULL, // global plugin state
322  NULL, // Free global state callback
323  &ImpalaAuxpropLookup, // Auxprop lookup method
324  const_cast<char*>(IMPALA_AUXPROP_PLUGIN.c_str()), // Name of plugin
325  NULL // Store property callback
326 };
327 
328 // This is a Sasl callback that's called in order to register our "auxprop"
329 // plugin. We give it the structure above, which installs ImpalaAuxpropLookup
330 // as the lookup method.
331 int ImpalaAuxpropInit(const sasl_utils_t* utils, int max_version, int* out_version,
332  sasl_auxprop_plug_t** plug, const char* plugname) {
333  VLOG(2) << "Initializing Impala SASL plugin: " << plugname;
334  *plug = &impala_auxprop_plugin;
335  *out_version = max_version;
336  return SASL_OK;
337 }
338 
339 // This Sasl callback will tell us what files Sasl is trying to access. It's
340 // here just for curiousity's sake at the moment. It might be useful for
341 // telling us precisely which plugins have been found.
342 //
343 // context: Ignored, always NULL
344 // file: The file being accessed
345 // type: What type of thing is it: plugin, config file, etc
346 // Return: SASL_OK
347 static int SaslVerifyFile(void* context, const char* file,
348  sasl_verify_type_t type ) {
349  switch(type) {
350  case SASL_VRFY_PLUGIN:
351  VLOG(2) << "Sasl found plugin " << file;
352  break;
353  case SASL_VRFY_CONF:
354  VLOG(2) << "Sasl trying to access config file " << file;
355  break;
356  case SASL_VRFY_PASSWD:
357  VLOG(2) << "Sasl accessing password file " << file;
358  break;
359  default:
360  VLOG(2) << "Sasl found other file " << file;
361  break;
362  }
363  return SASL_OK;
364 }
365 
366 // This callback could be used to authorize or restrict access to certain
367 // users. Currently it is used to log a message that we successfully
368 // authenticated with a user on an internal connection.
369 //
370 // conn: Sasl connection - Ignored
371 // context: Ignored, always NULL
372 // requested_user: The identity/username to authorize
373 // rlen: Length of above
374 // auth_identity: "The identity associated with the secret"
375 // alen: Length of above
376 // def_realm: Default user realm
377 // urlen: Length of above
378 // propctx: Auxiliary properties - Ignored
379 // Return: SASL_OK
380 static int SaslAuthorizeInternal(sasl_conn_t* conn, void* context,
381  const char* requested_user, unsigned rlen,
382  const char* auth_identity, unsigned alen,
383  const char* def_realm, unsigned urlen,
384  struct propctx* propctx) {
385  // We say "principal" here becase this is for internal communication, and hence
386  // ought always be --principal or --be_principal
387  VLOG(1) << "Successfully authenticated principal \"" << string(requested_user, rlen)
388  << "\" on an internal connection";
389  return SASL_OK;
390 }
391 
392 // This callback could be used to authorize or restrict access to certain
393 // users. Currently it is used to log a message that we successfully
394 // authenticated with a user on an external connection.
395 //
396 // conn: Sasl connection - Ignored
397 // context: Ignored, always NULL
398 // requested_user: The identity/username to authorize
399 // rlen: Length of above
400 // auth_identity: "The identity associated with the secret"
401 // alen: Length of above
402 // def_realm: Default user realm
403 // urlen: Length of above
404 // propctx: Auxiliary properties - Ignored
405 // Return: SASL_OK
406 static int SaslAuthorizeExternal(sasl_conn_t* conn, void* context,
407  const char* requested_user, unsigned rlen,
408  const char* auth_identity, unsigned alen,
409  const char* def_realm, unsigned urlen,
410  struct propctx* propctx) {
411  LOG(INFO) << "Successfully authenticated client user \""
412  << string(requested_user, rlen) << "\"";
413  return SASL_OK;
414 }
415 
416 // Sasl callback - where to look for plugins. We return the list of possible
417 // places the plugins might be; this comes from the sasl_path flag.
418 //
419 // Places we know they might be:
420 // UBUNTU: /usr/lib/sasl2
421 // CENTOS: /usr/lib64/sasl2
422 // custom install: /usr/local/lib/sasl2
423 // UBUNTU: /usr/lib/x86_64-linux-gnu/sasl2
424 //
425 // context: Ignored, always NULL
426 // path: We return the plugin paths here.
427 // Return: SASL_OK
428 static int SaslGetPath(void* context, const char** path) {
429  *path = FLAGS_sasl_path.c_str();
430  return SASL_OK;
431 }
432 
433 // When operating as a Kerberos client (internal connections only), we need to
434 // 'kinit' as the principal. A thread is created and calls this function for
435 // that purpose, and to periodically renew the ticket as well.
436 //
437 // first_kinit: Used to communicate success/failure of the initial kinit call to
438 // the parent thread
439 // Return: Only if the first call to 'kinit' fails
440 void SaslAuthProvider::RunKinit(Promise<Status>* first_kinit) {
441  // Minumum lifetime to request for each ticket renewal.
442  static const int MIN_TICKET_LIFETIME_IN_MINS = 1440;
443 
444  // Set the ticket lifetime to an arbitrarily large value or 2x the renewal interval,
445  // whichever is larger. The KDC will automatically fall back to using the maximum
446  // allowed allowed value if a longer lifetime is requested, so it is okay to be greedy
447  // here.
448  int ticket_lifetime_mins =
449  max(MIN_TICKET_LIFETIME_IN_MINS, FLAGS_kerberos_reinit_interval * 2);
450 
451  // Pass the path to the key file and the principal. Make the ticket renewable.
452  // Calling kinit -R ensures the ticket makes it to the cache, and should be a separate
453  // call to kinit.
454  const string kinit_cmd = Substitute("kinit -r $0m -k -t $1 $2 2>&1",
455  ticket_lifetime_mins, keytab_file_, principal_);
456 
457  bool first_time = true;
458  int failures_since_renewal = 0;
459  while (true) {
460  LOG(INFO) << "Registering " << principal_ << ", keytab file " << keytab_file_;
461  string kinit_output;
462  bool success = RunShellProcess(kinit_cmd, &kinit_output);
463 
464  if (!success) {
465  const string& err_msg = Substitute(
466  "Failed to obtain Kerberos ticket for principal: $0. $1", principal_,
467  kinit_output);
468  if (first_time) {
469  first_kinit->Set(Status(err_msg));
470  return;
471  } else {
472  LOG(ERROR) << err_msg;
473  }
474  }
475 
476  if (success) {
477  if (first_time) {
478  first_time = false;
479  first_kinit->Set(Status::OK);
480  }
481  failures_since_renewal = 0;
482  // Workaround for Kerberos 1.8.1 - wait a short time, before requesting a renewal of
483  // the ticket-granting ticket. The sleep time is >1s, to force the system clock to
484  // roll-over o a new second, avoiding a race between grant and renewal.
485  SleepForMs(1500);
486  string krenew_output;
487  if (RunShellProcess("kinit -R", &krenew_output)) {
488  LOG(INFO) << "Successfully renewed Keberos ticket";
489  } else {
490  // We couldn't renew the ticket so just report the error. Existing connections
491  // are ok and we'll try to renew the ticket later.
492  ++failures_since_renewal;
493  LOG(ERROR) << "Failed to extend Kerberos ticket. Error: " << krenew_output
494  << ". Failure count: " << failures_since_renewal;
495  }
496  }
497  // Sleep for the renewal interval, minus a random time between 0-5 minutes to help
498  // avoid a storm at the KDC. Additionally, never sleep less than a minute to
499  // reduce KDC stress due to frequent renewals.
500  mt19937 generator;
501  uniform_int<> dist(0, 300);
502  SleepForMs(1000 * max((60 * FLAGS_kerberos_reinit_interval) - dist(generator), 60));
503  }
504 }
505 
506 Status InitAuth(const string& appname) {
507  // We only set up Sasl things if we are indeed going to be using Sasl.
508  // Checking of these flags for sanity is done later, but this check is good
509  // enough at this early stage:
510  if (FLAGS_enable_ldap_auth || !FLAGS_principal.empty()) {
511  // Good idea to have logging everywhere
512  GENERAL_CALLBACKS[0].id = SASL_CB_LOG;
513  GENERAL_CALLBACKS[0].proc = (int (*)())&SaslLogCallback;
514  GENERAL_CALLBACKS[0].context = ((void *)"General");
515 
516  // Need this here so we can find available mechanisms
517  GENERAL_CALLBACKS[1].id = SASL_CB_GETPATH;
518  GENERAL_CALLBACKS[1].proc = (int (*)())&SaslGetPath;
519  GENERAL_CALLBACKS[1].context = NULL;
520 
521  // Allows us to view and set some options
522  GENERAL_CALLBACKS[2].id = SASL_CB_GETOPT;
523  GENERAL_CALLBACKS[2].proc = (int (*)())&SaslGetOption;
524  GENERAL_CALLBACKS[2].context = NULL;
525 
526  // For curiosity, let's see what files are being touched.
527  GENERAL_CALLBACKS[3].id = SASL_CB_VERIFYFILE;
528  GENERAL_CALLBACKS[3].proc = (int (*)())&SaslVerifyFile;
529  GENERAL_CALLBACKS[3].context = NULL;
530 
531  GENERAL_CALLBACKS[4].id = SASL_CB_LIST_END;
532 
533  if (!FLAGS_principal.empty()) {
534  // Callbacks for when we're a Kerberos Sasl internal connection. Just do logging.
535  KERB_INT_CALLBACKS.resize(3);
536 
537  KERB_INT_CALLBACKS[0].id = SASL_CB_LOG;
538  KERB_INT_CALLBACKS[0].proc = (int (*)())&SaslLogCallback;
539  KERB_INT_CALLBACKS[0].context = ((void *)"Kerberos (internal)");
540 
541  KERB_INT_CALLBACKS[1].id = SASL_CB_PROXY_POLICY;
542  KERB_INT_CALLBACKS[1].proc = (int (*)())&SaslAuthorizeInternal;
543  KERB_INT_CALLBACKS[1].context = NULL;
544 
545  KERB_INT_CALLBACKS[2].id = SASL_CB_LIST_END;
546 
547  // Our externally facing Sasl callbacks for Kerberos communication
548  KERB_EXT_CALLBACKS.resize(3);
549 
550  KERB_EXT_CALLBACKS[0].id = SASL_CB_LOG;
551  KERB_EXT_CALLBACKS[0].proc = (int (*)())&SaslLogCallback;
552  KERB_EXT_CALLBACKS[0].context = ((void *)"Kerberos (external)");
553 
554  KERB_EXT_CALLBACKS[1].id = SASL_CB_PROXY_POLICY;
555  KERB_EXT_CALLBACKS[1].proc = (int (*)())&SaslAuthorizeExternal;
556  KERB_EXT_CALLBACKS[1].context = NULL;
557 
558  KERB_EXT_CALLBACKS[2].id = SASL_CB_LIST_END;
559  }
560 
561  if (FLAGS_enable_ldap_auth) {
562  // Our external server-side SASL callbacks for LDAP communication
563  LDAP_EXT_CALLBACKS.resize(4);
564 
565  LDAP_EXT_CALLBACKS[0].id = SASL_CB_LOG;
566  LDAP_EXT_CALLBACKS[0].proc = (int (*)())&SaslLogCallback;
567  LDAP_EXT_CALLBACKS[0].context = ((void *)"LDAP");
568 
569  LDAP_EXT_CALLBACKS[1].id = SASL_CB_PROXY_POLICY;
570  LDAP_EXT_CALLBACKS[1].proc = (int (*)())&SaslAuthorizeExternal;
571  LDAP_EXT_CALLBACKS[1].context = NULL;
572 
573  // This last callback is where we take the password and turn around and
574  // call into openldap.
575  LDAP_EXT_CALLBACKS[2].id = SASL_CB_SERVER_USERDB_CHECKPASS;
576  LDAP_EXT_CALLBACKS[2].proc = (int (*)())&SaslLdapCheckPass;
577  LDAP_EXT_CALLBACKS[2].context = NULL;
578 
579  LDAP_EXT_CALLBACKS[3].id = SASL_CB_LIST_END;
580  }
581 
582  try {
583  // We assume all impala processes are both server and client.
586  } catch (sasl::SaslServerImplException& e) {
587  stringstream err_msg;
588  err_msg << "Could not initialize Sasl library: " << e.what();
589  return Status(err_msg.str());
590  }
591 
592  // Add our auxprop plugin, which gives us a hook before authentication
593  int rc = sasl_auxprop_add_plugin(IMPALA_AUXPROP_PLUGIN.c_str(), &ImpalaAuxpropInit);
594  if (rc != SASL_OK) {
595  return Status(Substitute("Error adding Sasl auxprop plugin: $0",
596  sasl_errstring(rc, NULL, NULL)));
597  }
598  }
599 
600  RETURN_IF_ERROR(AuthManager::GetInstance()->Init());
601  return Status::OK;
602 }
603 
604 // Ensure that /var/tmp (the location of the Kerberos replay cache) has drwxrwxrwt
605 // permissions. If it doesn't, Kerberos will be unhappy in a way that's very difficult
606 // to debug. We do this using direct stat() calls because boost doesn't support the
607 // detail we need.
609  struct stat st;
610 
611  if (stat("/var/tmp", &st) < 0) {
612  return Status(Substitute("Problem accessing /var/tmp: $0", GetStrErrMsg()));
613  }
614 
615  if (!(st.st_mode & S_IFDIR)) {
616  return Status("Error: /var/tmp is not a directory");
617  }
618 
619  if ((st.st_mode & 01777) != 01777) {
620  return Status("Error: The permissions on /var/tmp must precisely match "
621  "\"drwxrwxrwt\". This directory is used by the Kerberos replay cache. To "
622  "rectify this issue, run \"chmod 01777 /var/tmp\" as root.");
623  }
624 
625  return Status::OK;
626 }
627 
628 Status SaslAuthProvider::InitKerberos(const string& principal,
629  const string& keytab_file) {
630  principal_ = principal;
631  keytab_file_ = keytab_file;
632  // The logic here is that needs_kinit_ is false unless we are the internal
633  // auth provider and we support kerberos.
634  needs_kinit_ = is_internal_;
635 
636  // Replace the string _HOST in principal with our hostname.
637  size_t off = principal_.find(HOSTNAME_PATTERN);
638  if (off != string::npos) {
639  string hostname;
640  RETURN_IF_ERROR(GetHostname(&hostname));
641  principal_.replace(off, HOSTNAME_PATTERN.size(), hostname);
642  }
643 
644  vector<string> names;
645  split(names, principal_, is_any_of("/@"));
646 
647  if (names.size() != 3) {
648  return Status(Substitute("Kerberos principal should be of the form: "
649  "<service>/<hostname>@<realm> - got: $0", principal_));
650  }
651 
652  service_name_ = names[0];
653  hostname_ = names[1];
654  realm_ = names[2];
655 
657  RETURN_IF_ERROR(InitKerberosEnv());
658 
659  LOG(INFO) << "Using " << (is_internal_ ? "internal" : "external")
660  << " kerberos principal \"" << service_name_ << "/"
661  << hostname_ << "@" << realm_ << "\"";
662 
663  return Status::OK;
664 }
665 
666 // For the environment variable attr, append "-Dthing=thingval" if "thing" is not already
667 // in the current attr's value.
668 static Status EnvAppend(const string& attr, const string& thing, const string& thingval) {
669  // Carefully append to attr. There are three distinct cases:
670  // 1. Attr doesn't exist: set it
671  // 2. Attr exists, and doesn't contain thing: append to it
672  // 3. Attr exists, and already contains thing: do nothing
673  string current_val;
674  char* current_val_c = getenv(attr.c_str());
675  if (current_val_c != NULL) {
676  current_val = current_val_c;
677  }
678 
679  if (!current_val.empty() && (current_val.find(thing) != string::npos)) {
680  // Case 3 above
681  return Status::OK;
682  }
683 
684  stringstream val_out;
685  if (!current_val.empty()) {
686  // Case 2 above
687  val_out << current_val << " ";
688  }
689  val_out << "-D" << thing << "=" << thingval;
690 
691  if (setenv(attr.c_str(), val_out.str().c_str(), 1) < 0) {
692  return Status(Substitute("Bad $0=$1 value: Could not set environment variable $2: $3",
693  thing, thingval, attr, GetStrErrMsg()));
694  }
695 
696  return Status::OK;
697 }
698 
699 Status SaslAuthProvider::InitKerberosEnv() {
700  DCHECK(!principal_.empty());
701 
702  // Called only during setup; no locking required.
703  if (env_setup_complete_) return Status::OK;
704 
705  if (!is_regular(keytab_file_)) {
706  return Status(Substitute("Bad --keytab_file value: The file $0 is not a "
707  "regular file", keytab_file_));
708  }
709 
710  // Set the keytab name in the environment so that Sasl Kerberos and kinit can
711  // find and use it.
712  if (setenv("KRB5_KTNAME", keytab_file_.c_str(), 1)) {
713  return Status(Substitute("Kerberos could not set KRB5_KTNAME: $0",
714  GetStrErrMsg()));
715  }
716 
717  // We want to set a custom location for the impala credential cache.
718  // Usually, it's /tmp/krb5cc_xxx where xxx is the UID of the process. This
719  // is normally fine, but if you're not running impala daemons as user
720  // 'impala', the kinit we perform is going to blow away credentials for the
721  // current user. Not setting this isn't technically fatal, so ignore errors.
722  (void) setenv("KRB5CCNAME", "/tmp/krb5cc_impala_internal", 1);
723 
724  // If an alternate krb5_conf location is supplied, set both KRB5_CONFIG and
725  // JAVA_TOOL_OPTIONS in the environment.
726  if (!FLAGS_krb5_conf.empty()) {
727  // Ensure it points to a regular file
728  if (!is_regular(FLAGS_krb5_conf)) {
729  return Status(Substitute("Bad --krb5_conf value: The file $0 is not a "
730  "regular file", FLAGS_krb5_conf));
731  }
732 
733  // Overwrite KRB5_CONFIG
734  if (setenv("KRB5_CONFIG", FLAGS_krb5_conf.c_str(), 1) < 0) {
735  return Status(Substitute("Bad --krb5_conf value: Could not set "
736  "KRB5_CONFIG: $0", GetStrErrMsg()));
737  }
738 
739  RETURN_IF_ERROR(EnvAppend("JAVA_TOOL_OPTIONS", "java.security.krb5.conf",
740  FLAGS_krb5_conf));
741 
742  LOG(INFO) << "Using custom Kerberos configuration file at "
743  << FLAGS_krb5_conf;
744  }
745 
746  // Set kerberos debugging, if applicable. Errors are non-fatal.
747  if (!FLAGS_krb5_debug_file.empty()) {
748  bool krb5_debug_fail = false;
749  if (setenv("KRB5_TRACE", FLAGS_krb5_debug_file.c_str(), 1) < 0) {
750  LOG(WARNING) << "Failed to set KRB5_TRACE; --krb5_debuf_file not enabled for "
751  "back-end code";
752  krb5_debug_fail = true;
753  }
754  if (!EnvAppend("JAVA_TOOL_OPTIONS", "sun.security.krb5.debug", "true").ok()) {
755  LOG(WARNING) << "Failed to set JAVA_TOOL_OPTIONS; --krb5_debuf_file not enabled "
756  "for front-end code";
757  krb5_debug_fail = true;
758  }
759  if (!krb5_debug_fail) {
760  LOG(INFO) << "Kerberos debugging is enabled; kerberos messages written to "
761  << FLAGS_krb5_debug_file;
762  }
763  }
764 
765  env_setup_complete_ = true;
766  return Status::OK;
767 }
768 
769 Status SaslAuthProvider::Start() {
770  // True for kerberos internal use
771  if (needs_kinit_) {
772  DCHECK(is_internal_);
773  DCHECK(!principal_.empty());
774  Promise<Status> first_kinit;
775  stringstream thread_name;
776  thread_name << "kinit-" << principal_;
777  kinit_thread_.reset(new Thread("authentication", thread_name.str(),
778  &SaslAuthProvider::RunKinit, this, &first_kinit));
779  LOG(INFO) << "Waiting for Kerberos ticket for principal: " << principal_;
780  RETURN_IF_ERROR(first_kinit.Get());
781  LOG(INFO) << "Kerberos ticket granted to " << principal_;
782  }
783 
784  if (has_ldap_) {
785  DCHECK(!is_internal_);
786  if (!FLAGS_ldap_ca_certificate.empty()) {
787  int set_rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE,
788  FLAGS_ldap_ca_certificate.c_str());
789  if (set_rc != LDAP_SUCCESS) {
790  return Status(Substitute("Could not set location of LDAP server cert: $0",
791  ldap_err2string(set_rc)));
792  }
793  } else {
794  // A warning was already logged...
795  int val = LDAP_OPT_X_TLS_ALLOW;
796  int set_rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT,
797  reinterpret_cast<void*>(&val));
798  if (set_rc != LDAP_SUCCESS) {
799  return Status(Substitute(
800  "Could not disable certificate requirement for LDAP server: $0",
801  ldap_err2string(set_rc)));
802  }
803  }
804 
805  if (hostname_.empty()) {
806  RETURN_IF_ERROR(GetHostname(&hostname_));
807  }
808  }
809 
810  return Status::OK;
811 }
812 
813 Status SaslAuthProvider::GetServerTransportFactory(
814  shared_ptr<TTransportFactory>* factory) {
815  DCHECK(!principal_.empty() || has_ldap_);
816 
817  // This is the heart of the link between this file and thrift. Here we
818  // associate a Sasl mechanism with our callbacks.
819  try {
820  map<string, string> sasl_props; // Empty; unused by Thrift
821  TSaslServerTransport::Factory* sst_factory = NULL;
822  factory->reset(sst_factory = new TSaslServerTransport::Factory());
823 
824  if(!principal_.empty()) {
825  // Tell it about Kerberos:
826  sst_factory->addServerDefinition(KERBEROS_MECHANISM, service_name_,
827  hostname_, realm_, 0, sasl_props,
828  is_internal_ ? KERB_INT_CALLBACKS : KERB_EXT_CALLBACKS);
829  }
830 
831  if (has_ldap_) {
832  // Tell it about LDAP:
833  sst_factory->addServerDefinition(PLAIN_MECHANISM, "LDAP", hostname_,
834  "", 0, sasl_props, LDAP_EXT_CALLBACKS);
835  }
836 
837  } catch (const TException& e) {
838  LOG(ERROR) << "Failed to create Sasl Server transport factory: "
839  << e.what();
840  return Status(e.what());
841  }
842 
843  VLOG_RPC << "Made " << (is_internal_ ? "internal" : "external")
844  << " server transport factory with "
845  << (!principal_.empty() ? "Kerberos " : " ")
846  << (has_ldap_ ? "LDAP " : " ") << "authentication";
847 
848  return Status::OK;
849 }
850 
851 Status SaslAuthProvider::WrapClientTransport(const string& hostname,
852  shared_ptr<TTransport> raw_transport, const string& service_name,
853  shared_ptr<TTransport>* wrapped_transport) {
854 
855  shared_ptr<sasl::TSasl> sasl_client;
856  const map<string, string> props; // Empty; unused by thrift
857  const string auth_id; // Empty; unused by thrift
858 
859  DCHECK(!has_ldap_);
860  DCHECK(is_internal_);
861 
862  // Since the daemons are never LDAP clients, we go straight to Kerberos
863  try {
864  const string& service = service_name.empty() ? service_name_ : service_name;
865  sasl_client.reset(new sasl::TSaslClient(KERBEROS_MECHANISM, auth_id,
866  service, hostname, props, &KERB_INT_CALLBACKS[0]));
867  } catch (sasl::SaslClientImplException& e) {
868  LOG(ERROR) << "Failed to create a GSSAPI/SASL client: " << e.what();
869  return Status(e.what());
870  }
871  wrapped_transport->reset(new TSaslClientTransport(sasl_client, raw_transport));
872 
873  // This function is called immediately prior to sasl_client_start(), and so
874  // can be used to log an "I'm beginning authentication for this principal"
875  // message. Unfortunately, there are no hooks for us at this level to say
876  // that we successfully authenticated as a client.
877  VLOG_RPC << "Initiating client connection using principal " << principal_;
878 
879  return Status::OK;
880 }
881 
882 Status NoAuthProvider::GetServerTransportFactory(shared_ptr<TTransportFactory>* factory) {
883  // No Sasl - yawn. Here, have a regular old buffered transport.
884  factory->reset(new TBufferedTransportFactory());
885  return Status::OK;
886 }
887 
888 Status NoAuthProvider::WrapClientTransport(const string& hostname,
889  shared_ptr<TTransport> raw_transport, const string& dummy_service,
890  shared_ptr<TTransport>* wrapped_transport) {
891  // No Sasl - yawn. Don't do any transport wrapping for clients.
892  *wrapped_transport = raw_transport;
893  return Status::OK;
894 }
895 
896 Status AuthManager::Init() {
897  bool use_ldap = false;
898  const string excl_msg = "--$0 and --$1 are mutually exclusive "
899  "and should not be set together";
900 
901  // Get all of the flag validation out of the way
902  if (FLAGS_enable_ldap_auth) {
903  use_ldap = true;
904 
905  if (!FLAGS_ldap_domain.empty()) {
906  if (!FLAGS_ldap_baseDN.empty()) {
907  return Status(Substitute(excl_msg, "ldap_domain", "ldap_baseDN"));
908  }
909  if (!FLAGS_ldap_bind_pattern.empty()) {
910  return Status(Substitute(excl_msg, "ldap_domain", "ldap_bind_pattern"));
911  }
912  } else if (!FLAGS_ldap_baseDN.empty()) {
913  if (!FLAGS_ldap_bind_pattern.empty()) {
914  return Status(Substitute(excl_msg, "ldap_baseDN", "ldap_bind_pattern"));
915  }
916  }
917 
918  if (FLAGS_ldap_uri.empty()) {
919  return Status("--ldap_uri must be supplied when --ldap_enable_auth is set");
920  }
921 
922  if ((FLAGS_ldap_uri.find(LDAP_URI_PREFIX) != 0) &&
923  (FLAGS_ldap_uri.find(LDAPS_URI_PREFIX) != 0)) {
924  return Status(Substitute("--ldap_uri must start with either $0 or $1",
926  }
927 
928  LOG(INFO) << "Using LDAP authentication with server " << FLAGS_ldap_uri;
929 
930  if (!FLAGS_ldap_tls && (FLAGS_ldap_uri.find(LDAPS_URI_PREFIX) != 0)) {
931  if (FLAGS_ldap_passwords_in_clear_ok) {
932  LOG(WARNING) << "LDAP authentication is being used, but without TLS. "
933  << "ALL PASSWORDS WILL GO OVER THE NETWORK IN THE CLEAR.";
934  } else {
935  return Status("LDAP authentication specified, but without TLS. "
936  "Passwords would go over the network in the clear. "
937  "Enable TLS with --ldap_tls or use an ldaps:// URI. "
938  "To override this is non-production environments, "
939  "specify --ldap_passwords_in_clear_ok");
940  }
941  } else if (FLAGS_ldap_ca_certificate.empty()) {
942  LOG(WARNING) << "LDAP authentication is being used with TLS, but without "
943  << "an --ldap_ca_certificate file, the identity of the LDAP "
944  << "server cannot be verified. Network communication (and "
945  << "hence passwords) could be intercepted by a "
946  << "man-in-the-middle attack";
947  }
948  }
949 
950  if (FLAGS_principal.empty() && !FLAGS_be_principal.empty()) {
951  return Status("A back end principal (--be_principal) was supplied without "
952  "also supplying a regular principal (--principal). Either --principal "
953  "must be supplied alone, in which case it applies to all communication, "
954  "or --principal and --be_principal must be supplied together, in which "
955  "case --principal is used in external communication and --be_principal "
956  "is used in internal (back-end) communication.");
957  }
958 
959  // When acting as a server on external connections:
960  string kerberos_external_principal;
961  // When acting as a client, or as a server on internal connections:
962  string kerberos_internal_principal;
963 
964  if (!FLAGS_principal.empty()) {
965  kerberos_external_principal = FLAGS_principal;
966  if (FLAGS_be_principal.empty()) {
967  kerberos_internal_principal = FLAGS_principal;
968  } else {
969  kerberos_internal_principal = FLAGS_be_principal;
970  }
971  }
972 
973  // This is written from the perspective of the daemons - thus "internal"
974  // means "I am used for communication with other daemons, both as a client
975  // and as a server". "External" means that "I am used when being a server
976  // for clients that are external - that is, they aren't daemons - like the
977  // impala shell, odbc, jdbc, etc.
978  //
979  // Flags | Internal | External
980  // --------- | -------- | --------
981  // None | NoAuth | NoAuth
982  // LDAP only | NoAuth | Sasl(ldap)
983  // Kerb only | Sasl(be) | Sasl(fe)
984  // Both | Sasl(be) | Sasl(fe+ldap)
985 
986  // Set up the internal auth provider as per above. Since there's no LDAP on
987  // the client side, this is just a check for the "back end" kerberos
988  // principal.
989  if (!kerberos_internal_principal.empty()) {
990  SaslAuthProvider* sap = NULL;
991  internal_auth_provider_.reset(sap = new SaslAuthProvider(true));
992  RETURN_IF_ERROR(sap->InitKerberos(kerberos_internal_principal,
993  FLAGS_keytab_file));
994  LOG(INFO) << "Internal communication is authenticated with Kerberos";
995  } else {
996  internal_auth_provider_.reset(new NoAuthProvider());
997  LOG(INFO) << "Internal communication is not authenticated";
998  }
999  RETURN_IF_ERROR(internal_auth_provider_->Start());
1000 
1001  // Set up the external auth provider as per above. Either a "front end"
1002  // principal or ldap tells us to use a SaslAuthProvider, and we fill in
1003  // details from there.
1004  if (use_ldap || !kerberos_external_principal.empty()) {
1005  SaslAuthProvider* sap = NULL;
1006  external_auth_provider_.reset(sap = new SaslAuthProvider(false));
1007  if (!kerberos_external_principal.empty()) {
1008  RETURN_IF_ERROR(sap->InitKerberos(kerberos_external_principal,
1009  FLAGS_keytab_file));
1010  LOG(INFO) << "External communication is authenticated with Kerberos";
1011  }
1012  if (use_ldap) {
1013  sap->InitLdap();
1014  LOG(INFO) << "External communication is authenticated with LDAP";
1015  }
1016  } else {
1017  external_auth_provider_.reset(new NoAuthProvider());
1018  LOG(INFO) << "External communication is not authenticated";
1019  }
1020  RETURN_IF_ERROR(external_auth_provider_->Start());
1021 
1022  return Status::OK;
1023 }
1024 
1025 AuthProvider* AuthManager::GetExternalAuthProvider() {
1026  DCHECK(external_auth_provider_.get() != NULL);
1027  return external_auth_provider_.get();
1028 }
1029 
1030 AuthProvider* AuthManager::GetInternalAuthProvider() {
1031  DCHECK(internal_auth_provider_.get() != NULL);
1032  return internal_auth_provider_.get();
1033 }
1034 
1035 }
#define VLOG_CONNECTION_IS_ON
Definition: logging.h:62
Status InitAuth(const string &appname)
string path("/usr/lib/sasl2:/usr/lib64/sasl2:/usr/local/lib/sasl2:/usr/lib/x86_64-linux-gnu/sasl2")
TODO: Consider allowing fragment IDs as category parameters.
Definition: thread.h:45
void Set(const T &val)
Definition: promise.h:38
static vector< sasl_callback_t > KERB_INT_CALLBACKS
DEFINE_string(sasl_path,"/usr/lib/sasl2:/usr/lib64/sasl2:/usr/local/lib/sasl2:""/usr/lib/x86_64-linux-gnu/sasl2","Colon separated list of paths to look for SASL ""security library plugins.")
static sasl_callback_t GENERAL_CALLBACKS[5]
static const string PLAIN_MECHANISM
bool RunShellProcess(const std::string &cmd, std::string *msg)
#define RETURN_IF_ERROR(stmt)
some generally useful macros
Definition: status.h:242
DECLARE_string(keytab_file)
Status InitKerberos(const std::string &principal, const std::string &keytab_path)
static const string HOSTNAME_PATTERN
static void SaslInit(const sasl_callback_t *callbacks, const std::string &appname)
Definition: TSasl.h:176
static vector< sasl_callback_t > KERB_EXT_CALLBACKS
Status CheckReplayCacheDirPermissions()
static void ImpalaAuxpropLookup(void *glob_context, sasl_server_params_t *sparams, unsigned int flags, const char *user, unsigned ulen)
void SleepForMs(const int64_t duration_ms)
Sleeps the current thread for at least duration_ms milliseconds.
Definition: time.cc:21
static const string LDAPS_URI_PREFIX
static int SaslAuthorizeExternal(sasl_conn_t *conn, void *context, const char *requested_user, unsigned rlen, const char *auth_identity, unsigned alen, const char *def_realm, unsigned urlen, struct propctx *propctx)
static const string IMPALA_AUXPROP_PLUGIN
#define VLOG_QUERY
Definition: logging.h:57
static sasl_auxprop_plug_t impala_auxprop_plugin
static int SaslVerifyFile(void *context, const char *file, sasl_verify_type_t type)
string GetStrErrMsg()
Definition: error-util.cc:30
static const string KERBEROS_MECHANISM
DEFINE_int32(kerberos_reinit_interval, 60,"Interval, in minutes, between kerberos ticket renewals. Each renewal will request ""a ticket with a lifetime that is at least 2x the renewal interval.")
void addServerDefinition(const std::string &mechanism, const std::string &protocol, const std::string &serverName, const std::string &realm, unsigned int flags, std::map< std::string, std::string > props, std::vector< struct sasl_callback > callbacks)
static int SaslLogCallback(void *context, int level, const char *message)
Status GetHostname(string *hostname)
Definition: network-util.cc:40
DEFINE_bool(enable_ldap_auth, false,"If true, use LDAP authentication for client connections")
static Status EnvAppend(const string &attr, const string &thing, const string &thingval)
int SaslLdapCheckPass(sasl_conn_t *conn, void *context, const char *user, const char *pass, unsigned passlen, struct propctx *propctx)
static const string LDAP_URI_PREFIX
static int SaslGetPath(void *context, const char **path)
static vector< sasl_callback_t > LDAP_EXT_CALLBACKS
int ImpalaAuxpropInit(const sasl_utils_t *utils, int max_version, int *out_version, sasl_auxprop_plug_t **plug, const char *plugname)
#define VLOG_RPC
Definition: logging.h:56
static int SaslAuthorizeInternal(sasl_conn_t *conn, void *context, const char *requested_user, unsigned rlen, const char *auth_identity, unsigned alen, const char *def_realm, unsigned urlen, struct propctx *propctx)
#define VLOG_ROW_IS_ON
Definition: logging.h:66
static void SaslInit(sasl_callback_t *callbacks)
Definition: TSasl.h:130
static int SaslGetOption(void *context, const char *plugin_name, const char *option, const char **result, unsigned *len)
Definition: sasl-test.cc:9