Commit 8572f66b authored by Jondy Zhao's avatar Jondy Zhao

netreport: fix the problem that domain-user can't see net-drives

parent f53fc13f
......@@ -18,7 +18,7 @@ Here are example of src/Makefile:
PYTHON = /opt/slapos/bin/python
.PHONY : test
.PHONY : test
build: netuse.c
(cd ..; $(PYTHON) setup.py build)
......@@ -32,7 +32,7 @@ Then run test:
$ cd src
$ make test
Before test netreport.py,
Before test netreport.py,
$ easy_install lxml
$ ln -s /opt/git/slapos.core
......@@ -43,6 +43,46 @@ Use Cases
Slave Node
----------
Copy file "slapos-monitor", "netuse.dll" to /usr/sbin, then add a startup item for the domain user:
* Logon as local administrator, run the following command in the cygwin terminal:
username="The domain user name, it could be seen in the C:/Documents and Seetings"
target=$(cygpath -w "/cygdrive/c/Documents and Settings/${username}/Start Menu/Programs/Startup/netdrive monitor.lnk")
cat <<EOF > create_shortcut.vbs
Set WshShell = WScript.CreateObject("WScript.Shell")
strStartup = WshShell.SpecialFolders("Startup")
Set oShellLink = WshShell.CreateShortcut("${target}")
oShellLink.TargetPath = "$(cygpath -w /bin/mintty.exe)"
oShellLink.Arguments = "-l /var/log/slap-monitor.log -w hide --exec /usr/sbin/slapos-monitor"
oShellLink.WindowStyle = 1
oShellLink.Description = "netdrive monitor"
oShellLink.WorkingDirectory = "$(cygpath -w /usr/sbin)"
oShellLink.Save
EOF
cscript //B create_shortcut.vbs
rm create_shortcut.vbs
If you don't want to add a shortcut, the second way is to add a startup item in the registry:
* Login as domain user, and run the following command in the cygwin terminal:
regtool set \\HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\slap-monitor \
"$(cygpath -w /bin/mintty.exe) -l /var/log/slap-monitor.log -w hide --exec /usr/sbin/slapos-monitor"
You want to remove the entry by
regtool unset \\HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\slap-monitor
* In case the domain user have no right to change registry, then get sid first:
mkpasswd -d
It will list all the users in the current domain, find the expected sid, suppose it is "S-1-5-21-117609710-920026266-725345543-500", login as local administrator:
regtool set \\HKU\\S-1-5-21-117609710-920026266-725345543-500\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\slap-monitor \
"$(cygpath -w /bin/mintty.exe) -l /var/log/slap-monitor.log -w hide --exec /usr/sbin/slapos-monitor"
Master Node
-----------
......@@ -53,4 +93,3 @@ Issues
======
1. Some records may be lost if the computer is shutdown in unnormal way.
......@@ -27,13 +27,14 @@
#
##############################################################################
import argparse
from datetime import datetime, date
from lxml import etree
import netuse
import os.path
import slapos.slap.slap
import sqlite3
import sys
import xmlrpclib
from datetime import datetime, date
from lxml import etree
from time import sleep
def parseArgumentTuple():
......@@ -59,9 +60,13 @@ def parseArgumentTuple():
parser.add_argument("--data-file",
help="File used to save report data.",
default="net_drive_usage_report.data")
parser.add_argument("--server-name",
help="Interval in seconds to send report to master.",
default="")
parser.add_argument("--port",
help="RPC Port of SlapMonitor.",
default=8008)
parser.add_argument("--batch",
help="If True, send report per day at mid-night. "
"Otherwise send report instantly.",
default=False)
option = parser.parse_args()
# Build option_dict
......@@ -85,7 +90,8 @@ class NetDriveUsageReporter(object):
self._report_date = None
self.report_interval = float(self.report_interval)
self.initializeDatabase(self.data_file)
self.slap_monitor_uri = 'http://localhost:%d' % option_dict['port']
def initializeConnection(self):
connection_dict = {}
connection_dict['key_file'] = self.key_file
......@@ -96,8 +102,7 @@ class NetDriveUsageReporter(object):
self._slap_computer = slap.registerComputer(self.computer_id)
def initializeConfigData(self):
user_info = netuse.userInfo()
self._domain_account = "%s\\%s" % user_info[1:3]
self._domain_account = "SlapMonitor"
q = self._db.execute
s = "SELECT _rowid_, report_date FROM config " \
......@@ -119,13 +124,14 @@ class NetDriveUsageReporter(object):
last_timestamp = datetime.now()
interval = 30.0 if self.report_interval > 60 else (self.report_interval / 2)
try:
monitor = xmlrpclib.ServerProxy(self.slap_monitor_uri)
while True:
current_timestamp = datetime.now()
d = current_timestamp - last_timestamp
if d.seconds < self.report_interval:
sleep(interval)
continue
self.insertUsageReport(last_timestamp.isoformat(), d.seconds)
self.insertUsageReport(monitor, last_timestamp.isoformat(), d.seconds)
self.sendReport()
last_timestamp = current_timestamp
except KeyboardInterrupt:
......@@ -133,20 +139,20 @@ class NetDriveUsageReporter(object):
finally:
self._db.close()
def insertUsageReport(self, start, duration):
def insertUsageReport(self, monitor, start, duration):
q = self._db.execute
for r in netuse.usageReport(self.server_name):
for r in monitor.usageReport():
q( "INSERT INTO net_drive_usage "
"(config_id, drive_letter, remote_folder, "
"(config_id, domain_user, drive_letter, remote_folder, "
" start, duration, usage_bytes )"
" VALUES (?, ?, ?, ?, ?, ?)",
(self._config_id, r[0], r[1], start, duration, r[3] - r[2]))
(self._config_id, r[0], r[1], r[2], start, duration, r[4] - r[3]))
def sendAllReport(self):
"""Called at startup of this application, send all report
in the config table."""
q = self._db.execute
for r in q("SELECT _rowid_, domain_account, computer_id, report_date "
for r in q("SELECT _rowid_, computer_id, report_date "
"FROM config "
"WHERE report_date < date('now')"):
self._postData(self.generateDailyReport(*r))
......@@ -160,10 +166,9 @@ class NetDriveUsageReporter(object):
# Change report_date to today
# (Optional) Move all the reported data to histroy table
today = date.today().isoformat()
if self._report_date < today:
if (not self.batch) or self._report_date < today:
self._postData(self.generateDailyReport(self._config_id,
self.computer_id,
self._domain_account,
self._report_date))
self._db.execute("UPDATE config SET report_date=? where _rowid_=?",
(today, self._config_id))
......@@ -191,6 +196,7 @@ class NetDriveUsageReporter(object):
config_id INTEGER REFERENCES config ( _rowid_ ),
drive_letter TEXT NOT NULL,
remote_folder TEXT NOT NULL,
domain_user TEXT NOT NULL,
start TEXT DEFAULT CURRENT_TIMESTAMP,
duration FLOAT NOT NULL,
usage_bytes INTEGER,
......@@ -199,16 +205,17 @@ class NetDriveUsageReporter(object):
config_id INTEGER REFERENCES config ( _rowid_ ),
drive_letter TEXT NOT NULL,
remote_folder TEXT NOT NULL,
domain_user TEXT NOT NULL,
start TEXT NOT NULL,
duration FLOAT NOT NULL,
usage_bytes INTEGER,
remark TEXT)""")
def generateDailyReport(self, config_id, computer_id, domain_account,
report_date, remove=True):
def generateDailyReport(self, config_id, computer_id, report_date, remove=True):
q = self._db.execute
report = etree.Element("consumption")
for r in q("SELECT remote_folder, duration, usage_bytes FROM net_drive_usage "
for r in q("SELECT domain_user, remote_folder, duration, usage_bytes "
"FROM net_drive_usage "
"WHERE config_id=? AND strftime('%Y-%m-%d', start)=?",
(config_id, report_date)):
movement = etree.Element('movement')
......@@ -222,7 +229,7 @@ class NetDriveUsageReporter(object):
movement.append(element)
element = etree.Element("reference")
element.text = domain_account
element.text = etree.Element("domain_user"),
movement.append(element)
element = etree.Element("reference")
......
......@@ -131,18 +131,208 @@ netuse_user_info(PyObject *self, PyObject *args)
return NULL;
}
static char
get_free_drive_letter(void)
static int
wnet_enumerate_netdrive(LPNETRESOURCE lpnr)
{
DWORD bitmasks = GetLogicalDrives();
char ch = 'A';
while (bitmasks) {
if ((bitmasks & 1L) == 0)
return ch;
++ ch;
bitmasks >>= 1;
DWORD dwResult, dwResultEnum;
HANDLE hEnum;
DWORD cbBuffer = 16384; // 16K is a good size
DWORD cEntries = -1; // enumerate all possible entries
LPNETRESOURCE lpnrLocal; // pointer to enumerated structures
DWORD i;
dwResult = WNetOpenEnum(RESOURCE_GLOBALNET,
RESOURCETYPE_DISK,
0,
lpnr, // NULL first time the function is called
&hEnum); // handle to the resource
if (dwResult != NO_ERROR) {
printf("WnetOpenEnum failed with error %ld\n", dwResult);
return FALSE;
}
return (char)0;
lpnrLocal = (LPNETRESOURCE) GlobalAlloc(GPTR, cbBuffer);
if (lpnrLocal == NULL) {
printf("WnetOpenEnum failed with error %ld\n", dwResult);
return FALSE;
}
do {
ZeroMemory(lpnrLocal, cbBuffer);
dwResultEnum = WNetEnumResource(hEnum, // resource handle
&cEntries, // defined locally as -1
lpnrLocal, // LPNETRESOURCE
&cbBuffer);
if (dwResultEnum == NO_ERROR) {
for (i = 0; i < cEntries; i++) {
printf("NETRESOURCE[%ld] Usage: 0x%ld = ", i, lpnrLocal[i].dwUsage);
if (lpnrLocal[i].dwUsage & RESOURCEUSAGE_CONNECTABLE)
printf("connectable ");
if (lpnrLocal[i].dwUsage & RESOURCEUSAGE_CONTAINER)
printf("container ");
printf("\n");
printf("NETRESOURCE[%ld] Localname: %s\n", i, lpnrLocal[i].lpLocalName);
printf("NETRESOURCE[%ld] Remotename: %s\n", i, lpnrLocal[i].lpRemoteName);
printf("NETRESOURCE[%ld] Comment: %s\n", i, lpnrLocal[i].lpComment);
printf("NETRESOURCE[%ld] Provider: %s\n", i, lpnrLocal[i].lpProvider);
printf("\n");
if (RESOURCEUSAGE_CONTAINER == (lpnrLocal[i].dwUsage
& RESOURCEUSAGE_CONTAINER))
if (!wnet_enumerate_netdrive(&lpnrLocal[i]))
printf("EnumerateFunc returned FALSE\n");
}
}
else if (dwResultEnum != ERROR_NO_MORE_ITEMS) {
printf("WNetEnumResource failed with error %ld\n", dwResultEnum);
break;
}
} while (dwResultEnum != ERROR_NO_MORE_ITEMS);
GlobalFree((HGLOBAL) lpnrLocal);
dwResult = WNetCloseEnum(hEnum);
if (dwResult != NO_ERROR) {
printf("WNetCloseEnum failed with error %ld\n", dwResult);
return FALSE;
}
return TRUE;
}
static int
reg_enumerate_netdrive(void)
{
/* LsaEnumerateLogonSessions -> LogonId */
/* OpenTokenByLogonId -> TokenHandle */
/* LsaGetLogonSessionData -> Sid */
/* ConvertSidToStringSid */
/* From regkey HKU\SID\NETWORK */
/* ImpersonateLoggedOnUser Or CreateProcessWithTokenW */
/* WNetAddConnection */
return 0;
}
static PyObject *
netuse_list_drive(PyObject *self, PyObject *args)
{
PyObject *retvalue = NULL;
PyObject *pobj = NULL;
char *servername = NULL;
char *username = NULL;
char *password = NULL;
char chdrive = 'A';
char drivepath[] = { 'A', ':', '\\', 0 };
char drivename[] = { 'A', ':', 0 };
char szRemoteName[MAX_PATH];
DWORD dwResult;
DWORD cchBuff = MAX_PATH;
char szUserName[MAX_PATH];
if (! PyArg_ParseTuple(args, "|s", &servername)) {
return NULL;
}
retvalue = PyList_New(0);
if (retvalue == NULL)
return NULL;
while (chdrive <= 'Z') {
drivepath[0] = chdrive;
drivename[0] = chdrive;
dwResult = WNetGetConnection(drivename,
szRemoteName,
&cchBuff
);
if (dwResult == NO_ERROR) {
dwResult = WNetGetUser("z:",
(LPSTR) szUserName,
&cchBuff);
pobj = Py_BuildValue("ssss",
drivename,
szRemoteName,
"OK",
szUserName
);
if (PyList_Append(retvalue, pobj) == -1) {
Py_XDECREF(retvalue);
return NULL;
}
}
else if (dwResult == ERROR_CONNECTION_UNAVAIL) {
}
else if (dwResult == ERROR_NOT_CONNECTED) {
}
else if (dwResult == ERROR_BAD_DEVICE) {
}
else if (dwResult == ERROR_NO_NET_OR_BAD_PATH) {
}
else {
PyErr_Format(PyExc_RuntimeError,
"A system error has occurred in WNetGetConnection: %ld",
GetLastError()
);
Py_XDECREF(retvalue);
return NULL;
}
++ chdrive;
}
return retvalue;
}
static int
connect_net_drive(char *remote, char *drive)
{
DWORD dwRetVal;
NETRESOURCE nr;
DWORD dwFlags;
char *password=NULL;
char *user=NULL;
memset(&nr, 0, sizeof (NETRESOURCE));
nr.dwType = RESOURCETYPE_DISK;
nr.lpLocalName = drive;
nr.lpRemoteName = remote;
nr.lpProvider = NULL;
dwFlags = CONNECT_REDIRECT;
dwRetVal = WNetAddConnection2(&nr, password, user, dwFlags);
if (dwRetVal == NO_ERROR)
return 0;
PyErr_Format(PyExc_RuntimeError,
"WNetAddConnection2 failed with error: %lu\n",
dwRetVal
);
return dwRetVal;
}
static PyObject *
netuse_auto_connect(PyObject *self, PyObject *args)
{
Py_RETURN_NONE;
}
static PyObject *
netuse_remove_drive(PyObject *self, PyObject *args)
{
DWORD dwRetVal;
char *drive = NULL;
int force = 1;
if (! PyArg_ParseTuple(args, "si", &drive, &force))
return NULL;
dwRetVal = WNetCancelConnection2(drive, 0, force);
if (dwRetVal == NO_ERROR)
Py_RETURN_NONE;
PyErr_Format(PyExc_RuntimeError,
"WNetCancelConnection2 failed with error: %lu\n",
dwRetVal
);
return NULL;
}
static PyObject *
......@@ -153,20 +343,15 @@ netuse_map_drive(PyObject *self, PyObject *args)
DWORD dwFlags;
char *remote = NULL;
char drive[] = { 0, ':', 0 };
char *drive = NULL;
char *user = NULL;
char *password = NULL;
if (! PyArg_ParseTuple(args, "s", &remote)) {
return NULL;
}
char accessName[MAX_PATH] = {0};
DWORD dwBufSize = MAX_PATH;
DWORD dwResult;
drive[0] = get_free_drive_letter();
if (!drive[0]) {
PyErr_SetString(PyExc_RuntimeError,
"Add net drive failed: no available drive letter."
);
if (! PyArg_ParseTuple(args, "ss|ss", &remote, &drive, &user, &password))
return NULL;
}
memset(&nr, 0, sizeof (NETRESOURCE));
nr.dwType = RESOURCETYPE_DISK;
......@@ -175,15 +360,72 @@ netuse_map_drive(PyObject *self, PyObject *args)
nr.lpProvider = NULL;
dwFlags = CONNECT_UPDATE_PROFILE;
dwRetVal = WNetAddConnection2(&nr, password, user, dwFlags);
if (drive == NULL)
dwFlags |= CONNECT_REDIRECT;
dwRetVal = WNetUseConnection(NULL,
&nr,
password,
user,
dwFlags,
accessName,
&dwBufSize,
&dwResult);
if (dwRetVal == NO_ERROR)
return PyString_FromString(drive);
return PyString_FromString(accessName);
PyErr_Format(PyExc_RuntimeError,
"WNetAddConnection2 failed with error: %lu\n",
dwRetVal
);
return NULL;
}
static PyObject *
netuse_usage_report(PyObject *self, PyObject *args)
{
char *drive = NULL;
PyObject *pobj = NULL;
ULARGE_INTEGER lFreeBytesAvailable;
ULARGE_INTEGER lTotalNumberOfBytes;
/* ULARGE_INTEGER lTotalNumberOfFreeBytes; */
if (! PyArg_ParseTuple(args, "s", &drive))
return NULL;
if (GetDiskFreeSpaceEx(drive,
&lFreeBytesAvailable,
&lTotalNumberOfBytes,
NULL
)) {
pobj = Py_BuildValue("LL", lFreeBytesAvailable, lTotalNumberOfBytes);
return pobj;
}
PyErr_Format(PyExc_RuntimeError,
"A system error has occurred in GetDiskFreeSpaceEx(%s): %ld",
drive,
GetLastError()
);
return NULL;
}
/* Useless code */
#if 0
static char
get_free_drive_letter(void)
{
DWORD bitmasks = GetLogicalDrives();
char ch = 'A';
while (bitmasks) {
if ((bitmasks & 1L) == 0)
return ch;
++ ch;
bitmasks >>= 1;
}
return (char)0;
}
/*
* Travel all the mapped drive to check whether there is duplicated
* shared folder:
......@@ -235,7 +477,7 @@ check_duplicate_shared_folder(PyObject *retvalue, const char *folder)
}
static PyObject *
netuse_usage_report(PyObject *self, PyObject *args)
netuse_usage_report_orig(PyObject *self, PyObject *args)
{
char * servername = NULL;
PyObject *retvalue = NULL;
......@@ -381,26 +623,72 @@ netuse_usage_report(PyObject *self, PyObject *args)
}
return retvalue;
}
#endif /* #if 0 */
static PyMethodDef NetUseMethods[] = {
{
"userInfo",
netuse_user_info,
"autoConnect",
netuse_auto_connect,
METH_VARARGS,
(
"userInfo()\n\n"
"Get the logon user information, return a tuple:\n"
"(server, domain, user).\n"
"autoConnect()\n\n"
"Create mapped drive from shared folder, it uses the default user\n"
"name. (provided by the user context for the process.)\n"
"Raise exception if something is wrong.\n "
)
},
{
"mapDrive",
"listNetDrive",
netuse_list_drive,
METH_VARARGS,
(
"listNetDrive()\n\n"
"List all the net drives visible by current user. Return a list:\n"
" [ (drive, remote, status, user), ... ] \n"
"Not that if the calling application is running in a different logon\n"
"session than the application that made the connection, it's\n"
"unvisible for the current application. If the current user has\n"
"administrator privilege, these connections could be shown, but\n"
"the status is unavaliable or unconnect.\n"
"\n"
"Refer to http://msdn.microsoft.com/en-us/library/windows/desktop/aa363908(v=vs.85).aspx\n"
"Defining an MS-DOS Device Name\n"
"Refer to http://msdn.microsoft.com/en-us/library/windows/hardware/ff554302(v=vs.85).aspx\n"
"Local and Global MS-DOS Device Names\n"
)
},
{
"mapNetDrive",
netuse_map_drive,
METH_VARARGS,
(
"mapDrive(sharefolder)\n\n"
"Create mapped drive from shared folder, it uses the default user\n"
"name. (provided by the user context for the process.) \n"
"mapNetDrive(remote, drive, user=None, password=None)\n\n"
"Create net drive from remote folder, and return the assigned\n"
"drive letter. \n"
"It uses the default user which initialize this remote connection\n"
"if user is None. \n"
"When drive is an empty string, the system will automatically\n"
"assigns network drive letters, letters are assigned beginning\n"
"with Z:, then Y:, and ending with C:\n."
"For examples,"
" mapNetDrive(r'\\\\server\\data')\n"
" mapNetDrive(r'\\\\server\\data', 'T:')\n"
" mapNetDrive(r'\\\\server\\data', 'T:', r'\\\\server\\jack', 'abc')\n"
"Raise exception if something is wrong.\n"
)
},
{
"removeNetDrive",
netuse_remove_drive,
METH_VARARGS,
(
"removeNetDrive(drive, force=True)\n\n"
"Remove mapped drive specified by drive, For example,\n"
" removeNetDrive('X:')\n"
"Parameter force specifies whether the disconnection should occur\n"
"if there are open files or jobs on the connection. If this parameter\n"
"is FALSE, the function fails if there are open files or jobs.\n"
"Raise exception if something is wrong, otherwise return None.\n"
)
},
{
......@@ -408,11 +696,22 @@ static PyMethodDef NetUseMethods[] = {
netuse_usage_report,
METH_VARARGS,
(
"usagereport(servername='')\n\n"
"Return a tuple to report all the net drive information:\n"
"[ (drive, remote, available, total), ... ]\n"
"If servername is not empty, then only net drives in the specified server\n"
"are returned.\n"
"usageReport(drive)\n\n"
"Return a tuple to report the usage of the net drive:\n"
" (available, total)\n"
"For examples,\n"
" usageReport('Z:')\n"
"Raise exception if something is wrong.\n"
)
},
{
"userInfo",
netuse_user_info,
METH_VARARGS,
(
"userInfo()\n\n"
"Get the logon user information, return a tuple:\n"
"(server, domain, user).\n"
)
},
{NULL, NULL, 0, NULL}
......
#!/usr/bin/python
import logging
import os
import sys
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import netuse
# Restrict to a particular path.
class RequestHandler(SimpleXMLRPCRequestHandler):
rpc_paths = ('/RPC2',)
class SlapNodeMonitor(object):
def __init__(self):
super(SlapNodeMonitor, self).__init__()
self.port = 8008
netuse.autoConnect()
self.drivelist = netuse.listNetDrive()
print 'Net drive list:'
print self.drivelist
def cleanup(self):
pass
def netdrive_usage(self):
'''Get net drive usage, return a list as
[ (user, drive, remote, free, total), ... ]
'''
result = []
for k in self.drivelist:
r = netuse.usageReport(k[0])
result.append((k[3], k[0], k[1], r[0], r[1]))
return repr(result)
def run(self):
# Create server
server = SimpleXMLRPCServer(('localhost', self.port),
requestHandler=RequestHandler)
server.register_introspection_functions()
server.register_function(self.netdrive_usage, 'netdrive_usage')
try:
# Run the server's main loop
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
self.cleanup()
if __name__ == '__main__':
SlapNodeMonitor().run()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment