├── .gitignore ├── MIT-LICENSE.txt ├── README.md ├── SECURITY.md ├── authenticate_pam.cc ├── binding.gyp ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 Code Charm Ltd 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-authenticate-pam 2 | ===================== 3 | 4 | Asynchronous PAM authentication for NodeJS 5 | 6 | *You will most likely need to run it as root in most common environments!* 7 | **Running as non-root on my system (openSUSE 12.1) made a segfault happen somewhere in `libpam`! - but seems ok on on openSUSE Leap 42.2** 8 | 9 | It tries to superseed the previous and outdated node-pam extension with the following improvements: 10 | * Allows to provide own service name, for example common-auth or any custom service name defined in `/etc/pam.d` 11 | * Allows to provide PAM_RHOST via 'remoteHost' option. It is used to provide remote network authentication that will skip any local only authentication methods like for example fingerprint reading. 12 | * Already mentioned utilization of libuv and node-gyp 13 | * Proper type checking in C++ code, it throws exception if bad types are given 14 | * In case of error it passes the error string containing both pam function and `pam_strerror()` results 15 | 16 | Example 17 | ========= 18 | 19 | Simple usage 20 | ------------ 21 | Default service_name for `pam_start(2)` is 'login'. 22 | 23 | ```js 24 | var pam = require('authenticate-pam'); 25 | pam.authenticate('myusername', 'mysecretpassword', function(err) { 26 | if(err) { 27 | console.log(err); 28 | } 29 | else { 30 | console.log("Authenticated!"); 31 | } 32 | }); 33 | ``` 34 | 35 | Usage with options: 36 | ------------------- 37 | Proper apps should provide their own service name. Sample services are located in `/etc/pam.d`. 38 | As an example lookup a service name file for `sshd`. 39 | To do proper network authentication you should also provide `remoteHost` key to the options argument. It will be passed to pam as `PAM_RHOST` (`pam_set_item(2)`) 40 | 41 | ```js 42 | var pam = require('authenticate-pam'); 43 | pam.authenticate('rush', 'mysecretpassword', function(err) { 44 | if(err) { 45 | console.log(err); 46 | } 47 | else { 48 | console.log("Authenticated!"); 49 | } 50 | }, {serviceName: 'myapp', remoteHost: 'localhost'}); 51 | ``` 52 | 53 | Install 54 | ------------------- 55 | First you need install the development version of PAM libraries for your distro. 56 | 57 | **Centos and RHEL**: 58 | `yum install pam-devel` 59 | 60 | **Debian/Ubuntu**: 61 | `apt-get install libpam0g-dev` 62 | 63 | **debian6/maverick/natty**: 64 | `apt-get install libreadline5-dev` 65 | 66 | **oneiric (and any newer, eg. Debian 7 or Ubuntu 12.04)**: 67 | `apt-get install libreadline-gplv2-dev` 68 | 69 | Then you can install the module: 70 | `npm install authenticate-pam` 71 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only latest version is supported for security updates. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Create a Github ticket for tracking purposes and simultaneously send confidential information to rush@rushbase.net 10 | -------------------------------------------------------------------------------- /authenticate_pam.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2014 Damian Kaczmarek 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | using namespace v8; 33 | 34 | struct auth_context { 35 | auth_context() { 36 | remoteHost[0] = '\0'; 37 | serviceName[0] = '\0'; 38 | username[0] = '\0'; 39 | password[0] = '\0'; 40 | } 41 | Nan::Persistent callback; 42 | char serviceName[128]; 43 | char username[128]; 44 | char password[128]; 45 | char remoteHost[128]; 46 | int error; 47 | std::string errorString; 48 | }; 49 | 50 | static int function_conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { 51 | auth_context* data = static_cast(appdata_ptr); 52 | struct pam_response* reply = (struct pam_response*)malloc(sizeof(struct pam_response)); 53 | reply->resp = strdup((char*)data->password); 54 | reply->resp_retcode = 0; 55 | *resp = reply; 56 | return PAM_SUCCESS; 57 | } 58 | 59 | 60 | #define HANDLE_PAM_ERROR(header) if(retval != PAM_SUCCESS) { \ 61 | data->errorString = header[0]?(std::string(header) + std::string(": ")):std::string("") + std::string(pam_strerror(local_auth_handle, retval)); \ 62 | data->error = retval; \ 63 | pam_end(local_auth_handle, retval); \ 64 | return; \ 65 | } 66 | 67 | 68 | // actual authentication function 69 | void doing_auth_thread(uv_work_t* req) { 70 | auth_context* data = static_cast(req->data); 71 | 72 | const struct pam_conv local_conversation = { function_conversation, (void*)data }; 73 | pam_handle_t* local_auth_handle = NULL; // this gets set by pam_start 74 | 75 | int retval = pam_start(strlen(data->serviceName)?data->serviceName:"login", 76 | data->username, &local_conversation, &local_auth_handle); 77 | HANDLE_PAM_ERROR("pam_start") 78 | 79 | if(strlen(data->remoteHost)) { 80 | retval = pam_set_item(local_auth_handle, PAM_RHOST, data->remoteHost); 81 | HANDLE_PAM_ERROR("pam_set_item") 82 | } 83 | retval = pam_authenticate(local_auth_handle, 0); 84 | HANDLE_PAM_ERROR("") 85 | 86 | retval = pam_acct_mgmt(local_auth_handle, 0); 87 | HANDLE_PAM_ERROR("") 88 | 89 | retval = pam_end(local_auth_handle, retval); 90 | if(retval != PAM_SUCCESS) { 91 | data->errorString = "pam_end: " + std::string(pam_strerror(local_auth_handle, retval)); 92 | data->error = retval; 93 | return; 94 | } 95 | data->error = 0; 96 | return; 97 | } 98 | 99 | void after_doing_auth(uv_work_t* req, int status) { 100 | Nan::HandleScope scope; 101 | 102 | auth_context* m = static_cast(req->data); 103 | Nan::TryCatch try_catch; 104 | 105 | Local args[1] = {Nan::Undefined()}; 106 | if(m->error) { 107 | args[0] = Nan::New(m->errorString.c_str()).ToLocalChecked(); 108 | } 109 | 110 | Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(m->callback), 1, args); 111 | 112 | m->callback.Reset(); 113 | 114 | delete m; 115 | delete req; 116 | 117 | if(try_catch.HasCaught()) 118 | Nan::FatalException(try_catch); 119 | } 120 | 121 | NAN_METHOD(Authenticate) { 122 | if(info.Length() < 3) { 123 | Nan::ThrowTypeError("Wrong number of arguments"); 124 | return; 125 | } 126 | 127 | Local usernameVal(info[0]); 128 | Local passwordVal(info[1]); 129 | if(!usernameVal->IsString() || !passwordVal->IsString()) { 130 | Nan::ThrowTypeError("Argument 0 and 1 should be a String"); 131 | return; 132 | } 133 | Local callbackVal(info[2]); 134 | if(!callbackVal->IsFunction()) { 135 | Nan::ThrowTypeError("Argument 2 should be a Function"); 136 | return; 137 | } 138 | 139 | Local callback = Local::Cast(callbackVal); 140 | 141 | Local username = Local::Cast(usernameVal); 142 | Local password = Local::Cast(passwordVal); 143 | 144 | 145 | uv_work_t* req = new uv_work_t; 146 | struct auth_context* m = new auth_context; 147 | 148 | if(info.Length() == 4 && !info[3]->IsUndefined()) { 149 | Local options = Local::Cast(info[3]); 150 | Local res = Nan::Get(options, Nan::New("serviceName").ToLocalChecked()).ToLocalChecked(); 151 | if(! res->IsUndefined()) { 152 | Local serviceName = Local::Cast(res); 153 | #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >= 7 154 | serviceName->WriteUtf8(v8::Isolate::GetCurrent(),m->serviceName, sizeof(m->serviceName) - 1); 155 | #else 156 | serviceName->WriteUtf8(m->serviceName, sizeof(m->serviceName) - 1); 157 | #endif 158 | } 159 | res = Nan::Get(options, Nan::New("remoteHost").ToLocalChecked()).ToLocalChecked(); 160 | if(! res->IsUndefined()) { 161 | Local remoteHost = Local::Cast(res); 162 | #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >= 7 163 | remoteHost->WriteUtf8(v8::Isolate::GetCurrent(),m->remoteHost, sizeof(m->remoteHost) - 1); 164 | #else 165 | remoteHost->WriteUtf8(m->remoteHost, sizeof(m->remoteHost) - 1); 166 | #endif 167 | } 168 | } 169 | m->callback.Reset(callback); 170 | 171 | #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >= 7 172 | username->WriteUtf8(v8::Isolate::GetCurrent(),m->username, sizeof(m->username) - 1); 173 | password->WriteUtf8(v8::Isolate::GetCurrent(),m->password, sizeof(m->password) - 1); 174 | #else 175 | username->WriteUtf8(m->username, sizeof(m->username) - 1); 176 | password->WriteUtf8(m->password, sizeof(m->password) - 1); 177 | #endif 178 | 179 | req->data = m; 180 | 181 | uv_queue_work(uv_default_loop(), req, doing_auth_thread, after_doing_auth); 182 | 183 | info.GetReturnValue().Set(Nan::Undefined()); 184 | } 185 | 186 | void init(v8::Local exports) { 187 | Local tpl = Nan::New(Authenticate); 188 | Nan::Set(exports,Nan::New("authenticate").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); 189 | } 190 | 191 | NODE_MODULE(authenticate_pam, init); 192 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'authenticate_pam', 5 | 'sources': [ 'authenticate_pam.cc' ], 6 | 'libraries': [ '-lpam' ], 7 | 'include_dirs': [ 8 | "