-
Notifications
You must be signed in to change notification settings - Fork 7
/
Daemon.xs
2595 lines (2196 loc) · 72.2 KB
/
Daemon.xs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//////////////////////////////////////////////////////////////////////////////
//
// Daemon.cpp
// Win32::Daemon Perl extension main source file
//
// Copyright (c) 1998-2008 Dave Roth
// Courtesy of Roth Consulting
// http://www.roth.net/
//
// This file may be copied or modified only under the terms of either
// the Artistic License or the GNU General Public License, which may
// be found in the Perl 5.0 source kit.
//
// 2008.03.24 :Date
// 20080324 :Version
//////////////////////////////////////////////////////////////////////////////
// Enable Win32::Daemon callback support
#define ENABLE_CALLBACKS
// Enable MS Visual Studio 2005's secure stdlib
// The _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES macro defined with a value of 1
// will swap out a secured version of stdlib functions. These perform buffer overrun
// checking and such.
// define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
// As an alternative you can keep the depricated function calls and block out any
// security warnings by defining the _CRT_SECURE_NO_DEPRECATE macro.
#define _CRT_SECURE_NO_DEPRECATE
#define WIN32_LEAN_AND_MEAN
#ifdef __BORLANDC__
typedef wchar_t wctype_t; /* in tchar.h, but unavailable unless _UNICODE */
#endif
#include <windows.h>
#include <tchar.h>
#include <wtypes.h>
#include <stdio.h> // Gurusamy's right, Borland is brain damaged!
#include <math.h> // Gurusamy's right, MS is brain damaged!
#include <time.h>
// Use headers that define the security stuff
#include <lmaccess.h>
#include <lmserver.h>
#include <lmapibuf.h>
#include <LMERR.H> // For the NERR_Succes macro
#include "XS_Win32Perl.h"
// #include <preWin32Perl.h>
// #include <Win32Perl.h>
#include "constant.h"
#include "CWinStation.hpp"
#include "ServiceThread.hpp"
#ifdef ENABLE_CALLBACKS
#include "CCallbackList.hpp"
#include "CCallbackTimer.hpp"
#endif // ENABLE_CALLBACKS
#include "daemon.h"
#define SET_SERVICE_BITS_LIBRARY TEXT( "AdvApi32.dll" )
#define SET_SERVICE_BITS_FUNCTION TEXT( "SetServiceBits" )
/*----------------------- M I S C F U N C T I O N S -------------------*/
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int My_SetServiceBits( SERVICE_STATUS_HANDLE hService, DWORD dwServiceBits, BOOL bSetBitsOn, BOOL bUpdateImmediately )
{
int iResult = 0;
HINSTANCE hInstance;
DWORD dwMask = USER_SERVICE_BITS_MASK; // Default mask. These bits are not reserved by MS. We can use these w/o problems w/MS products.
hInstance = LoadLibrary( SET_SERVICE_BITS_LIBRARY );
if( NULL != hInstance )
{
typedef BOOL (CALLBACK *fSetServiceBits) ( SERVICE_STATUS_HANDLE hServiceGoo, DWORD dwBits, BOOL bBitsOn, BOOL bUpdate );
fSetServiceBits pSetServiceBits = NULL;
pSetServiceBits= (fSetServiceBits) GetProcAddress( hInstance, SET_SERVICE_BITS_FUNCTION );
if( NULL != pSetServiceBits )
{
// Note:
// We clear all user defined bits before applying our bits. If this sucks
// then use something like Win32::Lanman. We only want to track our bits
// and not mess with others.
// First clear all user defined bits...
// My_SetServiceBits() will mask out the user bits from the passed in DWORD.
(*pSetServiceBits)( ghService, 0xFFFFFFFF, FALSE, FALSE );
// Mask those lovely bits!
dwServiceBits &= dwMask;
iResult = (*pSetServiceBits)( hService, dwServiceBits, bSetBitsOn, bUpdateImmediately );
}
}
return( iResult );
}
BOOL GetProcessSid( HANDLE hProcess, SID *pSid, DWORD dwSidBufferSize )
{
BOOL fResult = FALSE;
HANDLE hToken = NULL;
if( ( NULL == pSid ) || ( sizeof( SID ) > dwSidBufferSize ) )
{
return( FALSE );
}
// Manually get the process token since we need it for more than just
// calling into SetPrivilege()...
if( OpenProcessToken(
hProcess,
TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY
| TOKEN_QUERY_SOURCE,
&hToken ) )
{
// Now that you have the token for this process, you want to set
// the SE_DEBUG_NAME privilege.
// TODO:
// We may want to not do this here. Only if we show the
// service. Otherwise this may give us too many privileges that
// we may not want to have
SetPrivilege( hToken, SE_DEBUG_NAME, TRUE );
// Determine the SID for the current process. We need
// to give this SID permissions to access the
// desktop object...
fResult = GetSidFromToken( hToken, pSid, dwSidBufferSize );
CloseHandle( hToken );
}
return( fResult );
}
////////////////////////////////////////////////////////////////////////////
// ResetCallbackTimer()
// Stops or changes the callback timer.
// Passing in nothing simply stops/starts the timer (depending upon the state)
// Passing in 0 stops the timer.
// Passing in any positive value starts the timer and sets that value as callback timeout.
//
// Default pass in value is -1 (if nothing is specified).
//
/*
BOOL ResetCallbackTimer( UINT uintTimeoutValue )
{
//
// If the callback timer is active then kill it
//
if( 0 != gpuintCallbackTimerID )
{
KillTimer( NULL, gpuintCallbackTimerID );
gpuintCallbackTimerID = 0;
if( -1 == uintTimeoutValue )
{
return( TRUE );
}
}
//
// Are we changing the value? A 0 means to simply stop
// the timer. Otherwise change the timeout value...
//
if( 0 == uintTimeoutValue ) return( TRUE );
if( 0 <= (int) uintTimeoutValue )
{
guintCallbackTimer = uintTimeoutValue;
}
if( 0 != guintCallbackTimer )
{
gpuintCallbackTimerID = SetTimer( NULL, CALLBACK_RUNNING_TIMER, guintCallbackTimer, (TIMERPROC) NULL );
}
return( TRUE );
}
*/
////////////////////////////////////////////////////////////////////////////
// Fill in a SID buffer from the specified token
BOOL GetSidFromToken( HANDLE hToken, SID *pSid, DWORD dwSidBufferSize )
{
BOOL fResult = FALSE;
if( NULL != hToken )
{
PTOKEN_USER pTokenUserStruct = NULL;
DWORD dwLength = 0;
if( FALSE == GetTokenInformation(
hToken,
TokenUser,
pTokenUserStruct,
0,
&dwLength ) )
{
pTokenUserStruct = (PTOKEN_USER) new BYTE [ dwLength ];
if( NULL != pTokenUserStruct )
{
ZeroMemory( pTokenUserStruct, dwLength );
if( GetTokenInformation(
hToken,
TokenUser,
pTokenUserStruct,
dwLength,
&dwLength ) )
{
if( FALSE != IsValidSid( pTokenUserStruct->User.Sid ) )
{
DWORD dwSidLength;
dwSidLength = GetLengthSid( pTokenUserStruct->User.Sid );
if( dwSidLength <= dwSidBufferSize )
{
fResult = CopySid( dwSidLength, pSid, pTokenUserStruct->User.Sid );
}
}
}
delete [] pTokenUserStruct;
}
}
}
return( fResult );
}
////////////////////////////////////////////////////////////////////////////
// The SetPrivilege function will accept a handle to a token, a
// privilege, and a flag to either enable/disable that privilege. The
// function will attempt to perform the desired action upon the token
// returning TRUE if it succeeded, or FALSE if it failed.
BOOL SetPrivilege( HANDLE hToken, const char *pszPrivilege, BOOL bSetFlag )
{
TOKEN_PRIVILEGES structPriv, structPrevPriv;
LUID Luid;
DWORD dwStructSize = sizeof( structPrevPriv );
BOOL fResult = FALSE;
BOOL fCloseToken = FALSE;
// If no token is specified assume that the process token is desired.
if( 0 == hToken )
{
OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY
| TOKEN_QUERY_SOURCE,
&hToken );
fCloseToken = TRUE;
}
if( 0 != hToken )
{
ZeroMemory( &structPrevPriv, sizeof( structPrevPriv ) );
ZeroMemory( &structPriv, sizeof( structPriv ) );
// Grab the LUID for the request privilege.
if( LookupPrivilegeValue( "", pszPrivilege, &Luid ) )
{
// Set up basic information for a call.
// You want to retrieve the current privileges
// of the token under concern before you can modify them.
structPriv.PrivilegeCount = 1;
structPriv.Privileges[0].Luid = Luid;
structPriv.Privileges[0].Attributes = ( bSetFlag )? SE_PRIVILEGE_ENABLED : 0;
// You need to acquire the current privileges first
fResult = ( FALSE != AdjustTokenPrivileges(
hToken,
0,
&structPriv,
sizeof( structPriv ),
&structPrevPriv,
&dwStructSize ) );
}
if( FALSE != fCloseToken )
{
CloseHandle( hToken );
}
}
return( fResult );
}
////////////////////////////////////////////////////////////////////////////
// Fill the specified text buffer with the text representation of the
// specified SID.
// NOTE: The buffer must have enough memory to represent the SID.
void TextFromSid( LPTSTR pszBuffer, SID *pSid )
{
TCHAR szTemp[ 100 ];
int iCount;
PUCHAR puCount;
SID_IDENTIFIER_AUTHORITY *pSia;
if( FALSE == IsValidSid( pSid ) )
{
return;
}
pSia = GetSidIdentifierAuthority( pSid );
// Create the string version of the SID
// We begin by S (for SID) - the revision level - the authority
// identifier. Note that we are assuming that the Authority ID is in
// the sixth element (element #5 starting at 0) in Authority Identifier
// structure. Currently as of this writing SID strings do not display
// the other values in the pSiz strucutre. They are normally something
// like: {0,0,0,0,0,5} where the last byte contains a value and the others
// are zero.
iCount = wsprintf( pszBuffer, TEXT( "S-%d-%d" ), pSid->Revision, (DWORD) pSia->Value[5] );
puCount = GetSidSubAuthorityCount( pSid );
for( DWORD dwTemp = 0; dwTemp < *puCount; dwTemp++ )
{
wsprintf( szTemp, TEXT( "-%d" ), (DWORD) *( GetSidSubAuthority( pSid, dwTemp ) ) );
_tcscat( pszBuffer, szTemp );
}
}
////////////////////////////////////////////////////////////////////////////
// Load the user's profile hive into the HKEY_USERS Registry key.
BOOL LoadProfile( SID *pSid )
{
BOOL fResult = FALSE;
TCHAR szUserProfilePath[ 1024 ];
TCHAR szSid[ 64 ];
HKEY hKey = NULL;
#ifdef DEBUG
TCHAR szDebugText[ 1024 ];
#endif // DEBUG
ZeroMemory( szSid, sizeof( szSid ) );
TextFromSid( szSid, pSid );
_tcscpy( szUserProfilePath, REG_KEY_USER_LOCAL_PROFILE );
_tcscat( szUserProfilePath, szSid );
#ifdef DEBUG
wsprintf( szDebugText, TEXT( "[LoadProfile] Loading profile for %s" ), szSid );
ALERT( szDebugText );
#endif
// Check to see if we already have the profile loaded
if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_USERS,
szSid,
0,
KEY_READ,
&hKey ) )
{
RegCloseKey( hKey );
ALERT( "[LoadProfile] Profile already loaded." );
return( TRUE );
}
// If we got here then the profile is not loaded, let's load it.
// First open the Registry key where profile info is held
if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE,
szUserProfilePath,
0,
KEY_READ,
&hKey ) )
{
TCHAR szCentralPath[ MAX_PATH + 1 ];
TCHAR szLocalPath[ MAX_PATH + 1 ];
DWORD dwLength;
DWORD dwType;
HANDLE hToken = NULL;
_tcscpy( szCentralPath, TEXT( "" ) );
_tcscpy( szLocalPath, TEXT( "" ) );
// We need to be granted Restore Name privileges. Otherwise
// we won't be able to load the profile
if( OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY
| TOKEN_QUERY_SOURCE,
&hToken ) )
{
// Now that you have the token for this process, you want to set
// the SE_RESTORE_NAME privilege.
SetPrivilege( hToken, SE_RESTORE_NAME, TRUE );
}
dwLength = sizeof( szCentralPath );
if( ERROR_SUCCESS == RegQueryValueEx(
hKey,
REG_VALUE_USER_CENTRAL_PROFILE_PATH,
0,
&dwType,
(LPBYTE) szCentralPath,
&dwLength
) )
{
// Expand environment variables if needed
if( REG_EXPAND_SZ == dwType )
{
TCHAR szBuffer[ MAX_PATH + 1 ];
_tcscpy( szBuffer, szCentralPath );
ExpandEnvironmentStrings( szBuffer, szCentralPath, sizeof( szCentralPath ) );
}
}
dwLength = sizeof( szLocalPath );
if( ERROR_SUCCESS == RegQueryValueEx(
hKey,
REG_VALUE_USER_LOCAL_PROFILE_PATH,
0,
&dwType,
(LPBYTE) szLocalPath,
&dwLength
) )
{
// Expand environment variables if needed
if( REG_EXPAND_SZ == dwType )
{
TCHAR szBuffer[ MAX_PATH + 1 ];
_tcscpy( szBuffer, szLocalPath );
ExpandEnvironmentStrings( szBuffer, szLocalPath, sizeof( szLocalPath ) );
}
}
if( 0 != _tcscmp( szCentralPath, TEXT( "" ) ) )
{
TCHAR szProfilePath[ MAX_PATH ];
_tcscpy( szProfilePath, szCentralPath );
_tcscat( szProfilePath, TEXT( "\\" ) );
_tcscat( szProfilePath, USER_PROFILE_HIVE_NAME );
// Try to load the profile. This may fail if the
// path is a UNC to another machine or for other
// reasons. RegLoadKey() seems to only load local
// copies of profiles--possibly to prevent corruption
// if the net goes down.
// If this is the case we may want to copy the file
// over.
if( ERROR_SUCCESS == RegLoadKey(
HKEY_USERS,
szSid,
szProfilePath
) )
{
fResult = TRUE;
#ifdef DEBUG
wsprintf( szDebugText, TEXT( "[LoadProfile] Loaded %s" ), szProfilePath );
ALERT( szDebugText );
#endif
}
else if( 0 != _tcscmp( szLocalPath, TEXT( "" ) ) )
{
TCHAR szDestination[ MAX_PATH ];
_tcscpy( szDestination, szLocalPath );
// determine if it is a file or directory, WinNT 4.0 it is a
// directory
// WinNT 3.51 it is a file
//
if( FILE_ATTRIBUTE_DIRECTORY == ( GetFileAttributes( szProfilePath )
& FILE_ATTRIBUTE_DIRECTORY ) )
{
_tcscat( szProfilePath, TEXT( "\\" ) );
_tcscat( szProfilePath, USER_PROFILE_HIVE_NAME );
}
// Copy the profile hive file from the origin location to the
// local local cached profile
CopyFile( szProfilePath, szDestination, TRUE );
#ifdef DEBUG
wsprintf( szDebugText, TEXT( "[LoadProfile] Copied path from %s to %s" ), szProfilePath, szDestination );
ALERT( szDebugText );
#endif
}
}
// Were we successful?
if( ( FALSE == fResult ) && ( 0 != _tcscmp( szLocalPath, TEXT( "" ) ) ) )
{
// No, try loading the local path.
TCHAR szProfilePath[ MAX_PATH ];
_tcscpy( szProfilePath, szLocalPath );
// determine if it is a file or directory, WinNT 4.0 it is a
// directory
// WinNT 3.51 it is a file
//
if( FILE_ATTRIBUTE_DIRECTORY == ( GetFileAttributes( szProfilePath )
& FILE_ATTRIBUTE_DIRECTORY ) )
{
_tcscat( szProfilePath, TEXT( "\\" ) );
_tcscat( szProfilePath, USER_PROFILE_HIVE_NAME );
}
// Try to load the profile.
if( ERROR_SUCCESS == RegLoadKey(
HKEY_USERS,
szSid,
szProfilePath
) )
{
fResult = TRUE;
#ifdef DEBUG
wsprintf( szDebugText, TEXT( "[LoadProfile] Loaded %s" ), szProfilePath );
ALERT( szDebugText );
#endif
}
}
RegCloseKey( hKey );
}
return( fResult );
}
//////////////////////////////////////////////////////////////////
// Store the service's description in the Registry. Win2k does
// this for you but we need to be backward compatible.
BOOL StoreServiceDescription( LPCTSTR pszMachine, LPCTSTR pszServiceName, LPCTSTR pszDescription )
{
HKEY hRoot = HKEY_LOCAL_MACHINE;
HKEY hKey = NULL;
BOOL fFlag = TRUE;
BOOL fResult = FALSE;
if( 0 != _tcscmp( TEXT( "" ), pszMachine ) )
{
fFlag = ( ERROR_SUCCESS == RegConnectRegistry( pszMachine, HKEY_LOCAL_MACHINE, &hRoot ) );
}
if( TRUE == fFlag )
{
TCHAR szBuffer[ 75 ];
_stprintf( szBuffer, REGISTRY_SERVICE_PATH TEXT( "\\%s" ), pszServiceName );
if( ERROR_SUCCESS == RegOpenKeyEx( hRoot, szBuffer, 0, KEY_SET_VALUE, &hKey ) )
{
DWORD dwSize = (DWORD)_tcslen( pszDescription ) * sizeof( TCHAR );
fResult = ( ERROR_SUCCESS == RegSetValueEx( hKey, REGISTRY_SERVICE_KEYWORD_DESCRIPTION, 0, REG_SZ, (LPBYTE) pszDescription, dwSize ) );
}
RegCloseKey( hKey );
}
if( 0 != _tcscmp( TEXT( "" ), pszMachine ) )
{
RegCloseKey( hRoot );
}
return( fResult );
}
#ifdef ENABLE_CALLBACKS
//////////////////////////////////////////////////////////////////
// The idea here is that when a state change occurs because the
// SCM has submitted a command (stop, pause, start, interrogate, etc)
// we can call this function. If callbacks have been enabled then
// this function will execute the Perl callback if a callback routine
// has been provided. Otherwise we just pass through.
//
BOOL ProcessStateChange( pTHX_ DWORD dwCommand, HV* pHvContext )
{
BOOL fMakeContextHash = FALSE;
PVOID pSvSubroutine = gCallback.Get( dwCommand );
//
// The SERVICE_CONTROL_TIMER has replaced SERVICE_RUNNING. Since
// SERVICE_RUNNING is easily confused with another constant with teh
// same value we have renamed this SERVICE_CONTROL_RUNNING for clarity.
// Regardless, SERVICE_CONTROL_RUNNING is being depreciated and replaced
// with SERVICE_CONTROL_TIMER to better indicate why the callback is
// occuring.
// For legacy purposes:
// 1) Registering a "running" callback will still work
// 2) Registering a "timer" callback is preferred instead of "running"
// 3) Registering both will result in only a "timer" callback
//
// Now let's check if this is the timer command AND if the script has
// registered only the "running" callback...
if( SERVICE_CONTROL_TIMER == dwCommand )
{
//
// Code currently changes 3) to result in "running" for legacy support
// if code called Callback() passing in only one catchall subroutine reference
//
PVOID pSvTemp;
pSvTemp = gCallback.Get( SERVICE_CONTROL_RUNNING );
if( NULL != pSvTemp )
{
pSvSubroutine = pSvTemp;
dwCommand = SERVICE_CONTROL_RUNNING;
}
}
#ifdef aTHX
if( NULL == aTHX )
{
return( FALSE );
}
#endif // aTHX
if( NULL == pSvSubroutine )
{
return( FALSE );
}
ALERT( "ProcessStateChange: checking for context hash..." );
// If there is no context hash provided then make one...
if( NULL == pHvContext )
{
ALERT( "ProcessStateChange: Creating a new context hash" );
pHvContext = newHV();
}
else
{
// If we don't create the context HV then increase its ref count because
// later we will decrease it. If we created the HV then the decrease will
// auto destroy it.
ALERT( "ProcessStateChange: Increasing ref count on existing context hash" );
SvREFCNT_inc( (SV*) pHvContext );
}
ALERT( "ProcessStateChange: Storing value into context hash" );
HASH_STORE_IV( pHvContext, KEYWORD_CALLBACK_COMMAND_NAME, dwCommand );
ALERT( "ProcessStateChange: Checking for a valid subroutine" );
if( SVt_PVCV == SvTYPE( (SV*) pSvSubroutine ) )
{
ALERT( "About to call into a Perl routine..." );
// Call into the Perl subroutine
// Push onto the stack the hash context
CallPerlRoutine( aTHX_ (CV*) pSvSubroutine,
dwCommand,
pHvContext );
ALERT( "Back from calling into a Perl routine..." );
// Callback into the subroutine.
// The subroutine should look like:
//
// sub EventCallback
// {
// my( $Event, $Context ) = @_;
// # Process the event
// Win32::Daemon::State( $NewState );
// return;
// }
//
// =================== OR ====================
// sub EventCallback
// {
// my( $Event, $Context ) = @_;
// # Process the event
// return( $NewState );
// }
}
// Decrease the context HV reference counter. If we created the HV then it is
// unloaded at this time. Otherwise we have previously increased the count so
// this just decreases it back to where it started at the beginning of this
// routine.
SvREFCNT_dec( (SV*) pHvContext );
return( TRUE );
}
#endif // ENABLE_CALLBACKS
#ifdef ENABLE_CALLBACKS
////////////////////////////////////////////////////////////////////////////
// CallPerlRoutine()
// This will callback into a specified Perl subroutine passing in any
// SVs that are passed into the function.
// Nothing is returned.
//
// void CallPerlRoutine( pTHX_ CV* pPerlSubroutine, int iTotalParams, ... )
void CallPerlRoutine( pTHX_ CV* pPerlSubroutine, DWORD dwCommand, HV* pHvContext )
{
SV *pSv = NULL;
int iReturnedItems = 0;
// Make sure that a routine was passed in AND that it is really a code reference
if( ( NULL == pPerlSubroutine ) || ( SVt_PVCV != SvTYPE( (SV*) pPerlSubroutine ) ) )
{
return;
}
// Declare necessary vars...
dSP;
// Begin a new scope...
ENTER;
// Start mortal stack. All mortals after this point will be put onto this stack
// which will be freeded up later with FREETMPS
SAVETMPS;
// Remember (or push) the current position of the stack...
ALERT( "CallPerlRoutine: Pushing parameters onto the perl stack" );
PUSHMARK( sp );
XPUSHs( sv_2mortal( newSViv( (IV) dwCommand ) ) );
XPUSHs( (SV*) newRV_inc( (SV*) pHvContext ) );
// Mark the end of arguments on the stack...
PUTBACK;
ALERT( "CallPerlRoutine: Done: Pushed parameters onto the perl stack" );
TCHAR szBuff[ 256 ];
sprintf( szBuff, "CallPerlRoutine: Calling into Perl for command: 0x%04x.\n", dwCommand );
ALERT( szBuff );
ALERT( "CallPerlRoutine: Calling into the perl routine now..." );
iReturnedItems = perl_call_sv( (SV*) pPerlSubroutine, G_SCALAR );
ALERT( "CallPerlRoutine: ...Back from calling the perl routine." );
// Begin the process of unwinding the return stack...
SPAGAIN;
if( iReturnedItems )
{
SV *pSvReturn = POPs;
//
// Check if the return value is an integer
//
if( SvIOK( pSvReturn ) )
{
//
// Any integer return value is a new state value
// so update the service's state. Specify waithint value of 0
// and error value of 0xFFFFFFFF to use defaults.
//
DWORD dwNewState = (DWORD)SvIV( pSvReturn );
UpdateServiceStatus( dwNewState, 0, 0xFFFFFFFF );
}
//
// NOTE: the while() look predecriments iReturnedItems to
// accomodate the new state value taken off of the stack
//
while( --iReturnedItems )
{
// Pop an SV off of the return stack...
POPs;
}
}
// We are done here so put back the stack pointer...
PUTBACK;
// Unwind and destroy any mortals that are on our temp stack...
FREETMPS;
// Leave our little scope...
LEAVE;
}
#endif // ENABLE_CALLBACKS
////////////////////////////////////////////////////////////////////////////
// DispatchThreadMessage()
// This is called for every message that the thread message queue receives.
// This is used instead of DispatchMessage() since there is no window available.
//
void DispatchThreadMessage( MSG *pMsg )
{
// Retrieve the Perl context...
dTHX;
#ifdef _DEBUG
TCHAR szBuffer[ 1024 ];
wsprintf( szBuffer, TEXT( "Servicing Thread Message Queue: Message = 0x%04x; aTHX = 0x%04x." ), pMsg->message, aTHX );
ALERT( szBuffer );
#endif // _DEBUG
BOOL fCallbackState = FALSE;
switch( pMsg->message )
{
case WM_DAEMON_STATE_CHANGE:
// We have a state change!
// Notice we are using gPerlObject here! Therefore we MUST be in
// callback mode!
//
// First reset the timer. Don't pass in any value so that it just
// temporarily pauses the timer. The next time you call it without
// any params it will start it again. This prevents queuing up
// timeout messages if the callback takes time.
ALERT( TEXT( "...processing WM_DAEMON_STATE_CHANGE message\n" ) )
fCallbackState = gCallbackTimer.QueryState();
if( fCallbackState )
{
gCallbackTimer.Stop();
}
ProcessStateChange( aTHX_ (DWORD) pMsg->wParam, gpHvContext );
if( fCallbackState )
{
gCallbackTimer.Start();
}
break;
case WM_TIMER:
//
// You get here when the callback timeout value has been exceeded. This
// simply means that it is time to callback into the Perl script to give
// the script a chance to process anything it needs to.
// The script sees this event as a "SERVICE_RUNNING" event.
//
//
// First reset the timer. Don't pass in any value so that it just
// temporarily pauses the timer. The next time you call it without
// any params it will start it again. This prevents queuing up
// timeout messages if the callback takes time.
ALERT( TEXT( "...processing WM_TIMER message (heartbeat callback)\n" ) )
fCallbackState = gCallbackTimer.QueryState();
if( fCallbackState )
{
gCallbackTimer.Stop();
}
ProcessStateChange( aTHX_ (DWORD) SERVICE_CONTROL_TIMER, gpHvContext );
if( fCallbackState )
{
gCallbackTimer.Start();
}
break;
default:
ALERT( "...Default handler has been invoked." );
}
}
////////////////////////////////////////////////////////////////////////////
// TimerProc()
// This is called by a Win32 Timer every time the timer's timeout value is
// reached. This is used to determine when to callback into the Perl script
// to allow it for processing. When the callback into Perl occurs it does
// so indicating the SERVICE_RUNNING state.
//
void CALLBACK TimerProc(
HWND hWnd, // handle of CWnd that called SetTimer
UINT nMsg, // WM_TIMER
UINT nIDEvent, // timer identification
DWORD dwTime // system time
)
{
MSG sMsg;
ZeroMemory( &sMsg, sizeof( sMsg ) );
sMsg.hwnd = hWnd;
sMsg.lParam = 0;
sMsg.message = WM_TIMER;
// sMsg.pt = 0;
sMsg.time = dwTime;
sMsg.wParam = nIDEvent;
DispatchThreadMessage( &sMsg );
}
#ifdef _DEBUG
//////////////////////////////////////////////////////////////////
//
//
HANDLE CreateLog( LPCTSTR pszPath )
{
HANDLE hFile = 0;
ZeroMemory( gszDebugOutputPath, sizeof( gszDebugOutputPath ) );
_tcsncpy( gszDebugOutputPath, pszPath, sizeof( gszDebugOutputPath ) - 1 );
if( 0 != ghLogFile )
{
CloseHandle( ghLogFile );
ghLogFile = 0;
}
if( _tcscmp( TEXT( "" ), gszDebugOutputPath ) != 0 )
{
ghLogFile = CreateFile( gszDebugOutputPath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
NULL );
if( 0 == ghLogFile )
{
_tcscpy( gszDebugOutputPath, TEXT( "" ) );
}
}
return( ghLogFile );
}
#endif // _DEBUG
/* =============== DLL Specific Functions =================== */
//////////////////////////////////////////////////////////////////
#if defined(__cplusplus)
extern "C"
#endif
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
BOOL fResult = TRUE;
DWORD dwSidBuferSize = MAX_SID_SIZE;
// Fetch the OS version number...
ZeroMemory( &gsOSVerInfo, sizeof( gsOSVerInfo ) );
gsOSVerInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
GetVersionEx( &gsOSVerInfo );
switch( fdwReason )
{
case DLL_PROCESS_ATTACH:
ghDLL = hinstDLL;
#ifdef ENABLE_CALLBACKS
gfCallbackMode = FALSE;
// gpPerlObject = NULL;
gpHvContext = NULL;
#endif // ENABLE_CALLBACKS
gdwLastError = 0;
gdwServiceErrorState = NO_ERROR;
ghService = 0;
ghServiceThread = 0;
gServiceThreadId = 0;
gServiceMainThreadID = 0;
#ifdef ENABLE_CALLBACKS
//
// Setup the callback timer
//
gCallbackTimer.SetMessageID( CALLBACK_TIMER_ID );
gCallbackTimer = DEFAULT_CALLBACK_TIMER;
#endif // ENABLE_CALLBACKS
gMainThreadId = GetCurrentThreadId();
gdwLastControlMessage = SERVICE_CONTROL_NONE;
gdwTimeoutState = SERVICE_START_PENDING;
gdwControlsAccepted = 0;
switch( gsOSVerInfo.dwMajorVersion )
{
default:
// We have Windows Vista
// The following constants only work on Vista and higher:
// SERVICE_ACCEPT_PRESHUTDOWN
//
#ifdef SERVICE_CONTROL_PRESHUTDOWN
gdwControlsAccepted |= SERVICE_ACCEPT_PRESHUTDOWN;
#endif // SERVICE_CONTROL_PRESHUTDOWN
case 5:
// We have Windows 2000 or XP
// The following constants only work on Win2k and higher:
// SERVICE_ACCEPT_PARAMCHANGE
// SERVICE_ACCEPT_NETBINDCHANGE
//
gdwControlsAccepted |= SERVICE_ACCEPT_PARAMCHANGE
| SERVICE_ACCEPT_NETBINDCHANGE;
case 4:
case 3:
case 2:
case 1:
case 0:
// NT 4.0
gdwControlsAccepted |= SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_PAUSE_CONTINUE
| SERVICE_ACCEPT_SHUTDOWN;
}
gdwServiceType = SERVICE_WIN32_OWN_PROCESS
| SERVICE_INTERACTIVE_PROCESS;
gdwServiceBits = 0;
gdwLastError = 0;
gpSid = NULL;
// Set the state to 0. This way we know when the service thread actually
// starts because it will set the state to SERVICE_START_PENDING
gdwState = 0;
ZeroMemory( &gServiceStatus, sizeof( gServiceStatus ) );