node-firebird 0.9.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ${{ matrix.os }}
8
+
9
+ strategy:
10
+ matrix:
11
+ os: [ubuntu-16.04, ubuntu-18.04, windows-2019, macos-10.15]
12
+ node-version: [10.x, 12.x, 14.x, 15.x]
13
+
14
+ steps:
15
+ - uses: actions/checkout@v1
16
+ with:
17
+ fetch-depth: 10
18
+
19
+ - name: Use Node.js ${{ matrix.node-version }}
20
+ uses: actions/setup-node@v1
21
+ with:
22
+ node-version: ${{ matrix.node-version }}
23
+
24
+ - name: Firebird install (Linux)
25
+ if: matrix.os == 'ubuntu-16.04' || matrix.os == 'ubuntu-18.04'
26
+ run: |
27
+ if [ `lsb_release -rs` = '16.04' ]
28
+ then
29
+ sudo apt-get install libtommath0
30
+ else
31
+ sudo apt-get install libtommath1
32
+ sudo ln -s /usr/lib/x86_64-linux-gnu/libtommath.so.1 /usr/lib/x86_64-linux-gnu/libtommath.so.0
33
+ fi
34
+ wget -nv -O Firebird-3.0.7.33374-0.amd64.tar.gz "https://github.com/FirebirdSQL/firebird/releases/download/R3_0_7/Firebird-3.0.7.33374-0.amd64.tar.gz"
35
+ tar xzvf Firebird-3.0.7.33374-0.amd64.tar.gz
36
+ (cd Firebird-3.0.7.33374-0.amd64; sudo ./install.sh -silent)
37
+ sudo usermod -a -G firebird `whoami`
38
+
39
+ - name: Firebird install (MacOS)
40
+ if: matrix.os == 'macos-10.15'
41
+ run: |
42
+ wget -nv -O Firebird-3.0.7-33374-x86_64.pkg "https://github.com/FirebirdSQL/firebird/releases/download/R3_0_7/Firebird-3.0.7-33374-x86_64.pkg"
43
+ sudo installer -verbose -pkg "Firebird-3.0.7-33374-x86_64.pkg" -target /
44
+
45
+ - name: Firebird install (Windows)
46
+ if: matrix.os == 'windows-2019'
47
+ shell: cmd
48
+ run: |
49
+ set FB_ZIP=Firebird-3.0.7.33374-1_x64.zip
50
+ powershell Invoke-WebRequest "https://github.com/FirebirdSQL/firebird/releases/download/R3_0_7/$env:FB_ZIP" -OutFile "$env:FB_ZIP"
51
+ 7z x -oC:\Firebird %FB_ZIP%
52
+
53
+ - name: Build
54
+ shell: bash
55
+ run: |
56
+ npm ci
57
+
58
+ - name: Test (Linux)
59
+ if: matrix.os == 'ubuntu-16.04' || matrix.os == 'ubuntu-18.04'
60
+ run: |
61
+ sg firebird -c "npx nyc npm test"
62
+
63
+ - name: Test (MacOS)
64
+ if: matrix.os == 'macos-10.15'
65
+ run: |
66
+ sudo mkdir `pwd`/tmp-node-fb
67
+ sudo chmod 777 `pwd`/tmp-node-fb
68
+ export ISC_USER=sysdba
69
+ export ISC_PASSWORD=masterkey
70
+ export NODE_FB_TEST_TMP_DIR=`pwd`/tmp-node-fb
71
+ npx nyc npm test
72
+
73
+ - name: Test (Windows)
74
+ if: matrix.os == 'windows-2019'
75
+ shell: cmd
76
+ run: |
77
+ set PATH=C:\Firebird;%PATH%
78
+ call npx nyc npm test
package/README.md CHANGED
@@ -66,7 +66,8 @@ options.password = 'masterkey';
66
66
  options.lowercase_keys = false; // set to true to lowercase keys
67
67
  options.role = null; // default
68
68
  options.pageSize = 4096; // default when creating database
69
-
69
+ options.pageSize = 4096; // default when creating database
70
+ options.retryConnectionInterval = 1000; // reconnect interval in case of connection drop
70
71
  ```
71
72
 
72
73
  ### Classic
@@ -183,7 +184,7 @@ Firebird.attach(options, function(err, db) {
183
184
  });
184
185
  ```
185
186
 
186
- ### Reading Blobs (Aasynchronous)
187
+ ### Reading Blobs (Asynchronous)
187
188
 
188
189
  ```js
189
190
  Firebird.attach(options, function(err, db) {
@@ -513,7 +514,7 @@ This is why you should use **Firebird 2.5** server at least.
513
514
  ### Firebird 3.0 Support
514
515
 
515
516
  Firebird new wire protocol is not supported yet so
516
- for Firebird 3.0 you need to add the following in firebird.conf according to Firebird documentation
517
+ for Firebird 3.0 you need to add the following in firebird.conf according to Firebird 3 release notes
517
518
  <https://firebirdsql.org/file/documentation/release_notes/html/en/3_0/rnfb30-security-new-authentication.html>
518
519
 
519
520
  ```bash
@@ -522,6 +523,21 @@ WireCrypt = Disabled
522
523
  UserManager = Legacy_UserManager
