MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
isasl.c
1 #include "config.h"
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <assert.h>
7 #include <ctype.h>
8 #include <stdint.h>
9 #include <pthread.h>
10 #include <stdbool.h>
11 #include <sys/stat.h>
12 #include <errno.h>
13 
14 #include "hash.h"
15 #include "isasl.h"
16 #include "memcached.h"
17 
18 static struct stat prev_stat = { 0 };
19 
20 static pthread_mutex_t uhash_lock = PTHREAD_MUTEX_INITIALIZER;
21 static pthread_mutex_t sasl_db_thread_lock = PTHREAD_MUTEX_INITIALIZER;
22 static bool run_sasl_db_thread;
23 static pthread_t sasl_db_thread_tid;
24 
25 static user_db_entry_t **user_ht;
26 static const int n_uht_buckets = 12289;
27 
28 static void kill_whitey(char *s) {
29  for(int i = strlen(s) - 1; i > 0 && isspace(s[i]); i--) {
30  s[i] = '\0';
31  }
32 }
33 
34 static int u_hash_key(const char *u)
35 {
36  uint32_t h = hash(u, strlen(u), 0) % n_uht_buckets;
37  assert(h < n_uht_buckets);
38  return h;
39 }
40 
41 static char *find_pw(const char *u, char **cfg)
42 {
43  assert(u);
44  assert(user_ht);
45 
46  int h = u_hash_key(u);
47 
48  user_db_entry_t *e = user_ht[h];
49  while (e && strcmp(e->username, u) != 0) {
50  e = e->next;
51  }
52 
53  if (e != NULL) {
54  *cfg = e->config;
55  return e->password;
56  } else {
57  return NULL;
58  }
59 }
60 
61 static void store_pw(user_db_entry_t **ht, const char *u, const char *p, const char *cfg)
62 {
63  assert(ht);
64  assert(u);
65  assert(p);
66  user_db_entry_t *e = calloc(1, sizeof(user_db_entry_t));
67  assert(e);
68  e->username = strdup(u);
69  assert(e->username);
70  e->password = strdup(p);
71  assert(e->password);
72  e->config = cfg ? strdup(cfg) : NULL;
73  assert(!cfg || e->config);
74 
75  int h = u_hash_key(u);
76 
77  e->next = ht[h];
78  ht[h] = e;
79 }
80 
81 static void free_user_ht(void)
82 {
83  if (user_ht) {
84  for (int i = 0; i < n_uht_buckets; i++) {
85  while (user_ht[i]) {
86  user_db_entry_t *e = user_ht[i];
87  user_db_entry_t *n = e->next;
88  free(e->username);
89  free(e->password);
90  free(e->config);
91  free(e);
92  user_ht[i] = n;
93  }
94  }
95  free(user_ht);
96  user_ht = NULL;
97  }
98 }
99 
100 static const char *get_isasl_filename(void)
101 {
102  return getenv("ISASL_PWFILE");
103 }
104 
105 static int load_user_db(void)
106 {
107  user_db_entry_t **new_ut = calloc(n_uht_buckets,
108  sizeof(user_db_entry_t*));
109 
110  if (!new_ut) {
111  return SASL_NOMEM;
112  }
113 
114  pthread_mutex_lock(&uhash_lock);
115  free_user_ht();
116  user_ht = new_ut;
117  pthread_mutex_unlock(&uhash_lock);
118 
119  const char *filename = get_isasl_filename();
120  if (!filename) {
121  return SASL_OK;
122  }
123  FILE *sfile = fopen(filename, "r");
124  if (!sfile) {
125  return SASL_OK;
126  }
127 
128  // File has lines that are newline terminated.
129  // File may have comment lines that must being with '#'.
130  // Lines should look like...
131  // <NAME><whitespace><PASSWORD><whitespace><CONFIG><optional_whitespace>
132  //
133  char up[128];
134  while (fgets(up, sizeof(up), sfile)) {
135  if (up[0] != '#') {
136  char *uname = up, *p = up, *cfg = NULL;
137  kill_whitey(up);
138  while (*p && !isspace(p[0])) {
139  p++;
140  }
141  // If p is pointing at a NUL, there's nothing after the username.
142  if (p[0] != '\0') {
143  p[0] = '\0';
144  p++;
145  }
146  // p now points to the first character after the (now)
147  // null-terminated username.
148  while (*p && isspace(*p)) {
149  p++;
150  }
151  // p now points to the first non-whitespace character
152  // after the above
153  cfg = p;
154  if (cfg[0] != '\0') {
155  // move cfg past the password
156  while (*cfg && !isspace(cfg[0])) {
157  cfg++;
158  }
159  if (cfg[0] != '\0') {
160  cfg[0] = '\0';
161  cfg++;
162  // Skip whitespace
163  while (*cfg && isspace(cfg[0])) {
164  cfg++;
165  }
166  }
167  }
168  store_pw(new_ut, uname, p, cfg);
169  }
170  }
171 
172  fclose(sfile);
173 
174  if (settings.verbose) {
175  settings.extensions.logger->log(EXTENSION_LOG_INFO, NULL,
176  "Loaded isasl db from %s\n",
177  filename);
178  }
179 
180  return SASL_OK;
181 }
182 
183 void sasl_dispose(sasl_conn_t **pconn)
184 {
185  free((*pconn)->username);
186  free((*pconn)->config);
187  free(*pconn);
188  *pconn = NULL;
189 }
190 
191 static bool isasl_is_fresh(void)
192 {
193  bool rv = false;
194  struct stat st;
195  const char *filename = get_isasl_filename();
196 
197  if (filename) {
198  if (stat(get_isasl_filename(), &st) < 0) {
199  perror(get_isasl_filename());
200  } else {
201  rv = prev_stat.st_mtime == st.st_mtime;
202  prev_stat = st;
203  }
204  }
205  return rv;
206 }
207 
208 static void* check_isasl_db_thread(void* arg)
209 {
210  uint32_t sleep_time = *(int*)arg;
211  if (settings.verbose > 1) {
212  settings.extensions.logger->log(EXTENSION_LOG_INFO, NULL,
213  "isasl checking DB every %ds",
214  sleep_time);
215  }
216 
217  run_sasl_db_thread = true;
218  bool run = true;
219  while (run) {
220  sleep(sleep_time);
221 
222  if (!isasl_is_fresh()) {
223  load_user_db();
224  }
225 
226  pthread_mutex_lock(&sasl_db_thread_lock);
227  if (!run_sasl_db_thread) {
228  run = false;
229  }
230  pthread_mutex_unlock(&sasl_db_thread_lock);
231  }
232 
233  return NULL;
234 }
235 
236 void shutdown_sasl(void)
237 {
238  pthread_mutex_lock(&sasl_db_thread_lock);
239  run_sasl_db_thread = false;
240  pthread_mutex_unlock(&sasl_db_thread_lock);
241  pthread_join(sasl_db_thread_tid, NULL);
242 }
243 
244 int sasl_server_init(const sasl_callback_t *callbacks,
245  const char *appname)
246 {
247  int rv = load_user_db();
248  if (rv == SASL_OK) {
249  static uint32_t sleep_time;
250  const char *sleep_time_str = getenv("ISASL_DB_CHECK_TIME");
251  pthread_attr_t attr;
252 
253  if (pthread_attr_init(&attr) != 0 ||
254  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0)
255  {
256  settings.extensions.logger->log(EXTENSION_LOG_WARNING, NULL,
257  "Failed to initialize pthread attributes: %s",
258  strerror(errno));
259  exit(EX_OSERR);
260  }
261 
262  if (! (sleep_time_str && safe_strtoul(sleep_time_str, &sleep_time))) {
263  // If we can't find a more frequent sleep time, set it to 60s.
264  sleep_time = 60;
265  }
266  if (get_isasl_filename() != NULL &&
267  pthread_create(&sasl_db_thread_tid, &attr, check_isasl_db_thread,
268  &sleep_time) != 0)
269  {
270  settings.extensions.logger->log(EXTENSION_LOG_WARNING, NULL,
271  "couldn't create isasl db update thread.");
272  exit(EX_OSERR);
273  }
274  }
275  return rv;
276 }
277 
278 int sasl_server_new(const char *service,
279  const char *serverFQDN,
280  const char *user_realm,
281  const char *iplocalport,
282  const char *ipremoteport,
283  const sasl_callback_t *callbacks,
284  unsigned flags,
285  sasl_conn_t **pconn)
286 {
287  *pconn = calloc(1, sizeof(sasl_conn_t));
288  return *pconn ? SASL_OK : SASL_NOMEM;
289 }
290 
291 int sasl_listmech(sasl_conn_t *conn,
292  const char *user,
293  const char *prefix,
294  const char *sep,
295  const char *suffix,
296  const char **result,
297  unsigned *plen,
298  int *pcount)
299 {
300  // We use this in a very specific way in the codebase. If that ever
301  // changes, detect it quickly.
302  assert(strcmp(prefix, "") == 0);
303  assert(strcmp(sep, " ") == 0);
304  assert(strcmp(suffix, "") == 0);
305 
306  *result = "PLAIN";
307  *plen = strlen(*result);
308  return SASL_OK;
309 }
310 
311 static bool check_up(const char *username, const char *password, char **cfg)
312 {
313  pthread_mutex_lock(&uhash_lock);
314  char *pw = find_pw(username, cfg);
315  bool rv = pw && (strcmp(password, pw) == 0);
316  pthread_mutex_unlock(&uhash_lock);
317  return rv;
318 }
319 
320 int sasl_server_start(sasl_conn_t *conn,
321  const char *mech,
322  const char *clientin,
323  unsigned clientinlen,
324  const char **serverout,
325  unsigned *serveroutlen)
326 {
327  int rv = SASL_FAIL;
328  *serverout = "";
329  *serveroutlen = 0;
330 
331  if(strcmp(mech, "PLAIN") == 0) {
332  // The clientin string looks like "[authzid]\0username\0password"
333  while (clientinlen > 0 && clientin[0] != '\0') {
334  // Skip authzid
335  clientin++;
336  clientinlen--;
337  }
338  if (clientinlen > 2 && clientinlen < 128 && clientin[0] == '\0') {
339  const char *username = clientin + 1;
340  char password[256];
341  int pwlen = clientinlen - 2 - strlen(username);
342  assert(pwlen >= 0);
343  if (pwlen < 256) {
344  char *cfg = NULL;
345  password[pwlen] = '\0';
346  memcpy(password, clientin + 2 + strlen(username), pwlen);
347 
348  if (check_up(username, password, &cfg)) {
349  if (conn->username) {
350  free(conn->username);
351  conn->username = NULL;
352  }
353  if (conn->config) {
354  free(conn->config);
355  conn->config = NULL;
356  }
357  conn->username = strdup(username);
358  assert(conn->username);
359  conn->config = strdup(cfg);
360  assert(conn->config);
361  rv = SASL_OK;
362  }
363  }
364  }
365  }
366 
367  return rv;
368 }
369 
370 int sasl_server_step(sasl_conn_t *conn,
371  const char *clientin,
372  unsigned clientinlen,
373  const char **serverout,
374  unsigned *serveroutlen)
375 {
376  // This is only useful when the above returns SASL_CONTINUE. In this
377  // implementation, only PLAIN is supported, so it never will.
378  return SASL_FAIL;
379 }
380 
381 int sasl_getprop(sasl_conn_t *conn, int propnum,
382  const void **pvalue)
383 {
384  switch (propnum) {
385  case SASL_USERNAME:
386  *pvalue = conn->username;
387  break;
388  case ISASL_CONFIG:
389  *pvalue = conn->config;
390  break;
391  default:
392  return SASL_BADPARAM;
393  }
394 
395  return SASL_OK;
396 }