Przeglądaj źródła

Merge branch 'plugins' into dev

Feng Lee 10 lat temu
rodzic
commit
8bd867325c

+ 0 - 0
plugins/emqttd_plugin_mysql/.placehodler


+ 32 - 0
plugins/emqttd_plugin_mysql/Makefile

@@ -0,0 +1,32 @@
+REBAR?=./rebar
+
+
+all: build
+
+
+clean:
+	$(REBAR) clean
+	rm -rf logs
+	rm -rf .eunit
+	rm -f test/*.beam
+
+
+distclean: clean
+	git clean -fxd
+
+build: depends
+	$(REBAR) compile
+
+
+eunit:
+	$(REBAR) eunit skip_deps=true
+
+
+check: build eunit
+
+
+%.beam: %.erl
+	erlc -o test/ $<
+
+
+.PHONY: all clean distclean depends build eunit check

+ 49 - 0
plugins/emqttd_plugin_mysql/README.md

@@ -0,0 +1,49 @@
+##  Overview
+
+Authentication with user table of MySQL database.
+
+## etc/plugin.config
+
+```erlang
+[
+ {emysql, [
+    {pool,      4},
+    {host,      "localhost"},
+    {port,      3306},
+    {username,  ""}, 
+    {password,  ""},
+    {database,  "mqtt"},
+    {encoding,  utf8}
+ ]},
+ {emqttd_auth_mysql, [
+    {user_table, mqtt_users},
+    %% plain password only
+    {password_hash, plain},
+    {field_mapper, [
+        {username, username},
+        {password, password}
+    ]}
+ ]}
+].
+```
+
+## Users Table(Demo)
+
+Notice: This is a demo table. You could authenticate with any user tables.
+
+```
+CREATE TABLE `mqtt_users` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `username` varchar(60) DEFAULT NULL,
+  `password` varchar(60) DEFAULT NULL,
+  `salt` varchar(20) DEFAULT NULL,
+  `created` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `mqtt_users_username` (`username`)
+) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
+```
+
+## Load Plugin
+
+Merge the'etc/plugin.config' to emqttd/etc/plugins.config, and the plugin will be loaded by the  broker.
+

+ 151 - 0
plugins/emqttd_plugin_mysql/c_src/base64.c

@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by the Kungliga Tekniska
+ *      Hgskolan and its contributors.
+ *
+ * 4. Neither the name of the Institute nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+/*RCSID("$Id: base64.c,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $");*/
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include "base64.h"
+
+static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static int pos(char c)
+{
+  char *p;
+  for(p = base64; *p; p++)
+    if(*p == c)
+      return p - base64;
+  return -1;
+}
+
+int base64_encode(const void *data, int size, char **str)
+{
+  char *s, *p;
+  int i;
+  int c;
+  unsigned char *q;
+
+  p = s = (char*)malloc(size*4/3+4);
+  if (p == NULL)
+      return -1;
+  q = (unsigned char*)data;
+  i=0;
+  for(i = 0; i < size;){
+    c=q[i++];
+    c*=256;
+    if(i < size)
+      c+=q[i];
+    i++;
+    c*=256;
+    if(i < size)
+      c+=q[i];
+    i++;
+    p[0]=base64[(c&0x00fc0000) >> 18];
+    p[1]=base64[(c&0x0003f000) >> 12];
+    p[2]=base64[(c&0x00000fc0) >> 6];
+    p[3]=base64[(c&0x0000003f) >> 0];
+    if(i > size)
+      p[3]='=';
+    if(i > size+1)
+      p[2]='=';
+    p+=4;
+  }
+  *p=0;
+  *str = s;
+  return strlen(s);
+}
+
+int base64_decode(const char *str, void *data)
+{
+  const char *p;
+  unsigned char *q;
+  int c;
+  int x;
+  int done = 0;
+  q=(unsigned char*)data;
+  for(p=str; *p && !done; p+=4){
+    x = pos(p[0]);
+    if(x >= 0)
+      c = x;
+    else{
+      done = 3;
+      break;
+    }
+    c*=64;
+
+    x = pos(p[1]);
+    if(x >= 0)
+      c += x;
+    else
+      return -1;
+    c*=64;
+
+    if(p[2] == '=')
+      done++;
+    else{
+      x = pos(p[2]);
+      if(x >= 0)
+	c += x;
+      else
+	return -1;
+    }
+    c*=64;
+
+    if(p[3] == '=')
+      done++;
+    else{
+      if(done)
+	return -1;
+      x = pos(p[3]);
+      if(x >= 0)
+	c += x;
+      else
+	return -1;
+    }
+    if(done < 3)
+      *q++=(c&0x00ff0000)>>16;
+
+    if(done < 2)
+      *q++=(c&0x0000ff00)>>8;
+    if(done < 1)
+      *q++=(c&0x000000ff)>>0;
+  }
+  return q - (unsigned char*)data;
+}