523
524
  ```
524
525
 
526
+ Firebird 4 wire protocol is not supported yet so
527
+ for Firebird 4.0 you need to add the following in firebird.conf according to Firebird release notes
528
+ <https://firebirdsql.org/file/documentation/release_notes/html/en/4_0/rlsnotes40.html#rnfb40-config-srp256>
529
+
530
+ ```bash
531
+ AuthServer = Srp256, Srp, Legacy_Auth
532
+ WireCrypt = Disabled
533
+ UserManager = Legacy_UserManager
534
+ ```
535
+
536
+ Please read also Authorization with Firebird 2.5 client library from Firebird 4 migration guide
537
+ <https://ib-aid.com/download/docs/fb4migrationguide.html#_authorization_with_firebird_2_5_client_library_fbclient_dll>
538
+
539
+
540
+
525
541
  ## Contributors
526
542
 
527
543
  - Henri Gourvest, <https://github.com/hgourvest>
package/lib/index.d.ts CHANGED
@@ -9,9 +9,23 @@ declare module 'node-firebird' {
9
9
  type SimpleCallback = (err: any) => void;
10
10
  type SequentialCallback = (row: any, index: number) => void;
11
11
 
12
+ export const AUTH_PLUGIN_LEGACY: string;
13
+ export const AUTH_PLUGIN_SRP: string;
14
+ export const AUTH_PLUGIN_SRP256: string;
15
+
16
+ export const WIRE_CRYPT_ENABLE: number;
17
+ export const WIRE_CRYPT_DISABLE: number;
18
+
19
+ /** A transaction sees changes done by uncommitted transactions. */
12
20
  export const ISOLATION_READ_UNCOMMITTED: number[];
21
+ /** A transaction sees only data committed before the statement has been executed. */
13
22
  export const ISOLATION_READ_COMMITED: number[];
23
+ /** A transaction sees during its lifetime only data committed before the transaction has been started. */
14
24
  export const ISOLATION_REPEATABLE_READ: number[];
25
+ /**
26
+ * This is the strictest isolation level, which enforces transaction serialization.
27
+ * Data accessed in the context of a serializable transaction cannot be accessed by any other transaction.
28
+ */
15
29
  export const ISOLATION_SERIALIZABLE: number[];
16
30
  export const ISOLATION_READ_COMMITED_READ_ONLY: number[];
17
31
 
@@ -46,6 +60,7 @@ declare module 'node-firebird' {
46
60
  lowercase_keys?: boolean;
47
61
  role?: string;
48
62
  pageSize?: number;
63
+ retryConnectionInterval?: number;
49
64
  }
50
65
 
51
66
  export interface SvcMgrOptions extends Options {
@@ -54,12 +69,12 @@ declare module 'node-firebird' {
54
69
 
55
70
  export interface ConnectionPool {
56
71
  get(callback: DatabaseCallback): void;
57
- destroy(): void;
72
+ destroy(callback?: SimpleCallback): void;
58
73
  }
59
74
 
60
75
  export function attach(options: Options, callback: DatabaseCallback): void;
61
76
  export function attach(options: SvcMgrOptions, callback: ServiceManagerCallback): void;
62
- export function escape(value: any, protocolVersion: number /*PROTOCOL_VERSION13*/): string;
77
+ export function escape(value: any, protocolVersion?: number /*PROTOCOL_VERSION13*/): string;
63
78
  export function create(options: Options, callback: DatabaseCallback): void;
64
79
  export function attachOrCreate(options: Options, callback: DatabaseCallback): void;
65
80
  export function pool(max: number, options: Options): ConnectionPool;
@@ -207,18 +222,18 @@ declare module 'node-firebird' {
207
222
  backup(options: BackupOptions, callback: ReadableCallback): void;
208
223
  nbackup(options: BackupOptions, callback: ReadableCallback): void;
209
224
  restore(options: NRestoreOptions, callback: ReadableCallback): void;
210
- nrestore(options, callback): void;
225
+ nrestore(options: any, callback: Function): void;
211
226
  setDialect(db: string, dialect: 1 | 3, callback: ReadableCallback): void;
212
- setSweepinterval(db: string, interval: number, callback): void; // gfix -h INTERVAL
213
- setCachebuffer(db: string, nbpages, callback: ReadableCallback): void; // gfix -b NBPAGES
227
+ setSweepinterval(db: string, interval: number, callback: Function): void; // gfix -h INTERVAL
228
+ setCachebuffer(db: string, nbpages: any, callback: ReadableCallback): void; // gfix -b NBPAGES
214
229
  BringOnline(db: string, callback: ReadableCallback): void; // gfix -o
215
230
  Shutdown(db: string, kind: ShutdownKind, delay: number, mode: ShutdownMode, callback: ReadableCallback): void; // server version >= 2.0
216
231
  Shutdown(db: string, kind: ShutdownKind, delay: number, callback: ReadableCallback): void; // server version < 2.0
217
232
  setShadow(db: string, val: boolean, callback: ReadableCallback): void;
218
233
  setForcewrite(db: string, val: boolean, callback: ReadableCallback): void; // gfix -write
219
234
  setReservespace(db: string, val: boolean, callback: ReadableCallback): void; // true: gfix -use reserve, false: gfix -use full
220
- setReadonlyMode(db: string, callback: ReadableCallback): void; // gfix -mode read_only
221
- setReadwriteMode(db: string, callback: ReadableCallback): void; // gfix -mode read_write
235
+ setReadonlyMode(db: string, callback: ReadableCallback): void; // gfix -mode read_only
236
+ setReadwriteMode(db: string, callback: ReadableCallback): void; // gfix -mode read_write
222
237
  validate(options: ValidateOptions, callback: ReadableCallback): void; // gfix -validate
223
238
  commit(db: string, transactid: number, callback: ReadableCallback): void; // gfix -commit
224
239
  rollback(db: string, transactid: number, callback: ReadableCallback): void;
package/lib/index.js CHANGED
@@ -11,7 +11,9 @@ var
11
11
  BitSet = serialize.BitSet,
12
12
  messages = require('./messages.js'),
13
13
  crypt = require('./unix-crypt.js'),
14
- path = require('path');
14
+ path = require('path'),
15
+ srp = require('./srp'),
16
+ BigInt = require('big-integer');
15
17
 
16
18
  if (typeof(setImmediate) === 'undefined') {
17
19
  global.setImmediate = function(cb) {
@@ -274,7 +276,9 @@ const
274
276
  ptype_rpc = 2, // Simple remote procedure call
275
277
  ptype_batch_send = 3, // Batch sends, no asynchrony
276
278
  ptype_out_of_band = 4, // Batch sends w/ out of band notification
277
- ptype_lazy_send = 5; // Deferred packets delivery;
279
+ ptype_lazy_send = 5, // Deferred packets delivery;
280
+ ptype_mask = 0xFF, // Mask - up to 255 types of protocol
281
+ pflag_compress = 0x100; // Turn on compression if possible
278
282
 
279
283
  const SUPPORTED_PROTOCOL = [
280
284
  [PROTOCOL_VERSION10, ARCHITECTURE_GENERIC, ptype_rpc, ptype_batch_send, 1],
@@ -790,11 +794,25 @@ const
790
794
  DEFAULT_PAGE_SIZE = 4096,
791
795
  DEFAULT_SVC_NAME = 'service_mgr';
792
796
 
793
- const AUTH_PLUGIN_LEGACY = 'Legacy_Auth';
797
+ const AUTH_PLUGIN_LEGACY = 'Legacy_Auth',
798
+ AUTH_PLUGIN_SRP = 'Srp';
799
+ // AUTH_PLUGIN_SRP256 = 'Srp256';
794
800
 
795
801
  const
796
- AUTH_PLUGIN_LIST = [AUTH_PLUGIN_LEGACY],
797
- LEGACY_AUTH_SALT = '9z';
802
+ // AUTH_PLUGIN_LIST = [AUTH_PLUGIN_SRP256, AUTH_PLUGIN_SRP, AUTH_PLUGIN_LEGACY],
803
+ AUTH_PLUGIN_LIST = [AUTH_PLUGIN_SRP, AUTH_PLUGIN_LEGACY],
804
+ // AUTH_PLUGIN_SRP_LIST = [AUTH_PLUGIN_SRP256, AUTH_PLUGIN_SRP],
805
+ AUTH_PLUGIN_SRP_LIST = [AUTH_PLUGIN_SRP],
806
+ LEGACY_AUTH_SALT = '9z',
807
+ WIRE_CRYPT_DISABLE = 0,
808
+ WIRE_CRYPT_ENABLE = 1;
809
+
810
+ exports.AUTH_PLUGIN_LEGACY = AUTH_PLUGIN_LEGACY;
811
+ exports.AUTH_PLUGIN_SRP = AUTH_PLUGIN_SRP;
812
+ // exports.AUTH_PLUGIN_SRP256 = AUTH_PLUGIN_SRP256;
813
+
814
+ exports.WIRE_CRYPT_DISABLE = WIRE_CRYPT_DISABLE;
815
+ exports.WIRE_CRYPT_ENABLE = WIRE_CRYPT_ENABLE;
798
816
 
799
817
  exports.ISOLATION_READ_UNCOMMITTED = ISOLATION_READ_UNCOMMITTED;
800
818
  exports.ISOLATION_READ_COMMITED = ISOLATION_READ_COMMITED;
@@ -1284,7 +1302,9 @@ SQLParamBool.prototype.calcBlr = function(blr) {
1284
1302
  ***************************************/
1285
1303
 
1286
1304
  function isError(obj) {
1287
- return (obj instanceof Object && obj.status);
1305
+ return Boolean(
1306
+ obj != null && typeof obj === "object" && !Array.isArray(obj) && obj.status
1307
+ );
1288
1308
  }
1289
1309
 
1290
1310
  function doCallback(obj, callback) {
@@ -2885,17 +2905,40 @@ Pool.prototype.check = function() {
2885
2905
  return self;
2886
2906
  };
2887
2907
 
2888
- Pool.prototype.destroy = function() {
2908
+ Pool.prototype.destroy = function(callback) {
2889
2909
  var self = this;
2910
+
2911
+ var connectionCount = this.internaldb.length;
2912
+
2913
+ if (connectionCount === 0 && callback) {
2914
+ callback();
2915
+ }
2916
+
2917
+ function detachCallback(err) {
2918
+ if (err) {
2919
+ if (callback) {
2920
+ callback(err);
2921
+ }
2922
+ return;
2923
+ }
2924
+
2925
+ connectionCount--;
2926
+ if (connectionCount === 0 && callback) {
2927
+ callback();
2928
+ }
2929
+ }
2930
+
2890
2931
  this.internaldb.forEach(function(db) {
2891
- if (db.connection._pooled === false)
2932
+ if (db.connection._pooled === false) {
2933
+ detachCallback();
2892
2934
  return;
2935
+ }
2893
2936
  // check if the db is not free into the pool otherwise user should manual detach it
2894
2937
  var _db_in_pool = self.pooldb.indexOf(db);
2895
2938
  if (_db_in_pool !== -1) {
2896
2939
  self.pooldb.splice(_db_in_pool, 1);
2897
2940
  db.connection._pooled = false;
2898
- db.detach();
2941
+ db.detach(detachCallback);
2899
2942
  }
2900
2943
  });
2901
2944
  };
@@ -2918,6 +2961,7 @@ var Connection = exports.Connection = function (host, port, callback, options, d
2918
2961
  this._detachAuto;
2919
2962
  this._socket = net.createConnection(port, host);
2920
2963
  this._pending = [];
2964
+ this._isOpened = false;
2921
2965
  this._isClosed = false;
2922
2966
  this._isDetach = false;
2923
2967
  this._isUsed = false;
@@ -2925,6 +2969,8 @@ var Connection = exports.Connection = function (host, port, callback, options, d
2925
2969
  this.options = options;
2926
2970
  this._bind_events(host, port, callback);
2927
2971
  this.error;
2972
+ this._retry_connection_id;
2973
+ this._retry_connection_interval = options.retryConnectionInterval || 1000;
2928
2974
  this._max_cached_query = options.maxCachedQuery || -1;
2929
2975
  this._cache_query = options.cacheQuery?{}:null;
2930
2976
  this._messageFile = options.messageFile || path.join(__dirname, 'firebird.msg');
@@ -2950,10 +2996,11 @@ exports.Connection.prototype._bind_events = function(host, port, callback) {
2950
2996
 
2951
2997
  self._socket.on('close', function() {
2952
2998
 
2953
- self._isClosed = true;
2954
-
2955
- if (self._isDetach)
2999
+ if (!self._isOpened || self._isDetach) {
2956
3000
  return;
3001
+ }
3002
+
3003
+ self._isOpened = false;
2957
3004
 
2958
3005
  if (!self.db) {
2959
3006
  if (callback)
@@ -2961,10 +3008,9 @@ exports.Connection.prototype._bind_events = function(host, port, callback) {
2961
3008
  return;
2962
3009
  }
2963
3010
 
2964
- setImmediate(function() {
3011
+ self._retry_connection_id = setTimeout(function() {
3012
+ self._socket.removeAllListeners();
2965
3013
  self._socket = null;
2966
- self._msg = null;
2967
- self._blr = null;
2968
3014
 
2969
3015
  var ctx = new Connection(host, port, function(err) {
2970
3016
  ctx.connect(self.options, function(err) {
@@ -2988,8 +3034,10 @@ exports.Connection.prototype._bind_events = function(host, port, callback) {
2988
3034
  }, self.db);
2989
3035
  });
2990
3036
 
3037
+ Object.assign(self, ctx);
3038
+
2991
3039
  }, self.options, self.db);
2992
- });
3040
+ }, self._retry_connection_interval);
2993
3041
 
2994
3042
  });
2995
3043
 
@@ -3255,22 +3303,77 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
3255
3303
  protocolArchitecture: data.readInt(),
3256
3304
  protocolMinimumType: data.readInt(),
3257
3305
  pluginName: '',
3258
- authData: ''
3306
+ authData: '',
3307
+ sessionKey: ''
3259
3308
  };
3260
3309
 
3261
3310
  accept.protocolMinimumType = accept.protocolMinimumType & 0xFF;
3311
+ //accept.compress = (accept.acceptType & pflag_compress) !== 0; // TODO Handle zlib compression
3262
3312
  if (accept.protocolVersion < 0) {
3263
3313
  accept.protocolVersion = (accept.protocolVersion & FB_PROTOCOL_MASK) | FB_PROTOCOL_FLAG;
3264
3314
  }
3265
3315
 
3266
3316
  if (r === op_cond_accept || r === op_accept_data) {
3267
- var d = data.readString(DEFAULT_ENCODING);
3317
+ var d = new BlrReader(data.readArray());
3268
3318
  accept.pluginName = data.readString(DEFAULT_ENCODING);
3269
3319
  var is_authenticated = data.readInt();
3270
3320
  var keys = data.readString(DEFAULT_ENCODING); // keys
3271
3321
 
3272
3322
  if (is_authenticated === 0) {
3273
- if (accept.pluginName === AUTH_PLUGIN_LEGACY) {
3323
+ if (cnx.options.pluginName && cnx.options.pluginName !== accept.pluginName) {
3324
+ doError(new Error('Server don\'t accept plugin : ' + cnx.options.pluginName + ', but support : ' + accept.pluginName), callback);
3325
+ }
3326
+
3327
+ if (AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1) {
3328
+ var crypto = {
3329
+ Srp: 'sha1',
3330
+ Srp256: 'sha256'
3331
+ };
3332
+ accept.srpAlgo = crypto[accept.pluginName];
3333
+
3334
+ // TODO : Fallback Srp256 to Srp ?
3335
+ /*if (!d.buffer) {
3336
+ cnx.sendOpContAuth(
3337
+ cnx.clientKeys.public.toString(16),
3338
+ DEFAULT_ENCODING,
3339
+ accept.pluginName
3340
+ );
3341
+
3342
+ return cb(new Error('login'));
3343
+ }*/
3344
+
3345
+ // Check buffer contains salt
3346
+ var saltLen = d.buffer.readUInt16LE(0);
3347
+ if (saltLen > 32 * 2) {
3348
+ console.log('salt to long'); // TODO : Throw error
3349
+ }
3350
+
3351
+ // Check buffer contains key
3352
+ var keyLen = d.buffer.readUInt16LE(saltLen + 2);
3353
+ var keyStart = saltLen + 4;
3354
+ if (d.buffer.length - keyStart !== keyLen) {
3355
+ console.log('key error'); // TODO : Throw error
3356
+ }
3357
+
3358
+ // Server keys
3359
+ cnx.serverKeys = {
3360
+ salt: d.buffer.slice(2, saltLen + 2).toString('utf8'),
3361
+ public: BigInt(d.buffer.slice(keyStart, d.buffer.length).toString('utf8'), 16)
3362
+ };
3363
+
3364
+ var proof = srp.clientProof(
3365
+ cnx.options.user.toUpperCase(),
3366
+ cnx.options.password,
3367
+ cnx.serverKeys.salt,
3368
+ cnx.clientKeys.public,
3369
+ cnx.serverKeys.public,
3370
+ cnx.clientKeys.private,
3371
+ accept.srpAlgo
3372
+ );
3373
+
3374
+ accept.authData = proof.authData.toString(16);
3375
+ accept.sessionKey = proof.clientSessionKey;
3376
+ } else if (accept.pluginName === AUTH_PLUGIN_LEGACY) {
3274
3377
  accept.authData = crypt.crypt(cnx.options.password, LEGACY_AUTH_SALT).substring(2);
3275
3378
  } else {
3276
3379
  return cb(new Error('Unknow auth plugin : ' + accept.pluginName));
@@ -3280,7 +3383,35 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
3280
3383
  accept.sessionKey = '';
3281
3384
  }
3282
3385
  }
3386
+
3283
3387
  return cb(undefined, accept);
3388
+ case op_cont_auth:
3389
+ var d = new BlrReader(data.readArray());
3390
+ var pluginName = data.readString(DEFAULT_ENCODING);
3391
+ data.readString(DEFAULT_ENCODING); // plist
3392
+ data.readString(DEFAULT_ENCODING); // pkey
3393
+
3394
+ if (!cnx.options.pluginName) {
3395
+ if (cnx.accept.pluginName === pluginName) {
3396
+ // Erreur plugin not able to connect
3397
+ return cb(new Error("Unable to connect with plugin " + cnx.accept.pluginName));
3398
+ }
3399
+
3400
+ if (pluginName === AUTH_PLUGIN_LEGACY) { // Fallback to LegacyAuth
3401
+ cnx.accept.pluginName = pluginName;
3402
+ cnx.accept.authData = crypt.crypt(cnx.options.password, LEGACY_AUTH_SALT).substring(2);
3403
+
3404
+ cnx.sendOpContAuth(
3405
+ cnx.accept.authData,
3406
+ DEFAULT_ENCODING,
3407
+ pluginName
3408
+ );
3409
+
3410
+ return {error: new Error('login')};
3411
+ }
3412
+ }
3413
+
3414
+ return data.accept;
3284
3415
  default:
3285
3416
  return cb(new Error('Unexpected:' + r));
3286
3417
  }
@@ -3366,6 +3497,20 @@ function parseOpResponse(data, response, cb) {
3366
3497
  }
3367
3498
  }
3368
3499
 
3500
+ Connection.prototype.sendOpContAuth = function(authData, authDataEnc, pluginName) {
3501
+ var msg = this._msg;
3502
+ msg.pos = 0;
3503
+
3504
+ msg.addInt(op_cont_auth);
3505
+ msg.addString(authData, authDataEnc);
3506
+ msg.addString(pluginName, DEFAULT_ENCODING)
3507
+ msg.addString(AUTH_PLUGIN_LIST.join(','), DEFAULT_ENCODING);
3508
+ // msg.addInt(0); // p_list
3509
+ msg.addInt(0); // keys
3510
+
3511
+ this._socket.write(msg.getData());
3512
+ }
3513
+
3369
3514
  Connection.prototype._queueEvent = function(callback){
3370
3515
  var self = this;
3371
3516
 
@@ -3380,7 +3525,7 @@ Connection.prototype._queueEvent = function(callback){
3380
3525
  };
3381
3526
 
3382
3527
  Connection.prototype.connect = function (options, callback) {
3383
- var pluginName = AUTH_PLUGIN_LEGACY;
3528
+ var pluginName = options.manager ? AUTH_PLUGIN_LEGACY : options.pluginName || AUTH_PLUGIN_LIST[0]; // TODO Srp for service
3384
3529
  var msg = this._msg;
3385
3530
  var blr = this._blr;
3386
3531
 
@@ -3393,13 +3538,19 @@ Connection.prototype.connect = function (options, callback) {
3393
3538
  blr.addString(CNCT_plugin_name, pluginName, DEFAULT_ENCODING);
3394
3539
  blr.addString(CNCT_plugin_list, AUTH_PLUGIN_LIST.join(','), DEFAULT_ENCODING);
3395
3540
 
3396
- if (pluginName === AUTH_PLUGIN_LEGACY) {
3397
- var specificData = crypt.crypt(options.password, LEGACY_AUTH_SALT).substring(2);
3541
+ var specificData = '';
3542
+ if (AUTH_PLUGIN_SRP_LIST.indexOf(pluginName) > -1) {
3543
+ this.clientKeys = srp.clientSeed();
3544
+ specificData = this.clientKeys.public.toString(16);
3545
+ blr.addMultiblockPart(CNCT_specific_data, specificData, DEFAULT_ENCODING);
3546
+ } else if (pluginName === AUTH_PLUGIN_LEGACY) {
3547
+ specificData = crypt.crypt(options.password, LEGACY_AUTH_SALT).substring(2);
3398
3548
  blr.addMultiblockPart(CNCT_specific_data, specificData, DEFAULT_ENCODING);
3399
3549
  } else {
3400
- throw new Error('Invalide auth plugin');
3550
+ doError(new Error('Invalide auth plugin \'' + pluginName + '\''), callback);
3551
+ return;
3401
3552
  }
3402
- blr.addBytes([CNCT_client_crypt, 4, 0, 0, 0, 0]); // WireCrypt = Disabled
3553
+ blr.addBytes([CNCT_client_crypt, 4, WIRE_CRYPT_DISABLE, 0, 0, 0]); // WireCrypt = Disabled
3403
3554
  blr.addString(CNCT_user, os.userInfo().username || 'Unknown', DEFAULT_ENCODING);
3404
3555
  blr.addString(CNCT_host, os.hostname(), DEFAULT_ENCODING);
3405
3556
  blr.addBytes([CNCT_user_verification, 0]);
@@ -3456,13 +3607,16 @@ Connection.prototype.attach = function (options, callback, db) {
3456
3607
  blr.addByte(isc_dpb_version1);
3457
3608
  blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING);
3458
3609
  blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING);
3459
- if (this.accept.protocolVersion < PROTOCOL_VERSION13) {
3460
- if (this.accept.protocolVersion === PROTOCOL_VERSION10) {
3461
- blr.addString(isc_dpb_password, password, DEFAULT_ENCODING);
3462
- } else {
3463
- blr.addString(isc_dpb_password_enc, crypt.crypt(password, LEGACY_AUTH_SALT).substring(2), DEFAULT_ENCODING);
3610
+ if (options.password && !this.accept.authData) {
3611
+ if (this.accept.protocolVersion < PROTOCOL_VERSION13) {
3612
+ if (this.accept.protocolVersion === PROTOCOL_VERSION10) {
3613
+ blr.addString(isc_dpb_password, password, DEFAULT_ENCODING);
3614
+ } else {
3615
+ blr.addString(isc_dpb_password_enc, crypt.crypt(password, LEGACY_AUTH_SALT).substring(2), DEFAULT_ENCODING);
3616
+ }
3464
3617
  }
3465
3618
  }
3619
+
3466
3620
  if (role)
3467
3621
  blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING);
3468
3622
 
@@ -3522,6 +3676,7 @@ Connection.prototype.detach = function (callback) {
3522
3676
  msg.addInt(0); // Database Object ID
3523
3677
 
3524
3678
  self._queueEvent(function(err, ret) {
3679
+ clearTimeout(self._retry_connection_id);
3525
3680
  delete(self.dbhandle);
3526
3681
  if (callback)
3527
3682
  callback(err, ret);
package/lib/serialize.js CHANGED
@@ -111,11 +111,11 @@ BlrWriter.prototype.addBuffer = function (b) {
111
111
 
112
112
  BlrWriter.prototype.addString2 = function (c, s, encoding) {
113
113
  this.addByte(c);
114
-
114
+
115
115
  var len = Buffer.byteLength(s, encoding);
116
116
  if (len > MAX_STRING_SIZE* MAX_STRING_SIZE)
117
117
  throw new Error('blr string is too big');
118
-
118
+
119
119
  this.ensure(len + 2);
120
120
  this.buffer.writeUInt16LE(len, this.pos);
121
121
  this.pos += 2;
@@ -135,6 +135,7 @@ BlrWriter.prototype.addMultiblockPart = function (c, s, encoding) {
135
135
  this.addByte(toWrite + 1);
136
136
  this.addByte(step);
137
137
 
138
+ this.ensure(toWrite);
138
139
  buff.copy(this.buffer, this.pos, step * 254, (step * 254) + toWrite);
139
140
 
140
141
  step++;
package/lib/srp.js ADDED
@@ -0,0 +1,281 @@
1
+ var BigInt = require('big-integer'),
2
+ crypto = require('crypto');
3
+
4
+ const SRP_KEY_SIZE = 128,
5
+ SRP_KEY_MAX = BigInt('340282366920938463463374607431768211456'), // 1 << SRP_KEY_SIZE
6
+ SRP_SALT_SIZE = 32;
7
+
8
+ const DEBUG = false;
9
+ const DEBUG_PRIVATE_KEY = BigInt('84316857F47914F838918D5C12CE3A3E7A9B2D7C9486346809E9EEFCE8DE7CD4259D8BE4FD0BCC2D259553769E078FA61EE2977025E4DA42F7FD97914D8A33723DFAFBC00770B7DA0C2E3778A05790F0C0F33C32A19ED88A12928567749021B3FD45DCD1CE259C45325067E3DDC972F87867349BA82C303CCCAA9B207218007B', 16);
10
+
11
+ /**
12
+ * Prime values.
13
+ *
14
+ * @type {{g: (bigInt.BigInteger), k: (bigInt.BigInteger), N: (bigInt.BigInteger)}}
15
+ */
16
+ const PRIME = {
17
+ N: BigInt('E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7', 16),
18
+ g: BigInt(2),
19
+ k: BigInt('1277432915985975349439481660349303019122249719989')
20
+ };
21
+
22
+ /**
23
+ * Generate a client key pair.
24
+ *
25
+ * @param a bigInt.BigInteger Client private key.
26
+ * @returns {{private: bigInt.BigInteger, public: bigInt.BigInteger}}
27
+ */
28
+ exports.clientSeed = function(a = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) {
29
+ var A = PRIME.g.modPow(a, PRIME.N);
30
+
31
+ dump('a', a);
32
+ dump('A', A);
33
+
34
+ return {
35
+ public: A,
36
+ private: a
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Generate a server key pair.
42
+ *
43
+ * @param user string Connection username.
44
+ * @param password string Connection password.
45
+ * @param salt bigInt.BigInteger Connection salt.
46
+ * @param b bigInt.BigInteger Server private key.
47
+ * @returns {{private: bigInt.BigInteger, public: bigInt.BigInteger}}
48
+ */
49
+ exports.serverSeed = function(user, password, salt, b = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) {
50
+ var v = getVerifier(user, password, salt);
51
+ var gb = PRIME.g.modPow(b, PRIME.N);
52
+ var kv = PRIME.k.multiply(v).mod(PRIME.N);
53
+ var B = kv.add(gb).mod(PRIME.N);
54
+
55
+ dump('v', v);
56
+ dump('b', b);
57
+ dump('gb', b);
58
+ dump('kv', v);
59
+ dump('B', B);
60
+
61
+ return {
62
+ public: B,
63
+ private: b
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Server session secret.
69
+ *
70
+ * @param user string Connection username.
71
+ * @param password string Connection password.
72
+ * @param salt bigInt.BigInteger Connection salt.
73
+ * @param A bigInt.BigInteger Client public key.
74
+ * @param B bigInt.BigInteger Server public key.
75
+ * @param b bigInt.BigInteger Server private key.
76
+ * @returns {bigInt.BigInteger}
77
+ */
78
+ exports.serverSession = function(user, password, salt, A, B, b) {
79
+ var u = getScramble(A, B);
80
+ var v = getVerifier(user, password, salt);
81
+ var vu = v.modPow(u, PRIME.N);
82
+ var Avu = A.multiply(vu).mod(PRIME.N);
83
+ var sessionSecret = Avu.modPow(b, PRIME.N);
84
+ var K = getHash('sha1', toBuffer(sessionSecret));
85
+
86
+ dump('server sessionSecret', sessionSecret);
87
+ dump('server K', K);
88
+
89
+ return BigInt(K, 16);
90
+ };
91
+
92
+ /**
93
+ * M = H(H(N) xor H(g), H(I), s, A, B, K)
94
+ */
95
+ exports.clientProof = function(user, password, salt, A, B, a, hashAlgo) {
96
+ var K = clientSession(user, password, salt, A, B, a);
97
+ var n1, n2;
98
+
99
+ n1 = toBigInt(getHash('sha1', toBuffer(PRIME.N)));
100
+ n2 = toBigInt(getHash('sha1', toBuffer(PRIME.g)));
101
+
102
+ dump('n1', n1);
103
+ dump('n2', n2);
104
+
105
+ n1 = n1.modPow(n2, PRIME.N);
106
+ n2 = toBigInt(getHash('sha1', user));
107
+ var M = toBigInt(getHash(hashAlgo, toBuffer(n1), toBuffer(n2), salt, toBuffer(A), toBuffer(B), toBuffer(K)));
108
+
109
+ dump('n1-2', n1);
110
+ dump('n2-2', n2);
111
+ dump('proof:M', M);
112
+
113
+ return {
114
+ clientSessionKey: K,
115
+ authData: M,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Pad hex string.
121
+ */
122
+ function hexPad(hex) {
123
+ if (hex.length % 2 !== 0) {
124
+ hex = '0' + hex;
125
+ }
126
+
127
+ return hex;
128
+ }
129
+ exports.hexPad = hexPad;
130
+
131
+ /**
132
+ * Pad key with SRP_KEY_SIZE.
133
+ *
134
+ * @param n BigInt Key to pad.
135
+ * @returns Buffer
136
+ */
137
+ function pad(n) {
138
+ var buff = Buffer.from(hexPad(n.toString(16)), 'hex');
139
+
140
+ if (buff.length > SRP_KEY_SIZE) {
141
+ buff = buff.slice(buff.length - SRP_KEY_SIZE, buff.length);
142
+ }
143
+
144
+ return buff;
145
+ }
146
+
147
+ /**
148
+ * Scramble keys.
149
+ *
150
+ * @param A bigInt.BigInteger Client public key.
151
+ * @param B bigInt.BigInteger Server public key.
152
+ * @returns {bigInt.BigInteger}
153
+ */
154
+ function getScramble(A, B) {
155
+ return BigInt(getHash('sha1', pad(A), pad(B)), 16);
156
+ }
157
+
158
+ /**
159
+ * Client session secret.
160
+ *
161
+ * Both: u = H(A, B)
162
+ * User: x = H(s, p) (user enters password)
163
+ * User: S = (B - kg^x) ^ (a + ux) (computes session key)
164
+ * User: K = H(S)
165
+ *
166
+ * @param user string Connection username.
167
+ * @param password string Connection password.
168
+ * @param salt bigInt.BigInteger Connection salt.
169
+ * @param A bigInt.BigInteger Client public key.
170
+ * @param B bigInt.BigInteger Server public key.
171
+ * @param a bigInt.BigInteger Client private key.
172
+ */
173
+ function clientSession(user, password, salt, A, B, a) {
174
+ var u = getScramble(A, B);
175
+ var x = getUserHash(user, salt, password);
176
+ var gx = PRIME.g.modPow(x, PRIME.N);
177
+ var kgx = PRIME.k.multiply(gx).mod(PRIME.N);
178
+ var diff = B.subtract(kgx).mod(PRIME.N);
179
+
180
+ if (diff.lesser(0)) {
181
+ diff = diff.add(PRIME.N);
182
+ }
183
+
184
+ var ux = u.multiply(x).mod(PRIME.N);
185
+ var aux = a.add(ux).mod(PRIME.N);
186
+ var sessionSecret = diff.modPow(aux, PRIME.N);
187
+ var K = toBigInt(getHash('sha1', toBuffer(sessionSecret)));
188
+
189
+ dump('B', B);
190
+ dump('u', u);
191
+ dump('x', x);
192
+ dump('gx', gx);
193
+ dump('kgx', kgx);
194
+ dump('diff', diff);
195
+ dump('ux', ux);
196
+ dump('aux', aux);
197
+ dump('sessionSecret', sessionSecret);
198
+ dump('sessionKey(K)', K);
199
+
200
+ return K;
201
+ }
202
+
203
+ /**
204
+ * Compute user hash.
205
+ *
206
+ * @param user string Connection username.
207
+ * @param salt bigInt.BigInteger Connection salt.
208
+ * @param password string Connection password.
209
+ * @returns {bigInt.BigInteger}
210
+ */
211
+ function getUserHash(user, salt, password) {
212
+ var hash1 = getHash('sha1', user.toUpperCase(), ':', password);
213
+ var hash2 = getHash('sha1', salt, toBuffer(hash1));
214
+
215
+ return toBigInt(hash2);
216
+ }
217
+
218
+ /**
219
+ * Verifier of user hash.
220
+ *
221
+ * @param user string Connection username.
222
+ * @param password string Connection password.
223
+ * @param salt bigInt.BigInteger Connection salt.
224
+ * @returns {bigInt.BigInteger}
225
+ */
226
+ function getVerifier(user, password, salt) {
227
+ return PRIME.g.modPow(getUserHash(user, salt, password), PRIME.N);
228
+ }
229
+
230
+ /**
231
+ * Hash data and return hex string.
232
+ *
233
+ * @param algo string Algorithm to use.
234
+ * @param data any[] Data to hash.
235
+ * @returns {string}
236
+ */
237
+ function getHash(algo, ...data) {
238
+ var hash = crypto.createHash(algo);
239
+
240
+ for (var d of data) {
241
+ hash.update(d);
242
+ }
243
+
244
+ return hash.digest('hex');
245
+ }
246
+
247
+ /**
248
+ * Convert BigInt to buffer.
249
+ *
250
+ * @param bigInt
251
+ * @returns {*}
252
+ */
253
+ function toBuffer(bigInt) {
254
+ return Buffer.from(BigInt.isInstance(bigInt) ? hexPad(bigInt.toString(16)) : bigInt, 'hex');
255
+ }
256
+
257
+ /**
258
+ * Convert hex buffer or string to BigInt.
259
+ *
260
+ * @param hex
261
+ * @returns {bigInt.BigInteger}
262
+ */
263
+ function toBigInt(hex) {
264
+ return BigInt(Buffer.isBuffer(hex) ? hex.toString('hex') : hex, 16);
265
+ }
266
+
267
+ /**
268
+ * Dump value in debug mode.
269
+ *
270
+ * @param key
271
+ * @param value
272
+ */
273
+ function dump(key, value) {
274
+ if (DEBUG) {
275
+ if (BigInt.isInstance(value)) {
276
+ value = value.toString(16);
277
+ }
278
+
279
+ console.log(key + '=' + value);
280
+ }
281
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-firebird",
3
- "version": "0.9.9",
3
+ "version": "1.0.0",
4
4
  "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
5
  "keywords": [
6
6
  "firebird",
@@ -33,6 +33,7 @@
33
33
  "test": "mocha"
34
34
  },
35
35
  "dependencies": {
36
+ "big-integer": "^1.6.48",
36
37
  "long": "^4.0.0"
37
38
  },
38
39
  "devDependencies": {
@@ -1,47 +0,0 @@
1
- # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
- # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3
-
4
- name: Node.js Package
5
-
6
- on:
7
- release:
8
- types: [created]
9
-
10
- jobs:
11
- build:
12
- runs-on: ubuntu-latest
13
- steps:
14
- - uses: actions/checkout@v2
15
- - uses: actions/setup-node@v1
16
- with:
17
- node-version: 12
18
- - run: npm ci
19
- - run: npm test
20
-
21
- publish-npm:
22
- needs: build
23
- runs-on: ubuntu-latest
24
- steps:
25
- - uses: actions/checkout@v2
26
- - uses: actions/setup-node@v1
27
- with:
28
- node-version: 12
29
- registry-url: https://registry.npmjs.org/
30
- - run: npm ci
31
- - run: npm publish
32
- env:
33
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
34
-
35
- publish-gpr:
36
- needs: build
37
- runs-on: ubuntu-latest
38
- steps:
39
- - uses: actions/checkout@v2
40
- - uses: actions/setup-node@v1
41
- with:
42
- node-version: 12
43
- registry-url: https://npm.pkg.github.com/
44
- - run: npm ci
45
- - run: npm publish
46
- env:
47
- NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
package/git DELETED
File without changes