2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
2021-01-21 11:31:35 -08:00
start ,
2021-07-07 12:59:17 -07:00
stop ,
_MOCK _APP : null
2015-07-20 00:09:47 -07:00
} ;
2021-08-19 21:39:27 -07:00
const addonConfigs = require ( './addonconfigs.js' ) ,
assert = require ( 'assert' ) ,
2016-02-18 16:04:53 +01:00
apps = require ( './apps.js' ) ,
2023-08-26 08:18:58 +05:30
AuditSource = require ( './auditsource.js' ) ,
2019-10-24 11:13:48 -07:00
BoxError = require ( './boxerror.js' ) ,
2019-10-24 13:41:41 -07:00
constants = require ( './constants.js' ) ,
2015-07-20 00:09:47 -07:00
debug = require ( 'debug' ) ( 'box:ldap' ) ,
2016-04-30 23:16:37 -07:00
eventlog = require ( './eventlog.js' ) ,
2020-11-12 23:25:33 -08:00
groups = require ( './groups.js' ) ,
2016-05-29 17:25:23 -07:00
ldap = require ( 'ldapjs' ) ,
2018-01-22 20:35:08 +01:00
mail = require ( './mail.js' ) ,
2019-03-22 15:42:16 -07:00
safe = require ( 'safetydance' ) ,
2021-09-07 09:57:49 -07:00
users = require ( './users.js' ) ,
util = require ( 'util' ) ;
2015-07-20 00:09:47 -07:00
2022-04-14 17:41:41 -05:00
let gServer = null ;
2015-07-20 00:09:47 -07:00
2021-07-07 12:59:17 -07:00
const NOOP = function ( ) { } ;
2015-07-20 00:09:47 -07:00
2018-09-03 15:38:50 +02:00
// Will attach req.app if successful
2021-08-20 09:19:44 -07:00
async function authenticateApp ( req , res , next ) {
2021-07-07 12:59:17 -07:00
const sourceIp = req . connection . ldap . id . split ( ':' ) [ 0 ] ;
2018-09-03 15:38:50 +02:00
if ( sourceIp . split ( '.' ) . length !== 4 ) return next ( new ldap . InsufficientAccessRightsError ( 'Missing source identifier' ) ) ;
2016-02-18 16:04:53 +01:00
2021-07-07 12:59:17 -07:00
// this is only used by the ldap test. the apps tests still uses proper docker
if ( constants . TEST && sourceIp === '127.0.0.1' ) {
req . app = exports . _MOCK _APP ;
return next ( ) ;
}
2021-08-20 09:19:44 -07:00
const [ error , app ] = await safe ( apps . getByIpAddress ( sourceIp ) ) ;
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
if ( ! app ) return next ( new ldap . OperationsError ( 'Could not detect app source' ) ) ;
2016-06-17 10:08:41 -05:00
2021-08-20 09:19:44 -07:00
req . app = app ;
2016-06-17 10:08:41 -05:00
2021-08-20 09:19:44 -07:00
next ( ) ;
2016-02-18 16:04:53 +01:00
}
2022-01-07 14:06:13 +01:00
// Will attach req.user if successful
2021-12-23 21:31:48 +01:00
async function userAuthInternal ( appId , req , res , next ) {
// extract the common name which might have different attribute names
const attributeName = Object . keys ( req . dn . rdns [ 0 ] . attrs ) [ 0 ] ;
const commonName = req . dn . rdns [ 0 ] . attrs [ attributeName ] . value ;
2024-01-03 15:19:03 +01:00
if ( ! commonName ) return next ( new ldap . NoSuchObjectError ( 'Missing CN' ) ) ;
2021-12-23 21:31:48 +01:00
2024-01-06 13:29:23 +01:00
// this code here is only for completeness. none of the apps send totptoken
2023-03-12 15:09:20 +01:00
const TOTPTOKEN _ATTRIBUTE _NAME = 'totptoken' ;
const totpToken = req . dn . rdns [ 0 ] . attrs [ TOTPTOKEN _ATTRIBUTE _NAME ] ? req . dn . rdns [ 0 ] . attrs [ TOTPTOKEN _ATTRIBUTE _NAME ] . value : null ;
2021-12-23 21:31:48 +01:00
let verifyFunc ;
if ( attributeName === 'mail' ) {
verifyFunc = users . verifyWithEmail ;
} else if ( commonName . indexOf ( '@' ) !== - 1 ) { // if mail is specified, enforce mail check
verifyFunc = users . verifyWithEmail ;
} else if ( commonName . indexOf ( 'uid-' ) === 0 ) {
verifyFunc = users . verify ;
} else {
verifyFunc = users . verifyWithUsername ;
}
2024-01-07 22:01:57 +01:00
const [ error , user ] = await safe ( verifyFunc ( commonName , req . credentials || '' , appId || '' , { skipTotpCheck : true , totpToken } ) ) ;
2024-01-03 14:51:00 +01:00
if ( error && error . reason === BoxError . NOT _FOUND ) return next ( new ldap . NoSuchObjectError ( error . message ) ) ;
if ( error && error . reason === BoxError . INVALID _CREDENTIALS ) return next ( new ldap . InvalidCredentialsError ( error . message ) ) ;
2021-12-23 21:31:48 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
req . user = user ;
next ( ) ;
}
2021-08-20 09:19:44 -07:00
async function getUsersWithAccessToApp ( req ) {
2018-09-03 15:38:50 +02:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2021-07-15 09:50:11 -07:00
2021-08-20 09:19:44 -07:00
const result = await users . list ( ) ;
2022-01-21 21:07:33 -08:00
const allowedUsers = result . filter ( ( user ) => user . active && apps . canAccess ( req . app , user ) ) ; // do not list inactive users
2021-08-20 09:19:44 -07:00
return allowedUsers ;
2016-05-12 13:36:53 -07:00
}
2015-07-20 00:09:47 -07:00
2017-10-27 01:25:07 +02:00
// helper function to deal with pagination
function finalSend ( results , req , res , next ) {
2021-08-20 09:19:44 -07:00
let min = 0 ;
let max = results . length ;
let cookie = null ;
let pageSize = 0 ;
2017-10-27 01:25:07 +02:00
// check if this is a paging request, if so get the cookie for session info
req . controls . forEach ( function ( control ) {
if ( control . type === ldap . PagedResultsControl . OID ) {
pageSize = control . value . size ;
cookie = control . value . cookie ;
}
} ) ;
function sendPagedResults ( start , end ) {
start = ( start < min ) ? min : start ;
end = ( end > max || end < min ) ? max : end ;
2021-08-20 09:19:44 -07:00
let i ;
2017-10-27 01:25:07 +02:00
for ( i = start ; i < end ; i ++ ) {
res . send ( results [ i ] ) ;
}
return i ;
}
if ( cookie && Buffer . isBuffer ( cookie ) ) {
// we have pagination
2022-04-14 17:41:41 -05:00
let first = min ;
2017-10-27 01:25:07 +02:00
if ( cookie . length !== 0 ) {
first = parseInt ( cookie . toString ( ) , 10 ) ;
}
2022-04-14 17:41:41 -05:00
const last = sendPagedResults ( first , first + pageSize ) ;
2017-10-27 01:25:07 +02:00
2022-04-14 17:41:41 -05:00
let resultCookie ;
2017-10-27 01:25:07 +02:00
if ( last < max ) {
2019-03-21 20:06:14 -07:00
resultCookie = Buffer . from ( last . toString ( ) ) ;
2017-10-27 01:25:07 +02:00
} else {
2019-03-21 20:06:14 -07:00
resultCookie = Buffer . from ( '' ) ;
2017-10-27 01:25:07 +02:00
}
res . controls . push ( new ldap . PagedResultsControl ( {
value : {
size : pageSize , // correctness not required here
cookie : resultCookie
}
} ) ) ;
} else {
// no pagination simply send all
results . forEach ( function ( result ) {
res . send ( result ) ;
} ) ;
}
// all done
res . end ( ) ;
next ( ) ;
}
2021-08-20 09:19:44 -07:00
async function userSearch ( req , res , next ) {
2017-03-13 11:09:12 +01:00
debug ( 'user search: dn %s, scope %s, filter %s (from %s)' , req . dn . toString ( ) , req . scope , req . filter . toString ( ) , req . connection . ldap . id ) ;
2021-08-20 09:19:44 -07:00
const [ error , result ] = await safe ( getUsersWithAccessToApp ( req ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2017-10-27 01:25:07 +02:00
2022-08-04 11:22:16 +02:00
const [ groupsError , allGroups ] = await safe ( groups . listWithMembers ( ) ) ;
2024-01-03 14:51:00 +01:00
if ( groupsError ) return next ( new ldap . OperationsError ( groupsError . message ) ) ;
2022-08-04 11:22:16 +02:00
2021-08-20 09:19:44 -07:00
let results = [ ] ;
2017-03-13 11:09:12 +01:00
2021-08-20 09:19:44 -07:00
// send user objects
result . forEach ( function ( user ) {
// skip entries with empty username. Some apps like owncloud can't deal with this
if ( ! user . username ) return ;
2017-03-13 11:09:12 +01:00
2021-08-20 09:19:44 -07:00
const dn = ldap . parseDN ( 'cn=' + user . id + ',ou=users,dc=cloudron' ) ;
2017-03-13 11:09:12 +01:00
2021-08-20 09:19:44 -07:00
const displayName = user . displayName || user . username || '' ; // displayName can be empty and username can be null
const nameParts = displayName . split ( ' ' ) ;
const firstName = nameParts [ 0 ] ;
const lastName = nameParts . length > 1 ? nameParts [ nameParts . length - 1 ] : '' ; // choose last part, if it exists
2017-03-13 11:09:12 +01:00
2021-08-20 09:19:44 -07:00
const obj = {
dn : dn . toString ( ) ,
attributes : {
2022-01-14 14:31:33 -08:00
objectclass : [ 'user' , 'inetorgperson' , 'person' , 'organizationalperson' , 'top' ] ,
2021-08-20 09:19:44 -07:00
objectcategory : 'person' ,
2022-02-18 17:31:02 +01:00
cn : displayName ,
2021-08-20 09:19:44 -07:00
uid : user . id ,
entryuuid : user . id , // to support OpenLDAP clients
mail : user . email ,
mailAlternateAddress : user . fallbackEmail ,
displayname : displayName ,
givenName : firstName ,
username : user . username ,
samaccountname : user . username , // to support ActiveDirectory clients
2022-10-30 15:07:26 +01:00
memberof : allGroups . filter ( function ( g ) { return g . userIds . indexOf ( user . id ) !== - 1 ; } ) . map ( function ( g ) { return ` cn= ${ g . name } ,ou=groups,dc=cloudron ` ; } )
2021-08-20 09:19:44 -07:00
}
} ;
2017-03-13 11:09:12 +01:00
2021-08-20 09:19:44 -07:00
// http://www.zytrax.com/books/ldap/ape/core-schema.html#sn has 'name' as SUP which is a DirectoryString
// which is required to have atleast one character if present
if ( lastName . length !== 0 ) obj . attributes . sn = lastName ;
2017-03-13 11:09:12 +01:00
2021-08-20 09:19:44 -07:00
// ensure all filter values are also lowercase
const lowerCaseFilter = safe ( function ( ) { return ldap . parseFilter ( req . filter . toString ( ) . toLowerCase ( ) ) ; } , null ) ;
2024-01-03 14:51:00 +01:00
if ( ! lowerCaseFilter ) return next ( new ldap . OperationsError ( safe . error . message ) ) ;
2017-03-13 11:09:12 +01:00
2021-08-20 09:19:44 -07:00
if ( ( req . dn . equals ( dn ) || req . dn . parentOf ( dn ) ) && lowerCaseFilter . matches ( obj . attributes ) ) {
results . push ( obj ) ;
}
2017-03-13 11:09:12 +01:00
} ) ;
2021-08-20 09:19:44 -07:00
finalSend ( results , req , res , next ) ;
2017-03-13 11:09:12 +01:00
}
2021-08-20 09:19:44 -07:00
async function groupSearch ( req , res , next ) {
2016-05-16 14:14:58 -07:00
debug ( 'group search: dn %s, scope %s, filter %s (from %s)' , req . dn . toString ( ) , req . scope , req . filter . toString ( ) , req . connection . ldap . id ) ;
2016-05-12 13:36:53 -07:00
2021-08-20 09:19:44 -07:00
const results = [ ] ;
2017-10-27 01:25:07 +02:00
2024-01-03 14:51:00 +01:00
let [ groupsListError , resultGroups ] = await safe ( groups . listWithMembers ( ) ) ;
if ( groupsListError ) return next ( new ldap . OperationsError ( groupsListError . message ) ) ;
2021-12-09 15:07:30 +01:00
2021-12-09 17:23:14 +01:00
if ( req . app . accessRestriction && req . app . accessRestriction . groups ) {
resultGroups = resultGroups . filter ( function ( g ) { return req . app . accessRestriction . groups . indexOf ( g . id ) !== - 1 ; } ) ;
}
2021-12-09 15:07:30 +01:00
2021-12-09 17:23:14 +01:00
resultGroups . forEach ( function ( group ) {
2022-07-29 11:11:53 +02:00
const dn = ldap . parseDN ( ` cn= ${ group . name } ,ou=groups,dc=cloudron ` ) ;
2021-12-09 15:07:30 +01:00
const obj = {
dn : dn . toString ( ) ,
attributes : {
objectclass : [ 'group' ] ,
cn : group . name ,
2022-07-29 11:11:53 +02:00
gidnumber : group . id ,
memberuid : group . userIds
2021-12-09 15:07:30 +01:00
}
} ;
// ensure all filter values are also lowercase
const lowerCaseFilter = safe ( function ( ) { return ldap . parseFilter ( req . filter . toString ( ) . toLowerCase ( ) ) ; } , null ) ;
2024-01-03 14:51:00 +01:00
if ( ! lowerCaseFilter ) return next ( new ldap . OperationsError ( safe . error . message ) ) ;
2021-12-09 15:07:30 +01:00
if ( ( req . dn . equals ( dn ) || req . dn . parentOf ( dn ) ) && lowerCaseFilter . matches ( obj . attributes ) ) {
results . push ( obj ) ;
}
} ) ;
2021-08-20 09:19:44 -07:00
finalSend ( results , req , res , next ) ;
2016-05-12 13:36:53 -07:00
}
2021-08-20 09:19:44 -07:00
async function groupUsersCompare ( req , res , next ) {
2017-10-24 01:35:35 +02:00
debug ( 'group users compare: dn %s, attribute %s, value %s (from %s)' , req . dn . toString ( ) , req . attribute , req . value , req . connection . ldap . id ) ;
2021-08-20 09:19:44 -07:00
const [ error , result ] = await safe ( getUsersWithAccessToApp ( req ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2017-10-24 01:35:35 +02:00
2021-08-20 09:19:44 -07:00
// we only support memberuid here, if we add new group attributes later add them here
if ( req . attribute === 'memberuid' ) {
const found = result . find ( function ( u ) { return u . id === req . value ; } ) ;
if ( found ) return res . end ( true ) ;
}
2017-10-24 01:35:35 +02:00
2021-08-20 09:19:44 -07:00
res . end ( false ) ;
2017-10-24 01:35:35 +02:00
}
2021-08-20 09:19:44 -07:00
async function groupAdminsCompare ( req , res , next ) {
2017-10-24 01:35:35 +02:00
debug ( 'group admins compare: dn %s, attribute %s, value %s (from %s)' , req . dn . toString ( ) , req . attribute , req . value , req . connection . ldap . id ) ;
2021-08-20 09:19:44 -07:00
const [ error , result ] = await safe ( getUsersWithAccessToApp ( req ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2017-10-24 01:35:35 +02:00
2021-08-20 09:19:44 -07:00
// we only support memberuid here, if we add new group attributes later add them here
if ( req . attribute === 'memberuid' ) {
2022-04-14 17:41:41 -05:00
const user = result . find ( function ( u ) { return u . id === req . value ; } ) ;
2021-08-20 09:19:44 -07:00
if ( user && users . compareRoles ( user . role , users . ROLE _ADMIN ) >= 0 ) return res . end ( true ) ;
}
2017-10-24 01:35:35 +02:00
2021-08-20 09:19:44 -07:00
res . end ( false ) ;
2017-10-24 01:35:35 +02:00
}
2021-08-17 15:45:57 -07:00
async function mailboxSearch ( req , res , next ) {
2016-09-26 10:18:58 -07:00
debug ( 'mailbox search: dn %s, scope %s, filter %s (from %s)' , req . dn . toString ( ) , req . scope , req . filter . toString ( ) , req . connection . ldap . id ) ;
2016-05-29 18:24:54 -07:00
2022-08-19 02:31:20 +02:00
// if cn is set OR filter is mail= we only search for one mailbox specifically
let email , dn ;
2018-05-03 18:05:32 +02:00
if ( req . dn . rdns [ 0 ] . attrs . cn ) {
2022-08-19 02:31:20 +02:00
email = req . dn . rdns [ 0 ] . attrs . cn . value . toLowerCase ( ) ;
dn = req . dn . toString ( ) ;
} else if ( req . filter instanceof ldap . EqualityFilter && req . filter . attribute === 'mail' ) {
email = req . filter . value . toLowerCase ( ) ;
dn = ` cn= ${ email } , ${ req . dn . toString ( ) } ` ;
}
if ( email ) {
2021-08-17 15:45:57 -07:00
const parts = email . split ( '@' ) ;
2022-08-19 02:31:20 +02:00
if ( parts . length !== 2 ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ;
2017-10-27 01:25:07 +02:00
2021-08-17 15:45:57 -07:00
const [ error , mailbox ] = await safe ( mail . getMailbox ( parts [ 0 ] , parts [ 1 ] ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2022-08-19 02:31:20 +02:00
if ( ! mailbox ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ;
2024-01-03 14:51:00 +01:00
if ( ! mailbox . active ) return next ( new ldap . NoSuchObjectError ( 'Mailbox is not active' ) ) ;
2021-08-17 15:45:57 -07:00
const obj = {
2022-08-19 02:31:20 +02:00
dn : dn . toString ( ) ,
2021-08-17 15:45:57 -07:00
attributes : {
objectclass : [ 'mailbox' ] ,
objectcategory : 'mailbox' ,
cn : ` ${ mailbox . name } @ ${ mailbox . domain } ` ,
uid : ` ${ mailbox . name } @ ${ mailbox . domain } ` ,
2022-08-17 23:18:38 +02:00
mail : ` ${ mailbox . name } @ ${ mailbox . domain } ` ,
storagequota : mailbox . storageQuota ,
messagesquota : mailbox . messagesQuota ,
2021-08-17 15:45:57 -07:00
}
} ;
// ensure all filter values are also lowercase
const lowerCaseFilter = safe ( function ( ) { return ldap . parseFilter ( req . filter . toString ( ) . toLowerCase ( ) ) ; } , null ) ;
2024-01-03 14:51:00 +01:00
if ( ! lowerCaseFilter ) return next ( new ldap . OperationsError ( safe . error . message ) ) ;
2021-08-17 15:45:57 -07:00
if ( lowerCaseFilter . matches ( obj . attributes ) ) {
finalSend ( [ obj ] , req , res , next ) ;
} else {
res . end ( ) ;
}
2022-08-19 02:31:20 +02:00
} else { // new sogo and dovecot listing (doveadm -A)
2022-02-07 16:22:05 +01:00
// TODO figure out how proper pagination here could work
let [ error , mailboxes ] = await safe ( mail . listAllMailboxes ( 1 , 100000 ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2018-05-03 18:05:32 +02:00
2021-08-17 15:45:57 -07:00
mailboxes = mailboxes . filter ( m => m . active ) ;
2021-04-14 22:37:01 -07:00
2021-08-17 15:45:57 -07:00
let results = [ ] ;
2018-05-03 18:05:32 +02:00
2021-08-17 15:45:57 -07:00
for ( const mailbox of mailboxes ) {
const dn = ldap . parseDN ( ` cn= ${ mailbox . name } @ ${ mailbox . domain } ,ou=mailboxes,dc=cloudron ` ) ;
2021-07-15 09:50:11 -07:00
2021-12-02 22:14:41 -08:00
if ( mailbox . ownerType === mail . OWNERTYPE _APP ) continue ; // cannot login with app mailbox anyway
2021-08-17 15:45:57 -07:00
const [ error , ownerObject ] = await safe ( mailbox . ownerType === mail . OWNERTYPE _USER ? users . get ( mailbox . ownerId ) : groups . get ( mailbox . ownerId ) ) ;
if ( error || ! ownerObject ) continue ; // skip mailboxes with unknown user
2021-07-15 09:50:11 -07:00
2021-08-17 15:45:57 -07:00
const obj = {
dn : dn . toString ( ) ,
attributes : {
objectclass : [ 'mailbox' ] ,
objectcategory : 'mailbox' ,
displayname : mailbox . ownerType === mail . OWNERTYPE _USER ? ownerObject . displayName : ownerObject . name ,
cn : ` ${ mailbox . name } @ ${ mailbox . domain } ` ,
uid : ` ${ mailbox . name } @ ${ mailbox . domain } ` ,
2022-08-17 23:18:38 +02:00
mail : ` ${ mailbox . name } @ ${ mailbox . domain } ` ,
storagequota : mailbox . storageQuota ,
messagesquota : mailbox . messagesQuota ,
2021-08-17 15:45:57 -07:00
}
} ;
2020-03-05 22:40:25 -08:00
2021-08-17 15:45:57 -07:00
mailbox . aliases . forEach ( function ( a , idx ) {
obj . attributes [ 'mail' + idx ] = ` ${ a . name } @ ${ a . domain } ` ;
} ) ;
2020-03-05 22:40:25 -08:00
2021-08-17 15:45:57 -07:00
// ensure all filter values are also lowercase
const lowerCaseFilter = safe ( function ( ) { return ldap . parseFilter ( req . filter . toString ( ) . toLowerCase ( ) ) ; } , null ) ;
2024-01-03 14:51:00 +01:00
if ( ! lowerCaseFilter ) return next ( new ldap . OperationsError ( safe . error . message ) ) ;
2021-07-15 09:50:11 -07:00
2021-08-17 15:45:57 -07:00
if ( ( req . dn . equals ( dn ) || req . dn . parentOf ( dn ) ) && lowerCaseFilter . matches ( obj . attributes ) ) {
results . push ( obj ) ;
2021-07-15 09:50:11 -07:00
}
2021-08-17 15:45:57 -07:00
}
2021-07-15 09:50:11 -07:00
2021-08-17 15:45:57 -07:00
finalSend ( results , req , res , next ) ;
2018-05-03 18:05:32 +02:00
}
2016-09-25 18:59:11 -07:00
}
2021-08-17 15:45:57 -07:00
async function mailAliasSearch ( req , res , next ) {
2016-09-25 18:59:11 -07:00
debug ( 'mail alias get: dn %s, scope %s, filter %s (from %s)' , req . dn . toString ( ) , req . scope , req . filter . toString ( ) , req . connection . ldap . id ) ;
2024-01-03 15:19:03 +01:00
if ( ! req . dn . rdns [ 0 ] . attrs . cn ) return next ( new ldap . NoSuchObjectError ( 'Missing CN' ) ) ;
2017-10-27 01:25:07 +02:00
2021-08-17 15:45:57 -07:00
const email = req . dn . rdns [ 0 ] . attrs . cn . value . toLowerCase ( ) ;
const parts = email . split ( '@' ) ;
2024-01-03 15:19:03 +01:00
if ( parts . length !== 2 ) return next ( new ldap . NoSuchObjectError ( 'Invalid CN' ) ) ;
2018-01-18 18:14:31 -08:00
2022-08-18 13:21:24 +02:00
const [ error , alias ] = await safe ( mail . searchAlias ( parts [ 0 ] , parts [ 1 ] ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2024-01-03 15:19:03 +01:00
if ( ! alias ) return next ( new ldap . NoSuchObjectError ( 'No such alias' ) ) ;
2021-08-17 15:45:57 -07:00
2024-01-03 14:51:00 +01:00
if ( ! alias . active ) return next ( new ldap . NoSuchObjectError ( 'Mailbox is not active' ) ) ; // there is no way to disable an alias. this is just here for completeness
2021-08-17 15:45:57 -07:00
// https://wiki.debian.org/LDAP/MigrationTools/Examples
// https://docs.oracle.com/cd/E19455-01/806-5580/6jej518pp/index.html
// member is fully qualified - https://docs.oracle.com/cd/E19957-01/816-6082-10/chap4.doc.html#43314
const obj = {
dn : req . dn . toString ( ) ,
attributes : {
objectclass : [ 'nisMailAlias' ] ,
objectcategory : 'nisMailAlias' ,
2022-08-18 13:21:24 +02:00
cn : ` ${ parts [ 0 ] } @ ${ alias . domain } ` , // alias.name can contain wildcard character
2021-08-17 15:45:57 -07:00
rfc822MailMember : ` ${ alias . aliasName } @ ${ alias . aliasDomain } `
}
} ;
2016-09-25 18:59:11 -07:00
2021-08-17 15:45:57 -07:00
// ensure all filter values are also lowercase
const lowerCaseFilter = safe ( function ( ) { return ldap . parseFilter ( req . filter . toString ( ) . toLowerCase ( ) ) ; } , null ) ;
2024-01-03 14:51:00 +01:00
if ( ! lowerCaseFilter ) return next ( new ldap . OperationsError ( safe . error . message ) ) ;
2016-09-25 18:59:11 -07:00
2021-08-17 15:45:57 -07:00
if ( lowerCaseFilter . matches ( obj . attributes ) ) {
finalSend ( [ obj ] , req , res , next ) ;
} else {
res . end ( ) ;
}
2016-09-25 18:59:11 -07:00
}
2021-08-17 15:45:57 -07:00
async function mailingListSearch ( req , res , next ) {
2016-09-27 12:20:20 -07:00
debug ( 'mailing list get: dn %s, scope %s, filter %s (from %s)' , req . dn . toString ( ) , req . scope , req . filter . toString ( ) , req . connection . ldap . id ) ;
2016-09-25 18:59:11 -07:00
2024-01-03 15:19:03 +01:00
if ( ! req . dn . rdns [ 0 ] . attrs . cn ) return next ( new ldap . NoSuchObjectError ( 'Missing CN' ) ) ;
2017-10-27 01:25:07 +02:00
2019-11-06 16:45:44 -08:00
let email = req . dn . rdns [ 0 ] . attrs . cn . value . toLowerCase ( ) ;
let parts = email . split ( '@' ) ;
2024-01-03 15:19:03 +01:00
if ( parts . length !== 2 ) return next ( new ldap . NoSuchObjectError ( 'Invalid CN' ) ) ;
2019-11-06 16:45:44 -08:00
const name = parts [ 0 ] , domain = parts [ 1 ] ;
2018-01-18 18:14:31 -08:00
2021-08-17 15:45:57 -07:00
const [ error , result ] = await safe ( mail . resolveList ( parts [ 0 ] , parts [ 1 ] ) ) ;
2024-01-03 15:19:03 +01:00
if ( error && error . reason === BoxError . NOT _FOUND ) return next ( new ldap . NoSuchObjectError ( 'No such list' ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2021-08-17 15:45:57 -07:00
const { resolvedMembers , list } = result ;
2024-01-03 14:51:00 +01:00
if ( ! list . active ) return next ( new ldap . NoSuchObjectError ( 'List is not active' ) ) ;
2021-08-17 15:45:57 -07:00
// http://ldapwiki.willeke.com/wiki/Original%20Mailgroup%20Schema%20From%20Netscape
// members are fully qualified (https://docs.oracle.com/cd/E19444-01/816-6018-10/groups.htm#13356)
const obj = {
dn : req . dn . toString ( ) ,
attributes : {
objectclass : [ 'mailGroup' ] ,
objectcategory : 'mailGroup' ,
cn : ` ${ name } @ ${ domain } ` , // fully qualified
mail : ` ${ name } @ ${ domain } ` ,
membersOnly : list . membersOnly , // ldapjs only supports strings and string array. so this is not a bool!
mgrpRFC822MailMember : resolvedMembers // fully qualified
}
} ;
2021-04-14 22:37:01 -07:00
2021-08-17 15:45:57 -07:00
// ensure all filter values are also lowercase
const lowerCaseFilter = safe ( function ( ) { return ldap . parseFilter ( req . filter . toString ( ) . toLowerCase ( ) ) ; } , null ) ;
2024-01-03 14:51:00 +01:00
if ( ! lowerCaseFilter ) return next ( new ldap . OperationsError ( safe . error . message ) ) ;
2016-05-29 18:24:54 -07:00
2021-08-17 15:45:57 -07:00
if ( lowerCaseFilter . matches ( obj . attributes ) ) {
finalSend ( [ obj ] , req , res , next ) ;
} else {
res . end ( ) ;
}
2016-05-29 18:24:54 -07:00
}
2018-09-03 15:38:50 +02:00
// Will attach req.user if successful
2021-07-15 09:50:11 -07:00
async function authenticateUser ( req , res , next ) {
2016-05-16 14:14:58 -07:00
debug ( 'user bind: %s (from %s)' , req . dn . toString ( ) , req . connection . ldap . id ) ;
2016-05-12 13:36:53 -07:00
2021-12-23 21:31:48 +01:00
const appId = req . app . id ;
2016-05-12 13:36:53 -07:00
2021-12-23 21:31:48 +01:00
await userAuthInternal ( appId , req , res , next ) ;
2016-05-29 17:16:52 -07:00
}
2021-08-20 09:19:44 -07:00
async function authorizeUserForApp ( req , res , next ) {
2018-09-03 15:38:50 +02:00
assert . strictEqual ( typeof req . user , 'object' ) ;
assert . strictEqual ( typeof req . app , 'object' ) ;
2016-05-29 17:16:52 -07:00
2021-09-21 10:00:47 -07:00
const canAccess = apps . canAccess ( req . app , req . user ) ;
2021-08-20 09:19:44 -07:00
// we return no such object, to avoid leakage of a users existence
2024-01-03 15:19:03 +01:00
if ( ! canAccess ) return next ( new ldap . NoSuchObjectError ( 'Invalid user or insufficient previleges' ) ) ;
2016-05-12 13:36:53 -07:00
2023-08-26 08:18:58 +05:30
await eventlog . upsertLoginEvent ( req . user . ghost ? eventlog . ACTION _USER _LOGIN _GHOST : eventlog . ACTION _USER _LOGIN , AuditSource . LDAP , { appId : req . app . id , userId : req . user . id , user : users . removePrivateFields ( req . user ) } ) ;
2016-05-12 13:36:53 -07:00
2021-08-20 09:19:44 -07:00
res . end ( ) ;
2016-05-12 13:36:53 -07:00
}
2021-07-15 09:50:11 -07:00
async function verifyMailboxPassword ( mailbox , password ) {
2020-11-12 23:25:33 -08:00
assert . strictEqual ( typeof mailbox , 'object' ) ;
assert . strictEqual ( typeof password , 'string' ) ;
2021-12-02 22:14:41 -08:00
if ( mailbox . ownerType === mail . OWNERTYPE _USER ) {
2024-01-07 22:01:57 +01:00
return await users . verify ( mailbox . ownerId , password , users . AP _MAIL /* identifier */ , { skipTotpCheck : true } ) ;
2021-12-02 22:14:41 -08:00
} else if ( mailbox . ownerType === mail . OWNERTYPE _GROUP ) {
const userIds = await groups . getMembers ( mailbox . ownerId ) ;
let verifiedUser = null ;
for ( const userId of userIds ) {
2024-01-07 22:01:57 +01:00
const [ error , result ] = await safe ( users . verify ( userId , password , users . AP _MAIL /* identifier */ , { skipTotpCheck : true } ) ) ;
2021-12-02 22:14:41 -08:00
if ( error ) continue ; // try the next user
verifiedUser = result ;
break ; // found a matching validated user
}
2020-11-12 23:25:33 -08:00
2021-12-02 22:14:41 -08:00
if ( ! verifiedUser ) throw new BoxError ( BoxError . INVALID _CREDENTIALS ) ;
return verifiedUser ;
} else {
throw new BoxError ( BoxError . INVALID _CREDENTIALS ) ;
2021-07-15 09:50:11 -07:00
}
2020-11-12 23:25:33 -08:00
}
2021-08-20 09:19:44 -07:00
async function authenticateSftp ( req , res , next ) {
2019-04-04 20:46:01 -07:00
debug ( 'sftp auth: %s (from %s)' , req . dn . toString ( ) , req . connection . ldap . id ) ;
2019-03-19 11:59:51 -07:00
2024-01-03 15:19:03 +01:00
if ( ! req . dn . rdns [ 0 ] . attrs . cn ) return next ( new ldap . NoSuchObjectError ( 'Missing CN' ) ) ;
2019-03-18 21:15:50 -07:00
2021-07-15 09:50:11 -07:00
const email = req . dn . rdns [ 0 ] . attrs . cn . value . toLowerCase ( ) ;
const parts = email . split ( '@' ) ;
2024-01-03 15:19:03 +01:00
if ( parts . length !== 2 ) return next ( new ldap . NoSuchObjectError ( 'Invalid CN' ) ) ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
let [ error , app ] = await safe ( apps . getByFqdn ( parts [ 1 ] ) ) ;
2024-01-03 15:19:03 +01:00
if ( error || ! app ) return next ( new ldap . InvalidCredentialsError ( ) ) ;
2019-03-18 21:15:50 -07:00
2024-01-07 22:01:57 +01:00
[ error ] = await safe ( users . verifyWithUsername ( parts [ 0 ] , req . credentials , app . id , { skipTotpCheck : true } ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . InvalidCredentialsError ( error . message ) ) ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
debug ( 'sftp auth: success' ) ;
2020-03-26 21:50:25 -07:00
2021-08-20 09:19:44 -07:00
res . end ( ) ;
2019-03-18 21:15:50 -07:00
}
2021-08-20 09:19:44 -07:00
async function userSearchSftp ( req , res , next ) {
2019-04-04 20:46:01 -07:00
debug ( 'sftp user search: dn %s, scope %s, filter %s (from %s)' , req . dn . toString ( ) , req . scope , req . filter . toString ( ) , req . connection . ldap . id ) ;
2019-03-19 11:59:51 -07:00
2024-01-03 15:19:03 +01:00
if ( req . filter . attribute !== 'username' || ! req . filter . value ) return next ( new ldap . NoSuchObjectError ( ) ) ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
const parts = req . filter . value . split ( '@' ) ;
2024-01-03 15:19:03 +01:00
if ( parts . length !== 2 ) return next ( new ldap . NoSuchObjectError ( ) ) ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
const username = parts [ 0 ] ;
const appFqdn = parts [ 1 ] ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
const [ error , app ] = await safe ( apps . getByFqdn ( appFqdn ) ) ;
2024-01-03 14:51:00 +01:00
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
// only allow apps which specify "ftp" support in the localstorage addon
if ( ! safe . query ( app . manifest . addons , 'localstorage.ftp.uid' ) ) return next ( new ldap . UnavailableError ( 'Not supported' ) ) ;
if ( typeof app . manifest . addons . localstorage . ftp . uid !== 'number' ) return next ( new ldap . UnavailableError ( 'Bad uid, must be a number' ) ) ;
2019-04-04 22:38:40 -07:00
2021-08-20 09:19:44 -07:00
const uidNumber = app . manifest . addons . localstorage . ftp . uid ;
2019-03-19 21:17:23 -07:00
2021-08-20 09:19:44 -07:00
const [ userGetError , user ] = await safe ( users . getByUsername ( username ) ) ;
2024-01-03 14:51:00 +01:00
if ( userGetError ) return next ( new ldap . OperationsError ( userGetError . message ) ) ;
2021-08-20 09:19:44 -07:00
if ( ! user ) return next ( new ldap . OperationsError ( 'Invalid username' ) ) ;
2019-03-18 21:15:50 -07:00
2021-09-21 22:42:32 -07:00
if ( ! apps . isOperator ( app , user ) ) return next ( new ldap . InsufficientAccessRightsError ( 'Not authorized' ) ) ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
const obj = {
dn : ldap . parseDN ( ` cn= ${ username } @ ${ appFqdn } ,ou=sftp,dc=cloudron ` ) . toString ( ) ,
attributes : {
2022-06-01 22:44:52 -07:00
homeDirectory : app . storageVolumeId ? ` /mnt/app- ${ app . id } ` : ` /mnt/appsdata/ ${ app . id } /data ` , // see also sftp.js
2021-08-20 09:19:44 -07:00
objectclass : [ 'user' ] ,
objectcategory : 'person' ,
cn : user . id ,
uid : ` ${ username } @ ${ appFqdn } ` , // for bind after search
uidNumber : uidNumber , // unix uid for ftp access
gidNumber : uidNumber // unix gid for ftp access
}
} ;
2019-03-18 21:15:50 -07:00
2021-08-20 09:19:44 -07:00
finalSend ( [ obj ] , req , res , next ) ;
2019-03-18 21:15:50 -07:00
}
2021-09-20 19:30:00 -07:00
async function verifyAppMailboxPassword ( serviceId , username , password ) {
assert . strictEqual ( typeof serviceId , 'string' ) ;
2020-12-03 12:14:04 -08:00
assert . strictEqual ( typeof username , 'string' ) ;
assert . strictEqual ( typeof password , 'string' ) ;
2021-09-20 19:30:00 -07:00
const pattern = serviceId === 'msa' ? 'MAIL_SMTP' : 'MAIL_IMAP' ;
2021-10-08 09:59:44 -07:00
const addonId = serviceId === 'msa' ? 'sendmail' : 'recvmail' ;
const appId = await addonConfigs . getAppIdByValue ( addonId , ` % ${ pattern } _PASSWORD ` , password ) ; // search by password because this is unique for each app
2021-08-19 21:39:27 -07:00
if ( ! appId ) throw new BoxError ( BoxError . NOT _FOUND ) ;
2020-12-03 12:14:04 -08:00
2021-10-08 09:59:44 -07:00
const result = await addonConfigs . get ( appId , addonId ) ;
2020-12-03 12:14:04 -08:00
2021-08-19 21:39:27 -07:00
if ( ! result . some ( r => r . name . endsWith ( ` ${ pattern } _USERNAME ` ) && r . value === username ) ) throw new BoxError ( BoxError . INVALID _CREDENTIALS ) ;
2020-12-03 12:14:04 -08:00
}
2021-11-01 17:17:47 -07:00
async function authenticateService ( serviceId , dn , req , res , next ) {
debug ( ` authenticateService: ${ req . dn . toString ( ) } (from ${ req . connection . ldap . id } ) ` ) ;
2018-02-08 18:49:27 -08:00
2021-11-01 17:17:47 -07:00
if ( ! dn . rdns [ 0 ] . attrs . cn ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ;
2020-12-03 13:35:50 -08:00
2021-11-01 17:17:47 -07:00
const email = dn . rdns [ 0 ] . attrs . cn . value . toLowerCase ( ) ;
2020-12-03 13:35:50 -08:00
const parts = email . split ( '@' ) ;
2021-11-01 17:17:47 -07:00
if ( parts . length !== 2 ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ;
2016-09-26 11:50:32 -07:00
2021-11-01 17:17:47 -07:00
const knownServices = [ 'msa' , 'imap' , 'pop3' , 'sieve' , 'sogo' ] ;
2021-10-08 10:15:48 -07:00
if ( ! knownServices . includes ( serviceId ) ) return next ( new ldap . OperationsError ( 'Invalid DN. Unknown service' ) ) ;
2018-12-06 21:08:19 -08:00
2021-08-17 15:45:57 -07:00
const [ error , domain ] = await safe ( mail . getDomain ( parts [ 1 ] ) ) ;
if ( error ) return next ( new ldap . OperationsError ( error . message ) ) ;
2021-11-01 17:17:47 -07:00
if ( ! domain ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ;
2018-01-22 20:35:08 +01:00
2021-11-01 17:17:47 -07:00
const serviceNeedsMailbox = serviceId === 'imap' || serviceId === 'sieve' || serviceId === 'pop3' || serviceId === 'sogo' ;
if ( serviceNeedsMailbox && ! domain . enabled ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ;
2018-01-22 20:35:08 +01:00
2021-10-03 23:59:06 -07:00
const [ getMailboxError , mailbox ] = await safe ( mail . getMailbox ( parts [ 0 ] , parts [ 1 ] ) ) ;
if ( getMailboxError ) return next ( new ldap . OperationsError ( getMailboxError . message ) ) ;
2021-11-01 19:53:38 -07:00
if ( serviceNeedsMailbox ) {
if ( ! mailbox || ! mailbox . active ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ;
if ( serviceId === 'pop3' && ! mailbox . enablePop3 ) return next ( new ldap . OperationsError ( 'POP3 is not enabled' ) ) ;
2021-10-03 23:59:06 -07:00
}
2018-01-22 20:35:08 +01:00
2021-11-01 19:53:38 -07:00
const [ appPasswordError ] = await safe ( verifyAppMailboxPassword ( serviceId , email , req . credentials || '' ) ) ;
if ( ! appPasswordError ) return res . end ( ) ; // validated as app
2018-01-29 19:29:04 +01:00
2024-01-03 14:51:00 +01:00
if ( appPasswordError . reason === BoxError . INVALID _CREDENTIALS ) return next ( new ldap . InvalidCredentialsError ( appPasswordError . message ) ) ;
2021-11-01 19:53:38 -07:00
if ( appPasswordError . reason !== BoxError . NOT _FOUND ) return next ( new ldap . OperationsError ( appPasswordError . message ) ) ;
2021-04-14 22:37:01 -07:00
2021-11-01 19:53:38 -07:00
if ( ! mailbox || ! mailbox . active ) return next ( new ldap . NoSuchObjectError ( dn . toString ( ) ) ) ; // user auth requires active mailbox
2021-08-17 15:45:57 -07:00
const [ verifyError , result ] = await safe ( verifyMailboxPassword ( mailbox , req . credentials || '' ) ) ;
2024-01-03 14:51:00 +01:00
if ( verifyError && verifyError . reason === BoxError . NOT _FOUND ) return next ( new ldap . NoSuchObjectError ( verifyError . message ) ) ;
if ( verifyError && verifyError . reason === BoxError . INVALID _CREDENTIALS ) return next ( new ldap . InvalidCredentialsError ( verifyError . message ) ) ;
2021-08-17 15:45:57 -07:00
if ( verifyError ) return next ( new ldap . OperationsError ( verifyError . message ) ) ;
2018-01-22 20:35:08 +01:00
2023-08-26 08:18:58 +05:30
eventlog . upsertLoginEvent ( result . ghost ? eventlog . ACTION _USER _LOGIN _GHOST : eventlog . ACTION _USER _LOGIN , AuditSource . MAIL , { mailboxId : email , userId : result . id , user : users . removePrivateFields ( result ) } ) ;
2021-06-01 09:35:20 -07:00
2021-08-17 15:45:57 -07:00
res . end ( ) ;
2016-05-29 17:25:23 -07:00
}
2021-11-01 17:17:47 -07:00
async function authenticateMail ( req , res , next ) {
2024-01-03 15:19:03 +01:00
if ( ! req . dn . rdns [ 1 ] . attrs . ou ) return next ( new ldap . NoSuchObjectError ( ) ) ;
2021-11-01 17:17:47 -07:00
await authenticateService ( req . dn . rdns [ 1 ] . attrs . ou . value . toLowerCase ( ) , req . dn , req , res , next ) ;
}
2022-03-25 14:14:26 -07:00
// https://ldapwiki.com/wiki/RootDSE / RFC 4512 - ldapsearch -x -h "${CLOUDRON_LDAP_SERVER}" -p "${CLOUDRON_LDAP_PORT}" -b "" -s base
2022-03-31 21:18:56 -07:00
// ldapjs seems to call this handler for everything when search === ''
async function maybeRootDSE ( req , res , next ) {
debug ( ` maybeRootDSE: requested with scope: ${ req . scope } dn: ${ req . dn . toString ( ) } ` ) ;
2022-03-25 14:14:26 -07:00
if ( req . scope !== 'base' ) return next ( new ldap . NoSuchObjectError ( ) ) ; // per the spec, rootDSE search require base scope
2022-03-31 21:18:56 -07:00
if ( ! req . dn || req . dn . toString ( ) !== '' ) return next ( new ldap . NoSuchObjectError ( ) ) ;
2022-03-25 14:14:26 -07:00
res . send ( {
dn : '' ,
attributes : {
objectclass : [ 'RootDSE' , 'top' , 'OpenLDAProotDSE' ] ,
supportedLDAPVersion : '3' ,
vendorName : 'Cloudron LDAP' ,
vendorVersion : '1.0.0'
}
} ) ;
res . end ( ) ;
}
2021-09-07 09:57:49 -07:00
async function start ( ) {
2023-10-01 13:26:43 +05:30
assert ( gServer === null , 'Already started' ) ;
2021-08-20 09:19:44 -07:00
const logger = {
2016-09-25 16:11:54 -07:00
trace : NOOP ,
debug : NOOP ,
info : debug ,
warn : debug ,
2020-08-02 11:43:18 -07:00
error : debug ,
fatal : debug
2016-09-25 16:11:54 -07:00
} ;
gServer = ldap . createServer ( { log : logger } ) ;
2016-05-12 13:36:53 -07:00
2019-04-25 13:10:52 +02:00
gServer . on ( 'error' , function ( error ) {
2023-04-16 10:49:59 +02:00
debug ( 'start: server error. %o' , error ) ;
2019-04-25 13:10:52 +02:00
} ) ;
2018-09-03 15:38:50 +02:00
gServer . search ( 'ou=users,dc=cloudron' , authenticateApp , userSearch ) ;
gServer . search ( 'ou=groups,dc=cloudron' , authenticateApp , groupSearch ) ;
gServer . bind ( 'ou=users,dc=cloudron' , authenticateApp , authenticateUser , authorizeUserForApp ) ;
2015-07-20 00:09:47 -07:00
2016-09-25 18:59:11 -07:00
// http://www.ietf.org/proceedings/43/I-D/draft-srivastava-ldap-mail-00.txt
2020-11-12 23:25:33 -08:00
gServer . search ( 'ou=mailboxes,dc=cloudron' , mailboxSearch ) ; // haraka (address translation), dovecot (LMTP), sogo (mailbox search)
2021-11-01 17:17:47 -07:00
gServer . bind ( 'ou=mailboxes,dc=cloudron' , async function ( req , res , next ) { // used for sogo only. this route happens only at sogo login time. after that it will use imap ldap route
await authenticateService ( 'sogo' , req . dn , req , res , next ) ;
} ) ;
2018-12-16 18:04:30 -08:00
gServer . search ( 'ou=mailaliases,dc=cloudron' , mailAliasSearch ) ; // haraka
gServer . search ( 'ou=mailinglists,dc=cloudron' , mailingListSearch ) ; // haraka
2016-09-25 18:59:11 -07:00
2021-09-20 19:30:00 -07:00
gServer . bind ( 'ou=imap,dc=cloudron' , authenticateMail ) ; // dovecot (IMAP auth)
gServer . bind ( 'ou=msa,dc=cloudron' , authenticateMail ) ; // haraka (MSA auth)
gServer . bind ( 'ou=sieve,dc=cloudron' , authenticateMail ) ; // dovecot (sieve auth)
2021-10-08 10:15:48 -07:00
gServer . bind ( 'ou=pop3,dc=cloudron' , authenticateMail ) ; // dovecot (pop3 auth)
2016-05-29 17:25:23 -07:00
2019-04-04 20:46:01 -07:00
gServer . bind ( 'ou=sftp,dc=cloudron' , authenticateSftp ) ; // sftp
2021-09-21 22:42:32 -07:00
gServer . search ( 'ou=sftp,dc=cloudron' , userSearchSftp ) ;
2019-03-18 21:15:50 -07:00
2018-09-03 15:38:50 +02:00
gServer . compare ( 'cn=users,ou=groups,dc=cloudron' , authenticateApp , groupUsersCompare ) ;
gServer . compare ( 'cn=admins,ou=groups,dc=cloudron' , authenticateApp , groupAdminsCompare ) ;
2017-10-24 01:35:35 +02:00
2016-05-12 13:20:57 -07:00
// this is the bind for addons (after bind, they might search and authenticate)
2017-11-15 18:07:10 -08:00
gServer . bind ( 'ou=addons,dc=cloudron' , function ( req , res /*, next */ ) {
2016-05-12 13:20:57 -07:00
debug ( 'addons bind: %s' , req . dn . toString ( ) ) ; // note: cn can be email or id
2016-05-11 14:26:34 -07:00
res . end ( ) ;
} ) ;
2016-05-12 13:20:57 -07:00
// this is the bind for apps (after bind, they might search and authenticate user)
2017-11-15 18:07:10 -08:00
gServer . bind ( 'ou=apps,dc=cloudron' , function ( req , res /*, next */ ) {
2015-09-25 21:17:48 -07:00
// TODO: validate password
2016-03-04 17:50:48 -08:00
debug ( 'application bind: %s' , req . dn . toString ( ) ) ;
2015-09-25 21:17:48 -07:00
res . end ( ) ;
} ) ;
2022-03-14 09:17:49 -07:00
// directus looks for the "DN" of the bind user
gServer . search ( 'ou=apps,dc=cloudron' , function ( req , res , next ) {
const obj = {
dn : req . dn . toString ( ) ,
} ;
finalSend ( [ obj ] , req , res , next ) ;
} ) ;
2022-03-31 21:18:56 -07:00
gServer . search ( '' , maybeRootDSE ) ; // when '', it seems the callback is called for everything else
2021-02-13 18:56:36 +01:00
// just log that an attempt was made to unknown route, this helps a lot during app packaging
gServer . use ( function ( req , res , next ) {
debug ( 'not handled: dn %s, scope %s, filter %s (from %s)' , req . dn ? req . dn . toString ( ) : '-' , req . scope , req . filter ? req . filter . toString ( ) : '-' , req . connection . ldap . id ) ;
return next ( ) ;
} ) ;
2021-09-07 09:57:49 -07:00
await util . promisify ( gServer . listen . bind ( gServer ) ) ( constants . LDAP _PORT , '0.0.0.0' ) ;
2015-07-20 00:09:47 -07:00
}
2015-09-14 10:59:05 -07:00
2021-09-07 09:57:49 -07:00
async function stop ( ) {
2021-12-10 17:48:36 +01:00
if ( ! gServer ) return ;
gServer . close ( ) ;
gServer = null ;
2015-09-14 10:59:05 -07:00
}