+ 47 - 0
plugins/emqttd_plugin_mysql/c_src/base64.h

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by the Kungliga Tekniska
+ *      Hgskolan and its contributors.
+ * 
+ * 4. Neither the name of the Institute nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* $Id: base64.h,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $ */
+
+#ifndef _BASE64_H_
+#define _BASE64_H_
+
+int base64_encode(const void *data, int size, char **str);
+int base64_decode(const char *str, void *data);
+
+#endif

+ 60 - 0
plugins/emqttd_plugin_mysql/c_src/emqttd_plugin_mysql_app.c

@@ -0,0 +1,60 @@
+// This file is part of Jiffy released under the MIT license.
+// See the LICENSE file for more information.
+
+#include "emqttd_plugin_mysql_app.h"
+
+static int
+load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
+{
+    emqttd_plugin_mysql_app_st* st = enif_alloc(sizeof(emqttd_plugin_mysql_app_st));
+    if(st == NULL) {
+        return 1;
+    }
+
+    st->atom_ok = make_atom(env, "ok");
+    st->atom_error = make_atom(env, "error");
+    st->atom_null = make_atom(env, "null");
+    st->atom_true = make_atom(env, "true");
+    st->atom_false = make_atom(env, "false");
+    st->atom_bignum = make_atom(env, "bignum");
+    st->atom_bignum_e = make_atom(env, "bignum_e");
+    st->atom_bigdbl = make_atom(env, "bigdbl");
+    st->atom_partial = make_atom(env, "partial");
+    st->atom_uescape = make_atom(env, "uescape");
+    st->atom_pretty = make_atom(env, "pretty");
+    st->atom_force_utf8 = make_atom(env, "force_utf8");
+
+    // Markers used in encoding
+    st->ref_object = make_atom(env, "$object_ref$");
+    st->ref_array = make_atom(env, "$array_ref$");
+
+    *priv = (void*) st;
+
+    return 0;
+}
+
+static int
+reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
+{
+    return 0;
+}
+
+static int
+upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info)
+{
+    return load(env, priv, info);
+}
+
+static void
+unload(ErlNifEnv* env, void* priv)
+{
+    enif_free(priv);
+    return;
+}
+
+static ErlNifFunc funcs[] =
+{
+    {"nif_pbkdf2_check", 2, pbkdf2_check}
+};
+
+ERL_NIF_INIT(emqttd_plugin_mysql_app, funcs, &load, &reload, &upgrade, &unload);

+ 44 - 0
plugins/emqttd_plugin_mysql/c_src/emqttd_plugin_mysql_app.h

