implemented single housekeeping thread for all message wait queues
[public/netxms.git] / src / server / tools / nxupload / nxupload.cpp
1 /*
2 ** nxupload - command line tool used to upload files to NetXMS agent
3 ** Copyright (C) 2004-2015 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: nxupload.cpp
20 **
21 **/
22
23 #include <nms_common.h>
24 #include <nms_agent.h>
25 #include <nms_util.h>
26 #include <nxsrvapi.h>
27
28 #ifndef _WIN32
29 #include <netdb.h>
30 #endif
31
32 /**
33 * Do agent upgrade
34 */
35 static int UpgradeAgent(AgentConnection &conn, TCHAR *pszPkgName, BOOL bVerbose, RSA *pServerKey)
36 {
37 UINT32 dwError;
38 int i;
39 BOOL bConnected = FALSE;
40
41 dwError = conn.startUpgrade(pszPkgName);
42 if (dwError == ERR_SUCCESS)
43 {
44 conn.disconnect();
45
46 if (bVerbose)
47 {
48 _tprintf(_T("Agent upgrade started, waiting for completion...\n")
49 _T("[............................................................]\r["));
50 fflush(stdout);
51 for(i = 0; i < 120; i += 2)
52 {
53 ThreadSleep(2);
54 _puttc(_T('*'), stdout);
55 fflush(stdout);
56 if ((i % 20 == 0) && (i > 30))
57 {
58 if (conn.connect(pServerKey, FALSE))
59 {
60 bConnected = TRUE;
61 break; // Connected successfully
62 }
63 }
64 }
65 _puttc(_T('\n'), stdout);
66 }
67 else
68 {
69 ThreadSleep(20);
70 for(i = 20; i < 120; i += 20)
71 {
72 ThreadSleep(20);
73 if (conn.connect(pServerKey, FALSE))
74 {
75 bConnected = TRUE;
76 break; // Connected successfully
77 }
78 }
79 }
80
81 // Last attempt to reconnect
82 if (!bConnected)
83 bConnected = conn.connect(pServerKey, FALSE);
84
85 if (bConnected && bVerbose)
86 {
87 _tprintf(_T("Successfully established connection to agent after upgrade\n"));
88 }
89 else
90 {
91 _ftprintf(stderr, _T("Failed to establish connection to the agent after upgrade\n"));
92 }
93 }
94 else
95 {
96 if (bVerbose)
97 _ftprintf(stderr, _T("%d: %s\n"), dwError, AgentErrorCodeToText(dwError));
98 }
99
100 return bConnected ? 0 : 1;
101 }
102
103 /**
104 * Upload progress callback
105 */
106 static void ProgressCallback(INT64 bytesTransferred, void *cbArg)
107 {
108 #ifdef _WIN32
109 _tprintf(_T("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%-16I64d"), bytesTransferred);
110 #else
111 _tprintf(_T("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%-16lld"), bytesTransferred);
112 #endif
113 }
114
115 /**
116 * Startup
117 */
118 int main(int argc, char *argv[])
119 {
120 char *eptr;
121 BOOL bStart = TRUE, bVerbose = TRUE, bUpgrade = FALSE;
122 int i, ch, iExitCode = 3;
123 int iAuthMethod = AUTH_NONE;
124 #ifdef _WITH_ENCRYPTION
125 int iEncryptionPolicy = ENCRYPTION_ALLOWED;
126 #else
127 int iEncryptionPolicy = ENCRYPTION_DISABLED;
128 #endif
129 WORD wPort = AGENT_LISTEN_PORT;
130 UINT32 dwTimeout = 5000, dwConnTimeout = 30000, dwError;
131 INT64 nElapsedTime;
132 TCHAR szSecret[MAX_SECRET_LENGTH] = _T("");
133 TCHAR szKeyFile[MAX_PATH] = DEFAULT_DATA_DIR DFILE_KEYS;
134 TCHAR szDestinationFile[MAX_PATH] = {0};
135 RSA *pServerKey = NULL;
136 NXCPCompressionMethod compression = NXCP_COMPRESSION_NONE;
137
138 // Parse command line
139 opterr = 1;
140 while((ch = getopt(argc, argv, "a:d:e:hK:p:qs:uvw:W:z")) != -1)
141 {
142 switch(ch)
143 {
144 case 'h': // Display help and exit
145 _tprintf(_T("Usage: nxupload [<options>] <host> <file>\n")
146 _T("Valid options are:\n")
147 _T(" -a <auth> : Authentication method. Valid methods are \"none\",\n")
148 _T(" \"plain\", \"md5\" and \"sha1\". Default is \"none\".\n")
149 _T(" -d <file> : Fully qualified destination file name\n")
150 #ifdef _WITH_ENCRYPTION
151 _T(" -e <policy> : Set encryption policy. Possible values are:\n")
152 _T(" 0 = Encryption disabled;\n")
153 _T(" 1 = Encrypt connection only if agent requires encryption;\n")
154 _T(" 2 = Encrypt connection if agent supports encryption;\n")
155 _T(" 3 = Force encrypted connection;\n")
156 _T(" Default value is 1.\n")
157 #endif
158 _T(" -h : Display help and exit.\n")
159 #ifdef _WITH_ENCRYPTION
160 _T(" -K <file> : Specify server's key file\n")
161 _T(" (default is ") DEFAULT_DATA_DIR DFILE_KEYS _T(").\n")
162 #endif
163 _T(" -p <port> : Specify agent's port number. Default is %d.\n")
164 _T(" -q : Quiet mode.\n")
165 _T(" -s <secret> : Specify shared secret for authentication.\n")
166 _T(" -u : Start agent upgrade from uploaded package.\n")
167 _T(" -v : Display version and exit.\n")
168 _T(" -w <seconds> : Set command timeout (default is 5 seconds)\n")
169 _T(" -W <seconds> : Set connection timeout (default is 30 seconds)\n")
170 _T(" -z : Compress data stream.\n")
171 _T("\n"), wPort);
172 bStart = FALSE;
173 break;
174 case 'a': // Auth method
175 if (!strcmp(optarg, "none"))
176 iAuthMethod = AUTH_NONE;
177 else if (!strcmp(optarg, "plain"))
178 iAuthMethod = AUTH_PLAINTEXT;
179 else if (!strcmp(optarg, "md5"))
180 iAuthMethod = AUTH_MD5_HASH;
181 else if (!strcmp(optarg, "sha1"))
182 iAuthMethod = AUTH_SHA1_HASH;
183 else
184 {
185 _tprintf(_T("Invalid authentication method \"%hs\"\n"), optarg);
186 bStart = FALSE;
187 }
188 break;
189 case 'p': // Port number
190 i = strtol(optarg, &eptr, 0);
191 if ((*eptr != 0) || (i < 0) || (i > 65535))
192 {
193 _tprintf(_T("Invalid port number \"%hs\"\n"), optarg);
194 bStart = FALSE;
195 }
196 else
197 {
198 wPort = (WORD)i;
199 }
200 break;
201 case 'q': // Quiet mode
202 bVerbose = FALSE;
203 break;
204 case 's': // Shared secret
205 #ifdef UNICODE
206 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, szSecret, MAX_SECRET_LENGTH);
207 szSecret[MAX_SECRET_LENGTH - 1] = 0;
208 #else
209 nx_strncpy(szSecret, optarg, MAX_SECRET_LENGTH);
210 #endif
211 break;
212 case 'u': // Upgrade agent
213 bUpgrade = TRUE;
214 break;
215 case 'v': // Print version and exit
216 _tprintf(_T("NetXMS UPLOAD command-line utility Version ") NETXMS_VERSION_STRING _T("\n"));
217 bStart = FALSE;
218 break;
219 case 'w': // Command timeout
220 i = strtol(optarg, &eptr, 0);
221 if ((*eptr != 0) || (i < 1) || (i > 120))
222 {
223 _tprintf(_T("Invalid timeout \"%hs\"\n"), optarg);
224 bStart = FALSE;
225 }
226 else
227 {
228 dwTimeout = (UINT32)i * 1000; // Convert to milliseconds
229 }
230 break;
231 case 'W': // Connection timeout
232 i = strtol(optarg, &eptr, 0);
233 if ((*eptr != 0) || (i < 1) || (i > 120))
234 {
235 _tprintf(_T("Invalid timeout \"%hs\"\n"), optarg);
236 bStart = FALSE;
237 }
238 else
239 {
240 dwConnTimeout = (UINT32)i * 1000; // Convert to milliseconds
241 }
242 break;
243 #ifdef _WITH_ENCRYPTION
244 case 'e':
245 iEncryptionPolicy = atoi(optarg);
246 if ((iEncryptionPolicy < 0) ||
247 (iEncryptionPolicy > 3))
248 {
249 _tprintf(_T("Invalid encryption policy %d\n"), iEncryptionPolicy);
250 bStart = FALSE;
251 }
252 break;
253 case 'K':
254 #ifdef UNICODE
255 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, szKeyFile, MAX_PATH);
256 szKeyFile[MAX_PATH - 1] = 0;
257 #else
258 nx_strncpy(szKeyFile, optarg, MAX_PATH);
259 #endif
260 break;
261 #else
262 case 'e':
263 case 'K':
264 if (bVerbose)
265 _tprintf(_T("ERROR: This tool was compiled without encryption support\n"));
266 bStart = FALSE;
267 break;
268 #endif
269 case 'd':
270 #ifdef UNICODE
271 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, szDestinationFile, MAX_PATH);
272 szDestinationFile[MAX_PATH - 1] = 0;
273 #else
274 nx_strncpy(szDestinationFile, optarg, MAX_PATH);
275 #endif
276 break;
277 case 'z':
278 compression = NXCP_COMPRESSION_LZ4;
279 break;
280 case '?':
281 bStart = FALSE;
282 break;
283 default:
284 break;
285 }
286 }
287
288 // Check parameter correctness
289 if (bStart)
290 {
291 if (argc - optind < 2)
292 {
293 if (bVerbose)
294 _tprintf(_T("Required argument(s) missing.\nUse nxupload -h to get complete command line syntax.\n"));
295 bStart = FALSE;
296 }
297 else if ((iAuthMethod != AUTH_NONE) && (szSecret[0] == 0))
298 {
299 if (bVerbose)
300 _tprintf(_T("Shared secret not specified or empty\n"));
301 bStart = FALSE;
302 }
303
304 // Load server key if requested
305 #ifdef _WITH_ENCRYPTION
306 if ((iEncryptionPolicy != ENCRYPTION_DISABLED) && bStart)
307 {
308 if (InitCryptoLib(0xFFFF, NULL))
309 {
310 pServerKey = LoadRSAKeys(szKeyFile);
311 if (pServerKey == NULL)
312 {
313 if (bVerbose)
314 _tprintf(_T("Error loading RSA keys from \"%s\"\n"), szKeyFile);
315 if (iEncryptionPolicy == ENCRYPTION_REQUIRED)
316 bStart = FALSE;
317 }
318 }
319 else
320 {
321 if (bVerbose)
322 _tprintf(_T("Error initializing cryptografy module\n"));
323 if (iEncryptionPolicy == ENCRYPTION_REQUIRED)
324 bStart = FALSE;
325 }
326 }
327 #endif
328
329 // If everything is ok, start communications
330 if (bStart)
331 {
332 // Initialize WinSock
333 #ifdef _WIN32
334 WSADATA wsaData;
335 WSAStartup(2, &wsaData);
336 #endif
337
338 InetAddress addr = InetAddress::resolveHostName(argv[optind]);
339 if (!addr.isValid())
340 {
341 if (bVerbose)
342 _tprintf(_T("Invalid host name or address specified\n"));
343 }
344 else
345 {
346 AgentConnection conn(addr, wPort, iAuthMethod, szSecret);
347
348 conn.setConnectionTimeout(dwConnTimeout);
349 conn.setCommandTimeout(dwTimeout);
350 conn.setEncryptionPolicy(iEncryptionPolicy);
351 if (conn.connect(pServerKey, bVerbose, &dwError))
352 {
353 UINT32 dwError;
354
355 #ifdef UNICODE
356 WCHAR fname[MAX_PATH];
357 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, argv[optind + 1], -1, fname, MAX_PATH);
358 fname[MAX_PATH - 1] = 0;
359 #else
360 #define fname argv[optind + 1]
361 #endif
362 nElapsedTime = GetCurrentTimeMs();
363 if (bVerbose)
364 _tprintf(_T("Upload: "));
365 dwError = conn.uploadFile(fname, szDestinationFile[0] != 0 ? szDestinationFile : NULL, bVerbose ? ProgressCallback : NULL, NULL, compression);
366 if (bVerbose)
367 _tprintf(_T("\r \r"));
368 nElapsedTime = GetCurrentTimeMs() - nElapsedTime;
369 if (bVerbose)
370 {
371 if (dwError == ERR_SUCCESS)
372 {
373 QWORD qwBytes;
374
375 qwBytes = FileSize(fname);
376 _tprintf(_T("File transferred successfully\n") UINT64_FMT _T(" bytes in %d.%03d seconds (%.2f KB/sec)\n"),
377 qwBytes, (LONG)(nElapsedTime / 1000),
378 (LONG)(nElapsedTime % 1000),
379 ((double)((INT64)qwBytes / 1024) / (double)nElapsedTime) * 1000);
380 }
381 else
382 {
383 _tprintf(_T("%d: %s\n"), dwError, AgentErrorCodeToText(dwError));
384 }
385 }
386
387 if (bUpgrade && (dwError == RCC_SUCCESS))
388 {
389 iExitCode = UpgradeAgent(conn, fname, bVerbose, pServerKey);
390 }
391 else
392 {
393 iExitCode = (dwError == ERR_SUCCESS) ? 0 : 1;
394 }
395 conn.disconnect();
396 }
397 else
398 {
399 if (bVerbose)
400 _tprintf(_T("%d: %s\n"), dwError, AgentErrorCodeToText(dwError));
401 iExitCode = 2;
402 }
403 }
404 }
405 }
406
407 MsgWaitQueue::shutdown();
408 return iExitCode;
409 }