Subversion Repositories general

Rev

Rev 1155 | Rev 1157 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1140 dev 1
/******************************************************************
1145 dev 2
 * RebootDaemon.
1140 dev 3
 *
1145 dev 4
 * A daemon which waits on specified TCP port for special string
1143 dev 5
 * and reboots the computer.
1140 dev 6
 *
7
 * Command line: rebootdaemon [--port PORT] [--config CONFIG_FILE]
8
 * Defaults are port 19 and config /etc/rebootdaemon.conf.
9
 *
10
 * Config file looks like:
11
 * --------------------------
12
 * port=19
13
 * password="some password"
14
 * --------------------------
15
 *
16
 * Usage example (from any other host):
17
 * echo -n "some password" | nc host_to_rebot 19
18
 *
19
 * Copyleft 2005 Anatoli Klassen
20
 *
21
 ******************************************************************/
22
 
23
#include <stdlib.h>
24
#include <stdio.h>
25
#include <string.h>
26
#include <stdarg.h>
1144 dev 27
#include <ctype.h>
1140 dev 28
#include <errno.h>
29
#include <signal.h>
30
#include <unistd.h>
31
#include <netdb.h>
32
#include <syslog.h>
33
#include <sys/types.h>
1144 dev 34
#include <sys/stat.h>
1140 dev 35
#include <sys/socket.h>
36
#include <sys/wait.h>
37
#include <sys/reboot.h>
38
#include <netinet/in.h>
39
#include <arpa/inet.h>
40
 
1145 dev 41
/* name version number */
42
#define APP_NAME          "RebootDaemon"
43
#define VERSION           "1.0"
44
#define REVISION          "$Rev: 1156 $"
1144 dev 45
 
1145 dev 46
#define DEFAULT_PORT      19
47
#define DEFAULT_CFG_FILE  "/etc/rebootdaemon.conf"
48
#define CMDSIZE           4096
49
#define BUFSIZE           4096
50
#define MAX_CONFIG_LINE   4096
51
#define PORT_MIN          1
52
#define PORT_MAX          65535
1140 dev 53
 
1145 dev 54
/* return values of functions */
55
#define RESULT_OK         0
56
#define RESULT_EXIT       1
57
#define RESULT_ERROR      2
58
#define RESULT_UNEXPECTED 3
1140 dev 59
 
1145 dev 60
/* return values for the whole program */
61
#define EXIT_OK           0
62
#define EXIT_USER_ERROR   1
63
#define EXIT_UNEXPECTED   2
64
 
65
/* command line and config file parameters */
66
#define PARAM_HELP_1      "-h"
67
#define PARAM_HELP_2      "--help"
68
#define PARAM_VERSION_1   "-v"
69
#define PARAM_VERSION_2   "--version"
70
#define PARAM_PORT_1      "-p"
71
#define PARAM_PORT_2      "--port"
72
#define PARAM_CONFIG_1    "-c"
73
#define PARAM_CONFIG_2    "--config"
74
#define CONFIG_PORT       "port"
75
#define CONFIG_PASSWORD   "password"
76
 
1140 dev 77
struct config {
1156 dev 78
	ushort port;
79
	char   password[MAX_CONFIG_LINE];
1140 dev 80
};
81
 
1156 dev 82
static int parse_cmd_line(int argc, const char* argv[], long *port, char *config_name);
83
static int parse_config_file(const char *config_name, long *port, char *password);
84
static int get_revision(void);
1140 dev 85
 
1156 dev 86
static uint min(uint a, uint b)
1140 dev 87
{
88
	return (a < b) ? a : b;
89
}
90
 
1156 dev 91
static int establish(ushort port)
1140 dev 92
{
93
	int s;
94
	struct sockaddr_in sa;
95
 
96
	memset(&sa, 0, sizeof(struct sockaddr_in));
97
 
98
	sa.sin_family      = AF_INET;
1156 dev 99
	sa.sin_port        = htons(port);
1140 dev 100
	sa.sin_addr.s_addr = INADDR_ANY;
101
 
102
	if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
103
		return -1;
104
 
105
	if(bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
106
		close(s);
107
		return -1;
108
	}
109
 
1155 dev 110
	if(listen(s, 1) != 0) {
111
		close(s);
112
		return -1;
113
	}
1140 dev 114
 
115
	return s;
116
}
117
 
1156 dev 118
static void listen_socket(const struct config *cfg, int soc)
1142 dev 119
{
120
	int                t;
121
	char               cmd[CMDSIZE];
122
	char               buf[BUFSIZE];
123
	int                br;
124
	struct sockaddr_in sa;
125
	socklen_t          sa_len;
1155 dev 126
	int                cmdlen;
1142 dev 127
 
128
	for(;;) {
129
		sa_len = sizeof(sa);
1156 dev 130
		if((t = accept(soc, (struct sockaddr *)&sa, &sa_len)) < 0) {
1142 dev 131
			if(errno == EINTR) /* EINTR might happen on accept(), */
132
				continue;        /* try again */
133
 
1155 dev 134
			syslog(LOG_ERR, "cannot get connection, %s", strerror(errno));
1142 dev 135
			continue;
136
		}
137
 
1155 dev 138
		syslog(LOG_INFO, "connect from %s", inet_ntoa(sa.sin_addr));
1142 dev 139
 
140
		cmd[0] = '\0';
1155 dev 141
		cmdlen = 0;
1142 dev 142
		while((br = recv(t, buf, BUFSIZE, 0)) > 0) {
1156 dev 143
			strncat(cmd, buf, min((uint)br, sizeof(cmd) - cmdlen - 1));
144
			cmdlen += min((uint)br, sizeof(cmd) - cmdlen - 1);
1142 dev 145
		}
146
		sleep(1);
147
		close(t);
148
 
1147 dev 149
		syslog(LOG_INFO, "got command");
1142 dev 150
 
1143 dev 151
		if(0 == strncmp(cmd, cfg->password, sizeof(cmd))) {
1142 dev 152
			syslog(LOG_EMERG, "REBOOT");
153
			sleep(5);
154
			if(reboot(RB_AUTOBOOT) < 0) {
1155 dev 155
				syslog(LOG_ERR, "cannot reboot, %s",  strerror(errno));
1142 dev 156
			}
157
		}
158
	}
159
}
160
 
1156 dev 161
static void print_version(void)
1140 dev 162
{
1145 dev 163
	printf("%s %s.%d\n", APP_NAME, VERSION, get_revision());
164
}
165
 
1156 dev 166
static void pring_usage(int argc, const char* argv[])
1145 dev 167
{
168
	print_version();
169
	printf("\nA daemon which waits on specified TCP port for special string and reboots the computer.\n\n");
170
	printf("Usage: %s [--port PORT] [--config CONFIG_FILE]\n", argv[0]);
171
	printf("   or: %s --version\n", argv[0]);
172
	printf("   or: %s --help\n\n", argv[0]);
173
	printf("Defaults are port 19 and config /etc/rebootdaemon.conf.\n\n");
174
	printf("Config file looks like:\n--------------------------\n");
175
	printf("port=19\npassword=\"some password\"\n--------------------------\n\n");
176
	printf("Then run from any other host:\necho -n \"some password\" | nc host_to_rebot 19\n");
177
}
178
 
1156 dev 179
static int print_cmd_error(int argc, const char* argv[], const char *msg, const char *value)
1145 dev 180
{
1140 dev 181
	fprintf(stderr, "%s", msg);
182
	if(value) fprintf(stderr, " - %s\n", value);
183
	fprintf(stderr, "\n");
184
 
1145 dev 185
	pring_usage(argc, argv);
186
 
187
	return RESULT_ERROR;
1140 dev 188
}
189
 
1156 dev 190
static int print_config_error(const char *config_name, int line, const char *msg, const char *value)
1140 dev 191
{
192
	fprintf(stderr, "Error in %s, line %d: %s", config_name, line, msg);
193
	if(value) fprintf(stderr, " - %s", value);
194
	fprintf(stderr, "\n");
195
 
1145 dev 196
	return RESULT_ERROR;
1140 dev 197
}
198
 