@@ -0,0 +1,44 @@
+// This file is part of Jiffy released under the MIT license.
+// See the LICENSE file for more information.
+
+#ifndef EMQTTD_PLUGIN_MYSQL_APP_H
+#define EMQTTD_PLUGIN_MYSQL_APP_H
+
+#include "erl_nif.h"
+
+typedef struct {
+    ERL_NIF_TERM    atom_ok;
+    ERL_NIF_TERM    atom_error;
+    ERL_NIF_TERM    atom_null;
+    ERL_NIF_TERM    atom_true;
+    ERL_NIF_TERM    atom_false;
+    ERL_NIF_TERM    atom_bignum;
+    ERL_NIF_TERM    atom_bignum_e;
+    ERL_NIF_TERM    atom_bigdbl;
+    ERL_NIF_TERM    atom_partial;
+    ERL_NIF_TERM    atom_uescape;
+    ERL_NIF_TERM    atom_pretty;
+    ERL_NIF_TERM    atom_force_utf8;
+
+    ERL_NIF_TERM    ref_object;
+    ERL_NIF_TERM    ref_array;
+} emqttd_plugin_mysql_app_st;
+
+ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name);
+ERL_NIF_TERM make_ok(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, ERL_NIF_TERM data);
+ERL_NIF_TERM make_error(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, const char* error);
+
+ERL_NIF_TERM pbkdf2_check(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+int int_from_hex(const unsigned char* p);
+int int_to_hex(int val, char* p);
+int utf8_len(int c);
+int utf8_esc_len(int c);
+int utf8_validate(unsigned char* data, size_t size);
+int utf8_to_unicode(unsigned char* buf, size_t size);
+int unicode_to_utf8(int c, unsigned char* buf);
+int unicode_from_pair(int hi, int lo);
+int unicode_uescape(int c, char* buf);
+int double_to_shortest(char *buf, size_t size, size_t* len, double val);
+
+#endif // Included EMQTTD_PLUGIN_MYSQL_APP_H

+ 278 - 0
plugins/emqttd_plugin_mysql/c_src/pbkdf2_check.c

@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2013 Jan-Piet Mens <jpmens()gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of mosquitto nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include "base64.h"
+#include "erl_nif.h"
+#include "emqttd_plugin_mysql_app.h"
+
+#define KEY_LENGTH      24
+#define SEPARATOR       "$"
+#define SEPARATOR1       "_"
+#define TRUE	(1)
+#define FALSE	(0)
+
+/*
+ * Split PBKDF2$... string into their components. The caller must free()
+ * the strings.
+ */
+
+static int detoken(char *pbkstr, char **sha, int *iter, char **salt, char **key)
+{
+    char *p, *s, *save;
+    int rc = 1;
+
+    save = s = strdup(pbkstr);
+
+    if ((p = strsep(&s, SEPARATOR1)) == NULL)
+        goto out;
+    if (strcmp(p, "pbkdf2") != 0)
+        goto out;
+
+    if ((p = strsep(&s, SEPARATOR)) == NULL)
+        goto out;
+    *sha = strdup(p);
+
+    if ((p = strsep(&s, SEPARATOR)) == NULL)
+        goto out;
+    *iter = atoi(p);
+
+    if ((p = strsep(&s, SEPARATOR)) == NULL)
+        goto out;
+    *salt = strdup(p);
+
+    if ((p = strsep(&s, SEPARATOR)) == NULL)
+        goto out;
+    *key = strdup(p);
+
+    rc = 0;
+
+out:
+    free(save);
+    return rc;
+}
+
+    ERL_NIF_TERM
+pbkdf2_check(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+    ERL_NIF_TERM ret;
+    ErlNifBinary binps, binhash;
+    emqttd_plugin_mysql_app_st* st = enif_alloc(sizeof(emqttd_plugin_mysql_app_st));
+    if(st == NULL) {
+        return make_atom(env, "alloc_error");
+    }
+
+    st->atom_ok = make_atom(env, "ok");
+    st->atom_error = make_atom(env, "error");
+    st->atom_null = make_atom(env, "null");
+    st->atom_true = make_atom(env, "true");
+    st->atom_false = make_atom(env, "false");
+    st->atom_bignum = make_atom(env, "bignum");
+    st->atom_bignum_e = make_atom(env, "bignum_e");
+    st->atom_bigdbl = make_atom(env, "bigdbl");
+    st->atom_partial = make_atom(env, "partial");
+    st->atom_uescape = make_atom(env, "uescape");
+    st->atom_pretty = make_atom(env, "pretty");
+    st->atom_force_utf8 = make_atom(env, "force_utf8");
+
+    // Markers used in encoding
+    st->ref_object = make_atom(env, "$object_ref$");
+    st->ref_array = make_atom(env, "$array_ref$");
+
+    if(argc != 2) {
+        return make_error(st, env, "Bad args");
+    } else if(!enif_inspect_binary(env, argv[0], &binps)|!enif_inspect_binary(env, argv[1], &binhash)) {
+        return make_error(st, env, "Bad args password or username inspect error");
+    }
+
+    char* password = (char*)binps.data;
+    char* hash = (char*)binhash.data;
+    static char *sha, *salt, *h_pw;
+    int iterations, saltlen, blen;
+    char *b64, *keybuf;
+    unsigned char *out;
+    int match = FALSE;
+    const EVP_MD *evpmd;
+    int keylen, rc;
+
+    if (detoken(hash, &sha, &iterations, &salt, &h_pw) != 0)
+        return match;
+
+    /* Determine key length by decoding base64 */
+    if ((keybuf = malloc(strlen(h_pw) + 1)) == NULL) {
+        return make_error(st, env, "internal_error: Out Of memory");
+    }
+    keylen = base64_decode(h_pw, keybuf);
+    if (keylen < 1) {
+        free(keybuf);
+        return make_atom(env, "false");
+    }
+    free(keybuf);
+
+    if ((out = malloc(keylen)) == NULL) {
+        return make_error(st, env, "Cannot allocate out; out of memory\n");
+    }
+
+#ifdef PWDEBUG
+    fprintf(stderr, "sha        =[%s]\n", sha);
+    fprintf(stderr, "iterations =%d\n", iterations);
+    fprintf(stderr, "salt       =[%s]\n", salt);
+    fprintf(stderr, "h_pw       =[%s]\n", h_pw);
+    fprintf(stderr, "kenlen     =[%d]\n", keylen);
+#endif
+
+    saltlen = strlen((char *)salt);
+
+    evpmd = EVP_sha256();
+    if (strcmp(sha, "sha1") == 0) {
+        evpmd = EVP_sha1();
+    } else if (strcmp(sha, "sha512") == 0) {
+        evpmd = EVP_sha512();
+    }
+
+    rc = PKCS5_PBKDF2_HMAC(password, strlen(password),
+            (unsigned char *)salt, saltlen,
+            iterations,
+            evpmd, keylen, out);
+    if (rc != 1) {
+        goto out;
+    }
+
+    blen = base64_encode(out, keylen, &b64);
+    if (blen > 0) {
+        int i, diff = 0, hlen = strlen(h_pw);
+#ifdef PWDEBUG
+        fprintf(stderr, "HMAC b64   =[%s]\n", b64);
+#endif
+
+        /* "manual" strcmp() to ensure constant time */
+        for (i = 0; (i < blen) && (i < hlen); i++) {
+            diff |= h_pw[i] ^ b64[i];
+        }
+
+        match = diff == 0;
+        if (hlen != blen)
+            match = 0;
+
+        free(b64);
+    }
+
+out:
+    free(sha);
+    free(salt);
+    free(h_pw);
+    free(out);
+
+    if(match == 0){
+        ret = make_atom(env, "false");
+    }else{
+        ret = make_atom(env, "true");
+    }
+    return ret;
+}
+
+int pbkdf2_check_native(char *password, char *hash)
+{
+    static char *sha, *salt, *h_pw;
+    int iterations, saltlen, blen;
+    char *b64;
+    unsigned char key[128];
+    int match = FALSE;
+    const EVP_MD *evpmd;
+
+    if (detoken(hash, &sha, &iterations, &salt, &h_pw) != 0)
+        return match;
+
+#ifdef PWDEBUG
+    fprintf(stderr, "sha        =[%s]\n", sha);
+    fprintf(stderr, "iterations =%d\n", iterations);
+    fprintf(stderr, "salt       =[%s]\n", salt);
+    fprintf(stderr, "h_pw       =[%s]\n", h_pw);
+#endif
+
+    saltlen = strlen((char *)salt);
+
+    evpmd = EVP_sha256();
+    if (strcmp(sha, "sha1") == 0) {
+        evpmd = EVP_sha1();
+    } else if (strcmp(sha, "sha512") == 0) {
+        evpmd = EVP_sha512();
+    }
+
+    PKCS5_PBKDF2_HMAC(password, strlen(password),
+            (unsigned char *)salt, saltlen,
+            iterations,
+            evpmd, KEY_LENGTH, key);
+
+    blen = base64_encode(key, KEY_LENGTH, &b64);
+    if (blen > 0) {
+        int i, diff = 0, hlen = strlen(h_pw);
+#ifdef PWDEBUG
+        fprintf(stderr, "HMAC b64   =[%s]\n", b64);
+#endif
+
+        /* "manual" strcmp() to ensure constant time */
+        for (i = 0; (i < blen) && (i < hlen); i++) {
+            diff |= h_pw[i] ^ b64[i];
+        }
+
+        match = diff == 0;
+        if (hlen != blen)
+            match = 0;
+
+        free(b64);
+    }
+
+    free(sha);
+    free(salt);
+    free(h_pw);
+
+    return match;
+}
+int main()
+{
+    // char password[] = "hello";
+    // char PB1[] = "PBKDF2$sha256$10000$eytf9sEo8EprP9P3$2eO6tROHiqI3bm+gg+vpmWooWMpz1zji";
+    char password[] = "supersecret";
+    //char PB1[] = "PBKDF2$sha256$10000$YEbSTt8FaMRDq/ib$Kt97+sMCYg00mqMOBAYinqZlnxX8HqHk";
+    char PB1[] = "pbkdf2_sha256$10000$YEbSTt8FaMRDq/ib$Kt97+sMCYg00mqMOBAYinqZlnxX8HqHk";
+    // char PB1[] = "PBKDF2$sha1$10000$XWfyPLeC9gsD6SbI$HOnjU4Ux7RpeBHdqYxpIGH1R5qCCtNA1";
+    // char PB1[] = "PBKDF2$sha512$10000$v/aaCgBZ+VZN5L8n$BpgjSTyb4weVxr9cA2mvQ+jaCyaAPeYe";
+    int match;
+
+    printf("Checking password [%s] for %s\n", password, PB1);
+
+    match = pbkdf2_check_native(password, PB1);
+    printf("match == %d\n", match);
+    return match;
+}

+ 26 - 0
plugins/emqttd_plugin_mysql/c_src/util.c

@@ -0,0 +1,26 @@
+// This file is part of Jiffy released under the MIT license.
+// See the LICENSE file for more information.
+
+#include "emqttd_plugin_mysql_app.h"
+
+ERL_NIF_TERM
+make_atom(ErlNifEnv* env, const char* name)
+{
+    ERL_NIF_TERM ret;
+    if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) {
+        return ret;
+    }
+    return enif_make_atom(env, name);
+}
+
+ERL_NIF_TERM
+make_ok(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, ERL_NIF_TERM value)
+{
+    return enif_make_tuple2(env, st->atom_ok, value);
+}
+
+ERL_NIF_TERM
+make_error(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, const char* error)
+{
+    return enif_make_tuple2(env, st->atom_error, make_atom(env, error));
+}

