2016-07-19 01:35:02 +00:00
"use strict" ;
2014-08-14 16:35:37 +00:00
var _ = require ( "lodash" ) ;
2016-06-12 01:44:28 +00:00
var pkg = require ( "../package.json" ) ;
2014-10-03 09:57:35 +00:00
var bcrypt = require ( "bcrypt-nodejs" ) ;
2014-08-14 16:35:37 +00:00
var Client = require ( "./client" ) ;
var ClientManager = require ( "./clientManager" ) ;
2014-09-26 22:12:53 +00:00
var express = require ( "express" ) ;
2014-08-14 16:35:37 +00:00
var fs = require ( "fs" ) ;
var io = require ( "socket.io" ) ;
2016-04-03 05:12:49 +00:00
var dns = require ( "dns" ) ;
2014-09-13 12:23:17 +00:00
var Helper = require ( "./helper" ) ;
2016-07-30 01:20:38 +00:00
var ldap = require ( "ldapjs" ) ;
2014-08-14 16:35:37 +00:00
2016-04-26 20:41:08 +00:00
var manager = null ;
2016-07-30 01:20:38 +00:00
var ldapclient = null ;
var authFunction = localAuth ;
2014-08-14 16:35:37 +00:00
2016-06-08 09:26:24 +00:00
module . exports = function ( ) {
2016-04-26 20:41:08 +00:00
manager = new ClientManager ( ) ;
2014-08-14 16:35:37 +00:00
2014-09-26 22:12:53 +00:00
var app = express ( )
2016-05-01 17:27:10 +00:00
. use ( allRequests )
2014-08-14 16:35:37 +00:00
. use ( index )
2014-10-03 23:33:44 +00:00
. use ( express . static ( "client" ) ) ;
2014-11-01 20:06:01 +00:00
2016-06-08 09:26:24 +00:00
var config = Helper . config ;
2014-09-26 23:26:21 +00:00
var server = null ;
2016-07-30 01:20:38 +00:00
if ( config . public && ( config . ldap || { } ) . enable ) {
2016-08-10 06:14:09 +00:00
log . warn ( "Server is public and set to use LDAP. Set to private mode if trying to use LDAP authentication." ) ;
2016-07-30 01:20:38 +00:00
}
2016-06-08 09:26:24 +00:00
if ( ! config . https . enable ) {
2014-09-26 23:26:21 +00:00
server = require ( "http" ) ;
2016-06-08 09:26:24 +00:00
server = server . createServer ( app ) . listen ( config . port , config . host ) ;
2014-09-26 23:26:21 +00:00
} else {
2016-03-09 12:04:05 +00:00
server = require ( "spdy" ) ;
2016-10-04 22:35:04 +00:00
const keyPath = Helper . expandHome ( config . https . key ) ;
const certPath = Helper . expandHome ( config . https . certificate ) ;
if ( ! config . https . key . length || ! fs . existsSync ( keyPath ) ) {
log . error ( "Path to SSL key is invalid. Stopping server..." ) ;
process . exit ( ) ;
}
if ( ! config . https . certificate . length || ! fs . existsSync ( certPath ) ) {
log . error ( "Path to SSL certificate is invalid. Stopping server..." ) ;
process . exit ( ) ;
}
2014-09-26 23:26:21 +00:00
server = server . createServer ( {
2016-10-04 22:35:04 +00:00
key : fs . readFileSync ( keyPath ) ,
cert : fs . readFileSync ( certPath )
2016-06-08 09:26:24 +00:00
} , app ) . listen ( config . port , config . host ) ;
2014-09-26 23:26:21 +00:00
}
2016-06-08 09:26:24 +00:00
if ( config . identd . enable ) {
2016-04-26 20:53:29 +00:00
if ( manager . identHandler ) {
log . warn ( "Using both identd and oidentd at the same time!" ) ;
}
2014-10-11 17:33:28 +00:00
require ( "./identd" ) . start ( config . identd . port ) ;
2014-10-11 10:09:27 +00:00
}
2016-08-10 06:14:09 +00:00
if ( ! config . public && ( config . ldap || { } ) . enable ) {
2016-07-30 01:20:38 +00:00
ldapclient = ldap . createClient ( {
url : config . ldap . url
} ) ;
authFunction = ldapAuth ;
}
2016-02-29 01:19:11 +00:00
var sockets = io ( server , {
2016-06-08 09:26:24 +00:00
transports : config . transports
2014-11-01 20:06:01 +00:00
} ) ;
2014-08-14 16:35:37 +00:00
sockets . on ( "connect" , function ( socket ) {
if ( config . public ) {
auth . call ( socket ) ;
} else {
init ( socket ) ;
}
} ) ;
2014-09-24 22:23:54 +00:00
manager . sockets = sockets ;
2016-06-08 09:26:24 +00:00
var protocol = config . https . enable ? "https" : "http" ;
log . info ( "The Lounge v" + pkg . version + " is now running on" , protocol + "://" + ( config . host || "*" ) + ":" + config . port + "/" , ( config . public ? "in public mode" : "in private mode" ) ) ;
2016-04-26 10:51:11 +00:00
log . info ( "Press ctrl-c to stop\n" ) ;
2014-08-14 16:35:37 +00:00
if ( ! config . public ) {
2014-09-24 22:23:54 +00:00
manager . loadUsers ( ) ;
if ( config . autoload ) {
manager . autoload ( ) ;
}
2014-08-14 16:35:37 +00:00
}
} ;
2016-04-03 05:12:49 +00:00
function getClientIp ( req ) {
2016-07-31 00:54:09 +00:00
var ip ;
2016-06-08 09:26:24 +00:00
if ( ! Helper . config . reverseProxy ) {
2016-07-31 00:54:09 +00:00
ip = req . connection . remoteAddress ;
2016-04-03 05:12:49 +00:00
} else {
2016-07-31 00:54:09 +00:00
ip = req . headers [ "x-forwarded-for" ] || req . connection . remoteAddress ;
2016-04-03 05:12:49 +00:00
}
2016-07-31 00:54:09 +00:00
return ip . replace ( /^::ffff:/ , "" ) ;
2016-04-03 05:12:49 +00:00
}
2016-05-01 17:27:10 +00:00
function allRequests ( req , res , next ) {
res . setHeader ( "X-Content-Type-Options" , "nosniff" ) ;
return next ( ) ;
}
2016-07-13 07:17:55 +00:00
// Information to populate the About section in UI, either from npm or from git
2016-07-19 01:35:02 +00:00
var gitCommit = null ;
2016-07-13 07:17:55 +00:00
try {
2016-07-19 01:35:02 +00:00
gitCommit = require ( "child_process" )
. execSync ( "git rev-parse --short HEAD 2> /dev/null" ) // Returns hash of current commit
2016-07-13 07:17:55 +00:00
. toString ( )
. trim ( ) ;
} catch ( e ) {
// Not a git repository or git is not installed: treat it as npm release
}
2014-08-14 16:35:37 +00:00
function index ( req , res , next ) {
2016-05-01 09:41:17 +00:00
if ( req . url . split ( "?" ) [ 0 ] !== "/" ) {
return next ( ) ;
}
2014-08-14 16:35:37 +00:00
return fs . readFile ( "client/index.html" , "utf-8" , function ( err , file ) {
var data = _ . merge (
2016-06-12 01:44:28 +00:00
pkg ,
2016-06-08 09:26:24 +00:00
Helper . config
2014-08-14 16:35:37 +00:00
) ;
2016-07-13 07:17:55 +00:00
data . gitCommit = gitCommit ;
2016-08-17 05:52:29 +00:00
data . themes = fs . readdirSync ( "client/themes/" ) . filter ( function ( file ) {
return file . endsWith ( ".css" ) ;
} ) . map ( function ( css ) {
return css . slice ( 0 , - 4 ) ;
} ) ;
2016-02-14 17:09:51 +00:00
var template = _ . template ( file ) ;
2016-09-09 05:17:31 +00:00
res . setHeader ( "Content-Security-Policy" , "default-src *; connect-src 'self' ws: wss:; style-src * 'unsafe-inline'; script-src 'self'; child-src 'none'; object-src 'none'; form-action 'none'; referrer no-referrer;" ) ;
2014-09-13 04:54:17 +00:00
res . setHeader ( "Content-Type" , "text/html" ) ;
res . writeHead ( 200 ) ;
2016-02-14 17:09:51 +00:00
res . end ( template ( data ) ) ;
2014-08-14 16:35:37 +00:00
} ) ;
}
2016-05-31 21:28:31 +00:00
function init ( socket , client ) {
2014-08-14 16:35:37 +00:00
if ( ! client ) {
2016-05-31 21:02:10 +00:00
socket . emit ( "auth" , { success : true } ) ;
2014-08-14 16:35:37 +00:00
socket . on ( "auth" , auth ) ;
} else {
2016-09-25 06:52:16 +00:00
socket . emit ( "authorized" ) ;
2014-08-14 16:35:37 +00:00
socket . on (
"input" ,
function ( data ) {
2014-09-09 19:31:23 +00:00
client . input ( data ) ;
2014-08-14 16:35:37 +00:00
}
) ;
socket . on (
2014-09-10 19:23:56 +00:00
"more" ,
2014-08-14 16:35:37 +00:00
function ( data ) {
2014-09-10 19:23:56 +00:00
client . more ( data ) ;
2014-08-14 16:35:37 +00:00
}
) ;
socket . on (
"conn" ,
function ( data ) {
2016-04-03 05:12:49 +00:00
// prevent people from overriding webirc settings
data . ip = null ;
data . hostname = null ;
2014-08-14 16:35:37 +00:00
client . connect ( data ) ;
}
) ;
2016-07-30 01:20:38 +00:00
if ( ! Helper . config . public && ! Helper . config . ldap . enable ) {
2016-02-17 00:14:43 +00:00
socket . on (
"change-password" ,
function ( data ) {
var old = data . old _password ;
var p1 = data . new _password ;
var p2 = data . verify _password ;
if ( typeof p1 === "undefined" || p1 === "" ) {
socket . emit ( "change-password" , {
error : "Please enter a new password"
} ) ;
return ;
}
if ( p1 !== p2 ) {
socket . emit ( "change-password" , {
error : "Both new password fields must match"
} ) ;
return ;
}
if ( ! bcrypt . compareSync ( old || "" , client . config . password ) ) {
socket . emit ( "change-password" , {
error : "The current password field does not match your account password"
} ) ;
return ;
}
2016-05-31 21:28:31 +00:00
2016-02-17 00:14:43 +00:00
var salt = bcrypt . genSaltSync ( 8 ) ;
var hash = bcrypt . hashSync ( p1 , salt ) ;
2016-05-31 21:28:31 +00:00
client . setPassword ( hash , function ( success ) {
var obj = { } ;
if ( success ) {
obj . success = "Successfully updated your password, all your other sessions were logged out" ;
obj . token = client . config . token ;
} else {
obj . error = "Failed to update your password" ;
}
socket . emit ( "change-password" , obj ) ;
2016-02-17 00:14:43 +00:00
} ) ;
}
) ;
}
2014-09-21 16:46:43 +00:00
socket . on (
"open" ,
function ( data ) {
client . open ( data ) ;
}
2014-09-24 19:42:36 +00:00
) ;
socket . on (
"sort" ,
function ( data ) {
client . sort ( data ) ;
}
) ;
2016-02-17 02:29:44 +00:00
socket . on (
"names" ,
function ( data ) {
client . names ( data ) ;
}
) ;
2014-08-14 16:35:37 +00:00
socket . join ( client . id ) ;
socket . emit ( "init" , {
2014-09-21 16:46:43 +00:00
active : client . activeChannel ,
2014-09-15 21:13:03 +00:00
networks : client . networks ,
2016-06-30 13:06:07 +00:00
token : client . config . token || null
2014-08-14 16:35:37 +00:00
} ) ;
}
}
2016-05-31 21:28:31 +00:00
function reverseDnsLookup ( socket , client ) {
2016-04-03 05:12:49 +00:00
client . ip = getClientIp ( socket . request ) ;
dns . reverse ( client . ip , function ( err , host ) {
if ( ! err && host . length ) {
client . hostname = host [ 0 ] ;
} else {
client . hostname = client . ip ;
}
2016-05-31 21:28:31 +00:00
init ( socket , client ) ;
2016-04-03 05:12:49 +00:00
} ) ;
}
2016-07-30 01:20:38 +00:00
function localAuth ( client , user , password , callback ) {
var result = false ;
try {
result = bcrypt . compareSync ( password || "" , client . config . password ) ;
} catch ( error ) {
if ( error === "Not a valid BCrypt hash." ) {
2016-08-10 06:14:09 +00:00
log . error ( "User (" + user + ") with no local password set tried to sign in. (Probably a LDAP user)" ) ;
2016-07-30 01:20:38 +00:00
}
result = false ;
} finally {
callback ( result ) ;
}
}
function ldapAuth ( client , user , password , callback ) {
var userDN = user . replace ( /([,\\\/#+<>;"= ])/g , "\\$1" ) ;
var bindDN = Helper . config . ldap . primaryKey + "=" + userDN + "," + Helper . config . ldap . baseDN ;
ldapclient . bind ( bindDN , password , function ( err ) {
if ( ! err && ! client ) {
if ( ! manager . addUser ( user , null ) ) {
2016-08-10 06:14:09 +00:00
log . error ( "Unable to create new user" , user ) ;
2016-07-30 01:20:38 +00:00
}
}
callback ( ! err ) ;
} ) ;
}
2014-08-14 16:35:37 +00:00
function auth ( data ) {
var socket = this ;
2016-07-30 01:20:38 +00:00
var client ;
2016-06-08 09:26:24 +00:00
if ( Helper . config . public ) {
2016-07-30 01:20:38 +00:00
client = new Client ( manager ) ;
2014-08-14 16:35:37 +00:00
manager . clients . push ( client ) ;
socket . on ( "disconnect" , function ( ) {
manager . clients = _ . without ( manager . clients , client ) ;
client . quit ( ) ;
} ) ;
2016-06-08 09:26:24 +00:00
if ( Helper . config . webirc ) {
2016-04-03 05:12:49 +00:00
reverseDnsLookup ( socket , client ) ;
} else {
init ( socket , client ) ;
}
2014-08-14 16:35:37 +00:00
} else {
2016-07-30 01:20:38 +00:00
client = manager . findClient ( data . user , data . token ) ;
2016-08-18 04:02:40 +00:00
var signedIn = data . token && client && data . token === client . config . token ;
2016-07-30 01:20:38 +00:00
var token ;
2016-08-18 04:02:40 +00:00
if ( client && ( data . remember || data . token ) ) {
2016-08-10 06:26:47 +00:00
token = client . config . token ;
2016-07-30 01:20:38 +00:00
}
var authCallback = function ( success ) {
2014-09-15 21:13:03 +00:00
if ( success ) {
2016-07-30 01:20:38 +00:00
if ( ! client ) {
// LDAP just created a user
manager . loadUser ( data . user ) ;
client = manager . findClient ( data . user ) ;
}
2016-06-08 09:26:24 +00:00
if ( Helper . config . webirc !== null && ! client . config [ "ip" ] ) {
2016-05-31 21:28:31 +00:00
reverseDnsLookup ( socket , client ) ;
2016-04-03 05:12:49 +00:00
} else {
2016-07-30 01:20:38 +00:00
init ( socket , client , token ) ;
2016-04-03 05:12:49 +00:00
}
2016-07-30 01:20:38 +00:00
} else {
socket . emit ( "auth" , { success : success } ) ;
2014-09-15 21:13:03 +00:00
}
2016-07-30 01:20:38 +00:00
} ;
if ( signedIn ) {
authCallback ( true ) ;
} else {
authFunction ( client , data . user , data . password , authCallback ) ;
2014-08-14 16:35:37 +00:00
}
}
}