1145 dev 199
/* return: 0 - no error, continue; 1 - no error, but exit; 2 - user error, exit; 3 - unexpected error, exit */
1156 dev 200
static int read_config(struct config *cfg, int argc, const char* argv[])
1140 dev 201
{
1145 dev 202
	int  res_cmd_line;
203
	int  res_config_file;
1140 dev 204
	long cmd_port;
205
	long file_port;
206
	char cmd_config[MAX_CONFIG_LINE];
207
 
1145 dev 208
	res_cmd_line = parse_cmd_line(argc, argv, &cmd_port, cmd_config);
209
	if(res_cmd_line != RESULT_OK) return res_cmd_line;
1140 dev 210
 
1145 dev 211
	res_config_file = parse_config_file(cmd_config[0] != '\0' ? cmd_config : DEFAULT_CFG_FILE,
212
		&file_port, cfg->password);
213
	if(res_config_file != RESULT_OK) return res_config_file;
1140 dev 214
 
1143 dev 215
	if(cfg->password[0] == '\0') {
1140 dev 216
		fprintf(stderr, "Password is not set\n");
1145 dev 217
		return RESULT_ERROR;
1140 dev 218
	}
219
 
220
	if(cmd_port > 0)
1156 dev 221
		cfg->port = (ushort)cmd_port;
1140 dev 222
	else if(file_port > 0)
1156 dev 223
		cfg->port = (ushort)file_port;
1140 dev 224
	else
1143 dev 225
		cfg->port = DEFAULT_PORT;
1140 dev 226
 
1145 dev 227
	return RESULT_OK;
1140 dev 228
}
229
 
1156 dev 230
static int parse_cmd_line(int argc, const char* argv[], long *port, char *config_name)
1140 dev 231
{
232
	char *end;
233
	int  i;
234
 
235
	*port          = 0;
236
	config_name[0] = '\0';
237
 
238
	for(i = 1; i < argc; i++) {
239
		if(0 == strcmp(argv[i], PARAM_PORT_1) || 0 == strcmp(argv[i], PARAM_PORT_2)) {
240
			if(*port > 0)
1145 dev 241
				return print_cmd_error(argc, argv, "Port is already set", NULL);
1140 dev 242
 
243
			if(++i < argc) {
244
				*port = strtol(argv[i], &end, 10);
245
				if(*end != '\0' || *port < PORT_MIN || *port > PORT_MAX)
1145 dev 246
					return print_cmd_error(argc, argv, 
247
						"Port number must be integer between 1 and 65535", NULL);
1140 dev 248
			}
249
			else {
1145 dev 250
				return print_cmd_error(argc, argv, "Port number expected", NULL);
1140 dev 251
			}
252
		}
253
		else if(0 == strcmp(argv[i], PARAM_CONFIG_1) || 0 == strcmp(argv[i], PARAM_CONFIG_2)) {
254
			if(config_name[0] != '\0')
1145 dev 255
				return print_cmd_error(argc, argv, "Config file is already set", NULL);
1140 dev 256
 
257
			if(++i < argc) {
258
				strncpy(config_name, argv[i], MAX_CONFIG_LINE);
259
				if(config_name[MAX_CONFIG_LINE - 1] != '\0')
1155 dev 260
					return print_cmd_error(argc, argv,
261
						"Config file name is too long", NULL);
1140 dev 262
			}
263
			else {
1145 dev 264
				return print_cmd_error(argc, argv, "Config file expected", NULL);
1140 dev 265
			}
266
		}
1145 dev 267
		else if(0 == strcmp(argv[i], PARAM_VERSION_1) || 0 == strcmp(argv[i], PARAM_VERSION_2)) {
268
			print_version();
269
			return RESULT_EXIT;
270
		}
271
		else if(0 == strcmp(argv[i], PARAM_HELP_1) || 0 == strcmp(argv[i], PARAM_HELP_2)) {
272
			pring_usage(argc, argv);
273
			return RESULT_EXIT;
274
		}
1140 dev 275
		else {
1145 dev 276
			return print_cmd_error(argc, argv, "Unknown parameter", argv[i]);
1140 dev 277
		}
278
	}
279
 
1145 dev 280
	return RESULT_OK;
1140 dev 281
}
282
 
283
 
1156 dev 284
static int parse_config_file(const char *config_name, long *port, char *password)
1140 dev 285
{
286
	FILE *config_file;
287
	char buf[MAX_CONFIG_LINE];
288
	char *line;
289
	char *subline;
290
	char *cur;
291
	int  count;
1156 dev 292
	uint len;
1140 dev 293
 
294
	config_file = fopen(config_name, "r");
295
	if(!config_file) {
296
		fprintf(stderr, "Can not open config file %s: %s\n", config_name, strerror(errno));
1145 dev 297
		return RESULT_ERROR;
1140 dev 298
	}
299
 
300
	count = 0;
301
	while(fgets(buf, sizeof(buf), config_file)) {
302
		count++;
303
		line = buf;
304
 
305
		/* skip end spaces */
1156 dev 306
		len = strlen(line);
1146 dev 307
		if(len == MAX_CONFIG_LINE-1)
308
			return print_config_error(config_name, count, "Line is too long", NULL);
1140 dev 309
		while(len && isspace(line[len-1])) --len;
310
		if(!len) continue;
311
		line[len] = '\0';
312
 
313
		/* skip begin spaces */
314
		while(line[0] != '\0' && isspace(line[0])) line++;
315
 
316
		if('#' == line[0]) { /* skip comment lines */
317
			continue;
318
		}
319
		else if(strncmp(line, CONFIG_PORT, min(sizeof(CONFIG_PORT) - 1, len)) == 0) {
1146 dev 320
			subline = line + sizeof(CONFIG_PORT) - 1;
1145 dev 321
			if('=' != subline[0] && !isspace(subline[0]))
322
				return print_config_error(config_name, count, "Unknown config parameter", line);
1140 dev 323
			while(subline[0] != '\0' && isspace(subline[0])) subline++;
324
			if('=' != subline[0])
325
				return print_config_error(config_name, count, "Equal sign expected", NULL);
326
			subline++;
327
			*port = strtol(subline, &subline, 10);
328
			if(*port < PORT_MIN || *port > PORT_MAX)
329
				return print_config_error(config_name, count, 
330
					"Port number must be integer between 1 and 65535", NULL);
331
			while(subline[0] != '\0' && isspace(subline[0])) subline++;
332
			if('\0' != subline[0] && '#' != subline[0])
333
				return print_config_error(config_name, count, "End of line expected", NULL);
334
		}
335
		else if(strncmp(line, CONFIG_PASSWORD, min(sizeof(CONFIG_PASSWORD) - 1, len)) == 0) {
1146 dev 336
			subline = line + sizeof(CONFIG_PASSWORD) - 1;
1145 dev 337
			if('=' != subline[0] && !isspace(subline[0]))
338
				return print_config_error(config_name, count, "Unknown config parameter", line);
1140 dev 339
			while(subline[0] != '\0' && isspace(subline[0])) subline++;
340
			if('=' != subline[0])
341
				return print_config_error(config_name, count, "Equal sign expected", NULL);
342
			subline++;
343
			while(subline[0] != '\0' && isspace(subline[0])) subline++;
344
			if('"' != subline[0])
345
				return print_config_error(config_name, count, "Open quot expected", NULL);
346
			subline++;
347
 
348
			cur = password;
349
			while(subline[0] != '\0' && subline[0] != '"') {
350
				cur[0] = subline[0];
351
				cur++;
352
				subline++;
353
			}
354
 
355
			cur[0] = '\0';
356
			if('"' != subline[0])
357
				return print_config_error(config_name, count, "Close quot expected", NULL);
358
			subline++;
359
			while(subline[0] != '\0' && isspace(subline[0])) subline++;
360
			if('\0' != subline[0] && '#' != subline[0])
361
				return print_config_error(config_name, count, "End of line expected", NULL);
362
		}
363
		else {
364
			return print_config_error(config_name, count, "Unknown config parameter", line);
365
		}
366
	}
367
 
368
	if(fclose(config_file) != 0) {
369
		fprintf(stderr, "Can not close config file %s: %s\n", config_name, strerror(errno));
1145 dev 370
		return RESULT_UNEXPECTED;
1140 dev 371
	}
372
 
1145 dev 373
	return RESULT_OK;
1140 dev 374
}
375
 
1156 dev 376
static int get_revision(void)
1145 dev 377
{
1156 dev 378
	int        r;
379
	const char *begin;
1145 dev 380
 
381
	begin = REVISION;
382
	while(begin[0] != '\0' && begin[0] != ':') begin++;
383
	if(begin[0] == '\0') return 0;
384
	begin++;
1156 dev 385
	r = (int)strtol(begin, (char**)NULL, 10);
1145 dev 386
 
387
	return r;
388
}
389
 
1156 dev 390
static int create_child(int soc)
1143 dev 391
{
392
	pid_t child;
393
 
394
	child = fork();
395
	if(child == -1) {
396
		fprintf(stderr, "Cannot fork: %s\n", strerror(errno));
1145 dev 397
		return RESULT_UNEXPECTED; /* an unexpected error */
1143 dev 398
	}
399
	else if(child > 0) {
1156 dev 400
		if(close(soc) != 0) {
1143 dev 401
			fprintf(stderr, "Cannot close socket: %s\n", strerror(errno));
1145 dev 402
			return RESULT_UNEXPECTED; /* an unexpected error */
1143 dev 403
		}
1145 dev 404
		return RESULT_EXIT; /* we are the parent */
1143 dev 405
	}
406
	else {
1145 dev 407
		return RESULT_OK; /* we are the child */
1143 dev 408
	}
409
}
410
 
411
static int close_descr(int d)
412
{
413
	if(close(d) != 0) {
414
		syslog(LOG_ERR, "Cannot close descriptor %d: %s\n", d, strerror(errno));
1145 dev 415
		return RESULT_UNEXPECTED;
1143 dev 416
	}
1145 dev 417
	return RESULT_OK;
1143 dev 418
}
419
 
1156 dev 420
int main(int argc, const char* argv[])
1140 dev 421
{
1156 dev 422
	int           soc;
1143 dev 423
	struct config cfg;
1140 dev 424
 
1143 dev 425
	/* get config */
1145 dev 426
	switch(read_config(&cfg, argc, argv)) {
1156 dev 427
		case RESULT_EXIT:       return EXIT_OK;           /* no error but exit */
428
		case RESULT_ERROR:      return EXIT_USER_ERROR;   /* user error */
429
		case RESULT_UNEXPECTED: return EXIT_UNEXPECTED;   /* unexpected error */
1145 dev 430
	}
1143 dev 431
 
432
	/* try to listen the port */
1156 dev 433
	if((soc = establish(cfg.port)) < 0) {
1140 dev 434
		fprintf(stderr, "Cannot listen to port %i\n", cfg.port);
1145 dev 435
		return EXIT_USER_ERROR;
1140 dev 436
	}
437
 
1143 dev 438
	/* fork and release the console */
1156 dev 439
	switch(create_child(soc)) {
440
		case RESULT_EXIT:       return EXIT_OK;           /* no error but exit */
441
		case RESULT_ERROR:      return EXIT_USER_ERROR;   /* user error */
442
		case RESULT_UNEXPECTED: return EXIT_UNEXPECTED;   /* unexpected error */
1143 dev 443
	}
1140 dev 444
 
1143 dev 445
	/* continue as first child */
446
	if(setsid() == -1) {
447
		fprintf(stderr, "Cannot create session: %s\n", strerror(errno));
1145 dev 448
		return EXIT_UNEXPECTED;
1143 dev 449
	}
450
 
451
	/* fork the second time */
1156 dev 452
	switch(create_child(soc)) {
453
		case RESULT_EXIT:       return EXIT_OK;           /* no error but exit */
454
		case RESULT_ERROR:      return EXIT_USER_ERROR;   /* user error */
455
		case RESULT_UNEXPECTED: return EXIT_UNEXPECTED;   /* unexpected error */
1143 dev 456
	}
457
 
458
	/* continue as final child, from now do not use console for error output */
459
	chdir("/");
460
	umask(0);
1145 dev 461
	if(close_descr(0) != RESULT_OK) return EXIT_UNEXPECTED;
462
	if(close_descr(1) != RESULT_OK) return EXIT_UNEXPECTED;
463
	if(close_descr(2) != RESULT_OK) return EXIT_UNEXPECTED;
1143 dev 464
 
465
	syslog(LOG_INFO, "listen on %d", cfg.port);
1156 dev 466
	listen_socket(&cfg, soc);
1143 dev 467
 
1156 dev 468
	return EXIT_OK; /* unreachedable */
1140 dev 469
}
470