+ 23 - 0
plugins/emqttd_plugin_mysql/etc/plugin.config

@@ -0,0 +1,23 @@
+[
+  {emysql, [
+    {pool, 4},
+    {host, "localhost"},
+    {port, 3306},
+    {username, "root"},
+    {password, "root"},
+    {database, "emqtt"},
+    {encoding, utf8}
+  ]},
+  {emqttd_plugin_mysql, [
+    {users_table, auth_user},
+    {acls_table, auth_acl},
+    {field_mapper, [
+      {username, username},
+      {password, password, pbkdf2},
+      {user_super, is_super_user},
+      {acl_username, username},
+      {acl_rw, rw},
+      {acl_topic, topic}
+    ]}
+  ]}
+].

+ 23 - 0
plugins/emqttd_plugin_mysql/etc/plugins.config

@@ -0,0 +1,23 @@
+[
+  {emysql, [
+    {pool, 4},
+    {host, "59.188.253.198"},
+    {port, 3306},
+    {username, "root"},
+    {password, "lhroot."},
+    {database, "musicfield"},
+    {encoding, utf8}
+  ]},
+  {emqttd_plugin_mysql, [
+    {users_table, auth_user},
+    {acls_table, auth_acl},
+    {field_mapper, [
+      {username, username},
+      {password, password, pbkdf2},
+      {user_super, is_super_user},
+      {acl_username, username},
+      {acl_rw, rw},
+      {acl_topic, topic}
+    ]}
+  ]}
+].

+ 112 - 0
plugins/emqttd_plugin_mysql/include/emqttd.hrl

@@ -0,0 +1,112 @@
+%%------------------------------------------------------------------------------
+%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
+%% 
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%% 
+%% The above copyright notice and this permission notice shall be included in all
+%% copies or substantial portions of the Software.
+%% 
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%------------------------------------------------------------------------------
+%%% @doc
+%%% MQTT Broker Header.
+%%%
+%%% @end
+%%%-----------------------------------------------------------------------------
+
+%%------------------------------------------------------------------------------
+%% Banner
+%%------------------------------------------------------------------------------
+-define(COPYRIGHT, "Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>").
+
+-define(LICENSE_MESSAGE, "Licensed under MIT"). 
+
+-define(PROTOCOL_VERSION, "MQTT/3.1.1").
+
+-define(ERTS_MINIMUM, "6.0").
+
+%%------------------------------------------------------------------------------
+%% PubSub
+%%------------------------------------------------------------------------------
+-type pubsub() :: publish | subscribe.
+
+%%------------------------------------------------------------------------------
+%% MQTT Topic
+%%------------------------------------------------------------------------------
+-record(mqtt_topic, {
+    topic   :: binary(),
+    node    :: node()
+}).
+
+-type mqtt_topic() :: #mqtt_topic{}.
+
+%%------------------------------------------------------------------------------
+%% MQTT Subscriber
+%%------------------------------------------------------------------------------
+-record(mqtt_subscriber, {
+    topic    :: binary(),
+    qos = 0  :: 0 | 1 | 2,
+    pid      :: pid()
+}).
+
+-type mqtt_subscriber() :: #mqtt_subscriber{}.
+
+%%------------------------------------------------------------------------------
+%% P2P Queue Subscriber
+%%------------------------------------------------------------------------------
+-record(mqtt_queue, {
+    name     :: binary(),
+    subpid   :: pid(),
+    qos = 0  :: 0 | 1 | 2
+}).
+
+-type mqtt_queue() :: #mqtt_queue{}.
+
+%%------------------------------------------------------------------------------
+%% MQTT Client
+%%------------------------------------------------------------------------------
+-record(mqtt_client, {
+    clientid    :: binary(),
+    username    :: binary() | undefined,
+    ipaddr      :: inet:ip_address()
+}).
+
+-type mqtt_client() :: #mqtt_client{}.
+
+%%------------------------------------------------------------------------------
+%% MQTT Session
+%%------------------------------------------------------------------------------
+-record(mqtt_session, {
+    clientid,
+    session_pid,
+    subscriptions = [],
+    awaiting_ack,
+    awaiting_rel
+}).
+
+-type mqtt_session() :: #mqtt_session{}.
+
+%%------------------------------------------------------------------------------
+%% MQTT Plugin
+%%------------------------------------------------------------------------------
+-record(mqtt_plugin, {
+    name,
+    version,
+    attrs,
+    description
+}).
+
+-type mqtt_plugin() :: #mqtt_plugin{}.
+
+

+ 0 - 0
plugins/emqttd_plugin_mysql/priv/.placeholder


BIN
plugins/emqttd_plugin_mysql/rebar


+ 32 - 0
plugins/emqttd_plugin_mysql/rebar.config

@@ -0,0 +1,32 @@
+{port_specs, [
+    {"priv/emqttd_plugin_mysql_app.so", [
+        "c_src/*.c"
+    ]}
+]}.
+
+{port_env, [
+    {".*", "CXXFLAGS", "$CXXFLAGS -g -Wall -Werror -O3"},
+
+    {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin)",
+        "LDFLAGS", "$LDFLAGS -lstdc++ -lcrypto"},
+
+    %% OS X Leopard flags for 64-bit
+    {"darwin9.*-64$", "CXXFLAGS", "-m64"},
+    {"darwin9.*-64$", "LDFLAGS", "-arch x86_64"},
+
+    %% OS X Snow Leopard flags for 32-bit
+    {"darwin10.*-32$", "CXXFLAGS", "-m32"},
+    {"darwin10.*-32$", "LDFLAGS", "-arch i386"},
+
+    %% This will merge into basho/rebar/rebar.config eventually
+    {"win32", "CFLAGS", "/Wall /DWIN32 /D_WINDOWS /D_WIN32 /DWINDOWS"},
+    {"win32", "CXXFLAGS", "-g -Wall -O3"}
+]}.
+
+
+{eunit_opts, [
+    verbose,
+    {report, {
+        eunit_surefire, [{dir,"."}]
+    }}
+]}.

+ 70 - 0
plugins/emqttd_plugin_mysql/src/emqttd_acl_mysql.erl

@@ -0,0 +1,70 @@
+%%%-----------------------------------------------------------------------------
+%%% @Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>
+%%%
+%%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%%% of this software and associated documentation files (the "Software"), to deal
+%%% in the Software without restriction, including without limitation the rights
+%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%%% copies of the Software, and to permit persons to whom the Software is
+%%% furnished to do so, subject to the following conditions:
+%%%
+%%% The above copyright notice and this permission notice shall be included in all
+%%% copies or substantial portions of the Software.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%%% SOFTWARE.
+%%%-----------------------------------------------------------------------------
+%%% @doc
+%%% emqttd demo acl module.
+%%%
+%%% @end
+%%%-----------------------------------------------------------------------------
+-module(emqttd_acl_mysql).
+
+-include("emqttd.hrl").
+
+-behaviour(emqttd_acl_mod).
+
+%% ACL callbacks
+-export([init/1, check_acl/2, reload_acl/1, description/0]).
+-record(state, {user_table, acl_table, acl_username_field, acl_topic_field, acl_rw_field, user_name_field, user_super_field}).
+
+init(Opts) ->
+  Mapper = proplists:get_value(field_mapper, Opts),
+  State =
+    #state{
+      user_table = proplists:get_value(users_table, Opts, auth_user),
+      user_super_field = proplists:get_value(is_super, Mapper, is_superuser),
+      user_name_field = proplists:get_value(username, Mapper, username),
+      acl_table = proplists:get_value(acls_table, Opts, auth_acl),
+      acl_username_field = proplists:get_value(acl_username, Mapper, username),
+      acl_rw_field = proplists:get_value(acl_rw, Mapper, rw),
+      acl_topic_field = proplists:get_value(acl_topic, Mapper, topic)
+    },
+  {ok, State}.
+
+check_acl({#mqtt_client{username = Username}, PubSub, Topic}, #state{user_table = UserTab, acl_table = AclTab, user_name_field = UsernameField, user_super_field = SuperField, acl_topic_field = TopicField, acl_username_field = AclUserField, acl_rw_field = AclRwField}) ->
+  Flag = case PubSub of publish -> 2; subscribe -> 1; pubsub -> 2 end,
+  Where = {'and', {'>=', AclRwField, Flag}, {TopicField, Topic}},
+  Where1 = {'or', {AclUserField, Username}, {AclUserField, "*"}},
+  Where2 = {'and', Where, Where1},
+  case emysql:select(UserTab, {'and', {UsernameField, Username}, {SuperField, 1}}) of
+    {ok, []} ->
+      case emysql:select(UserTab, {UsernameField, Username}) of
+        {ok, []} -> ignore;
+        {ok, _} -> case emysql:select(AclTab, Where2) of
+                     {ok, []} -> deny;
+                     {ok, _Record} -> allow
+                   end
+      end;
+    {ok, _} -> allow
+  end.
+
+reload_acl(_State) -> ok.
+
+description() -> "ACL Module by Mysql".

+ 110 - 0
plugins/emqttd_plugin_mysql/src/emqttd_auth_mysql.erl

@@ -0,0 +1,110 @@
+%%%-----------------------------------------------------------------------------
+%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
+%%%
+%%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%%% of this software and associated documentation files (the "Software"), to deal
+%%% in the Software without restriction, including without limitation the rights
+%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%%% copies of the Software, and to permit persons to whom the Software is
+%%% furnished to do so, subject to the following conditions:
+%%%
+%%% The above copyright notice and this permission notice shall be included in all
+%%% copies or substantial portions of the Software.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%%% SOFTWARE.
+%%%-----------------------------------------------------------------------------
+%%% @doc
+%%% emqttd authentication by mysql 'user' table.
+%%%
+%%% @end
+%%%-----------------------------------------------------------------------------
+-module(emqttd_auth_mysql).
+
+-author("Feng Lee <feng@emqtt.io>").
+
+-include("emqttd.hrl").
+
+-behaviour(emqttd_auth_mod).
+
+-export([init/1, check/3, description/0]).
+
+-define(NOT_LOADED, not_loaded(?LINE)).
+
+-record(state, {user_table, name_field, pass_field, pass_hash}).
+
+init(Opts) ->
+  Mapper = proplists:get_value(field_mapper, Opts),
+  {ok, #state{user_table = proplists:get_value(user_table, Opts, auth_user),
+    name_field = proplists:get_value(username, Mapper),
+    pass_field = proplists:get_value(password, Mapper),
+    pass_hash = proplists:get_value(Opts, password_hash)}}.
+
+check(#mqtt_client{username = undefined}, _Password, _State) ->
+  {error, "Username undefined"};
+check(_Client, undefined, _State) ->
+  {error, "Password undefined"};
+check(#mqtt_client{username = Username}, Password,
+    #state{user_table = UserTab, pass_hash = Type,
+      name_field = NameField, pass_field = PassField}) ->
+  Where = {'and', {NameField, Username}, {PassField, hash(Type, Password)}},
+  if Type =:= pbkdf2 ->
+    case emysql:select(UserTab, [PassField], {NameField, Username}) of
+      {ok, []} -> {error, "User not exist"};
+      {ok, Records} ->
+        if length(Records) =:= 1 ->
+          case pbkdf2_check(Password, lists:nth(Records, 1)) of
+            true ->
+              {ok, []};
+            false ->
+              {error, "UserName or Password is invalid"};
+            ErrorInfo ->
+              {error, ErrorInfo}
+          end;
+          true ->
+            {error, "UserName is ambiguous"}
+        end
+    end;
+    true ->
+      case emysql:select(UserTab, Where) of
+        {ok, []} -> {error, "Username or Password "};
+        {ok, _Record} -> ok
+      end
+  end.
+
+description() -> "Authentication by MySQL".
+
+hash(plain, Password) ->
+  Password;
+
+hash(md5, Password) ->
+  hexstring(crypto:hash(md5, Password));
+
+hash(sha, Password) ->
+  hexstring(crypto:hash(sha, Password)).
+
+hexstring(<<X:128/big-unsigned-integer>>) ->
+  lists:flatten(io_lib:format("~32.16.0b", [X]));
+
+hexstring(<<X:160/big-unsigned-integer>>) ->
+  lists:flatten(io_lib:format("~40.16.0b", [X])).
+
+not_loaded(Line) ->
+  erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).
+
+pbkdf2_check(Password, Pbkstr) ->
+  case nif_pbkdf2_check(Password, Pbkstr) of
+    {error, _} = Error ->
+      throw(Error);
+    IOData ->
+      IOData
+  end.
+
+nif_pbkdf2_check(Password, Pbkstr) ->
+  ?NOT_LOADED.
+

+ 12 - 0
plugins/emqttd_plugin_mysql/src/emqttd_plugin_mysql.app.src

@@ -0,0 +1,12 @@
+{application, emqttd_plugin_mysql,
+ [
+  {description, "emqttd MySQL Authentication Plugin"},
+  {vsn, "1.0"},
+  {registered, []},
+  {applications, [
+                  kernel,
+                  stdlib
+                 ]},
+  {mod, {emqttd_plugin_mysql_app, []}},
+  {env, []}
+ ]}.

+ 80 - 0
plugins/emqttd_plugin_mysql/src/emqttd_plugin_mysql_app.erl

@@ -0,0 +1,80 @@
+%%%-----------------------------------------------------------------------------
+%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
+%%%
+%%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%%% of this software and associated documentation files (the "Software"), to deal
+%%% in the Software without restriction, including without limitation the rights
+%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%%% copies of the Software, and to permit persons to whom the Software is
+%%% furnished to do so, subject to the following conditions:
+%%%
+%%% The above copyright notice and this permission notice shall be included in all
+%%% copies or substantial portions of the Software.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%%% SOFTWARE.
+%%%-----------------------------------------------------------------------------
+%%% @doc
+%%% emqttd mysql authentication app.
+%%%
+%%% @end
+%%%-----------------------------------------------------------------------------
+-module(emqttd_plugin_mysql_app).
+-on_load(init/0).
+-behaviour(application).
+%% Application callbacks
+-export([start/2, prep_stop/1, stop/1, nif_pbkdf2_check/2]).
+
+-behaviour(supervisor).
+%% Supervisor callbacks
+-export([init/1]).
+-define(NOT_LOADED, not_loaded(?LINE)).
+
+
+%%%=============================================================================
+%%% Application callbacks
+%%%=============================================================================
+
+start(_StartType, _StartArgs) ->
+  Env = application:get_all_env(),
+  emqttd_access_control:register_mod(auth, emqttd_auth_mysql, Env),
+  emqttd_access_control:register_mod(acl, emqttd_acl_mysql, Env),
+  crypto:start(),
+  supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+prep_stop(State) ->
+  emqttd_access_control:unregister_mod(auth, emqttd_auth_mysql), State,
+  emqttd_access_control:unregister_mod(acl, emqttd_acl_mysql), State,
+  crypto:stop().
+
+stop(_State) ->
+  ok.
+
+init() ->
+  PrivDir = case code:priv_dir(?MODULE) of
+              {error, _} ->
+                EbinDir = filename:dirname(code:which(?MODULE)),
+                AppPath = filename:dirname(EbinDir),
+                filename:join(AppPath, "priv");
+              Path ->
+                Path
+            end,
+  erlang:load_nif(filename:join(PrivDir, "emqttd_plugin_mysql_app"), 0).
+
+%%%=============================================================================
+%%% Supervisor callbacks(Dummy)
+%%%=============================================================================
+
+init([]) ->
+  {ok, {{one_for_one, 5, 10}, []}}.
+
+not_loaded(Line) ->
+  erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).
+
+nif_pbkdf2_check(Password, Hash) ->
+  ?NOT_LOADED.

+ 6 - 0
plugins/emysql/src/emysql.erl

@@ -384,9 +384,15 @@ encode_where({like, Field, Value}) ->
 encode_where({'<', Field, Value}) ->	
 encode_where({'<', Field, Value}) ->	
 	atom_to_list(Field) ++ " < " ++ encode(Value);
 	atom_to_list(Field) ++ " < " ++ encode(Value);
 
 
+encode_where({'<=', Field, Value}) ->
+	atom_to_list(Field) ++ " <= " ++ encode(Value);
+
 encode_where({'>', Field, Value}) ->	
 encode_where({'>', Field, Value}) ->	
 	atom_to_list(Field) ++ " > " ++ encode(Value);
 	atom_to_list(Field) ++ " > " ++ encode(Value);
 
 
+encode_where({'>=', Field, Value}) ->
+	atom_to_list(Field) ++ " >= " ++ encode(Value);
+
 encode_where({'in', Field, Values}) ->	
 encode_where({'in', Field, Values}) ->	
 	InStr = string:join([encode(Value) || Value <- Values], ","),
 	InStr = string:join([encode(Value) || Value <- Values], ","),
 	atom_to_list(Field) ++ " in (" ++ InStr ++ ")";
 	atom_to_list(Field) ++ " in (" ++ InStr ++ ")";