Beispiel #1
0
void
test_llcp_pdu_size(void)
{
  cut_assert_equal_int(sizeof(sample_i_pdu_packed), pdu_size(sample_i_pdu), cut_message("pdu_size returns invalid value"));
}
Beispiel #2
0
int main(int argc, char **argv)
{
	OPTIONS.verbose    = 0;
	OPTIONS.endpoint   = strdup("tcp://127.0.0.1:2997");
	OPTIONS.daemonize  = 1;
	OPTIONS.pidfile    = strdup("/var/run/bolo2redis.pid");
	OPTIONS.user       = strdup("root");
	OPTIONS.group      = strdup("root");
	OPTIONS.redis_host = strdup("127.0.0.1");
	OPTIONS.redis_port = 6379;

	struct option long_opts[] = {
		{ "help",             no_argument, NULL, 'h' },
		{ "version",          no_argument, NULL, 'V' },
		{ "verbose",          no_argument, NULL, 'v' },
		{ "endpoint",   required_argument, NULL, 'e' },
		{ "foreground",       no_argument, NULL, 'F' },
		{ "pidfile",    required_argument, NULL, 'p' },
		{ "user",       required_argument, NULL, 'u' },
		{ "group",      required_argument, NULL, 'g' },
		{ "host",       required_argument, NULL, 'H' },
		{ "port",       required_argument, NULL, 'P' },
		{ 0, 0, 0, 0 },
	};
	for (;;) {
		int idx = 1;
		int c = getopt_long(argc, argv, "h?Vv+e:Fp:u:g:H:P:", long_opts, &idx);
		if (c == -1) break;

		switch (c) {
		case 'h':
		case '?':
			printf("bolo2redis v%s\n", BOLO_VERSION);
			printf("Usage: bolo2redis [-h?FVv] [-e tcp://host:port]\n"
			       "                  [-H redis.host.or.ip] [-P port]\n"
			       "                  [-u user] [-g group] [-p /path/to/pidfile]\n\n");
			printf("Options:\n");
			printf("  -?, -h               show this help screen\n");
			printf("  -F, --foreground     don't daemonize, stay in the foreground\n");
			printf("  -V, --version        show version information and exit\n");
			printf("  -v, --verbose        turn on debugging, to standard error\n");
			printf("  -e, --endpoint       bolo broadcast endpoint to connect to\n");
			printf("  -H, --host           name or address of redis server\n");
			printf("  -P, --port           what port redis is running on\n");
			printf("  -u, --user           user to run as (if daemonized)\n");
			printf("  -g, --group          group to run as (if daemonized)\n");
			printf("  -p, --pidfile        where to store the pidfile (if daemonized)\n");
			exit(0);

		case 'V':
			logger(LOG_DEBUG, "handling -V/--version");
			printf("bolo2redis v%s\n"
			       "Copyright (c) 2016 The Bolo Authors.  All Rights Reserved.\n",
			       BOLO_VERSION);
			exit(0);

		case 'v':
			OPTIONS.verbose++;
			break;

		case 'e':
			free(OPTIONS.endpoint);
			OPTIONS.endpoint = strdup(optarg);
			break;

		case 'F':
			OPTIONS.daemonize = 0;
			break;

		case 'p':
			free(OPTIONS.pidfile);
			OPTIONS.pidfile = strdup(optarg);
			break;

		case 'u':
			free(OPTIONS.user);
			OPTIONS.user = strdup(optarg);
			break;

		case 'g':
			free(OPTIONS.group);
			OPTIONS.group = strdup(optarg);
			break;

		case 'H':
			free(OPTIONS.redis_host);
			OPTIONS.redis_host = strdup(optarg);
			break;

		case 'P':
			OPTIONS.redis_port = atoi(optarg);
			break;

		default:
			fprintf(stderr, "unhandled option flag %#02x\n", c);
			return 1;
		}
	}

	if (OPTIONS.daemonize) {
		log_open("bolo2redis", "daemon");
		log_level(LOG_ERR + OPTIONS.verbose, NULL);

		mode_t um = umask(0);
		if (daemonize(OPTIONS.pidfile, OPTIONS.user, OPTIONS.group) != 0) {
			fprintf(stderr, "daemonization failed: (%i) %s\n", errno, strerror(errno));
			return 3;
		}
		umask(um);
	} else {
		log_open("bolo2redis", "console");
		log_level(LOG_INFO + OPTIONS.verbose, NULL);
	}
	logger(LOG_NOTICE, "starting up");

	logger(LOG_DEBUG, "allocating 0MQ context");
	void *zmq = zmq_ctx_new();
	if (!zmq) {
		logger(LOG_ERR, "failed to initialize 0MQ context");
		return 3;
	}
	logger(LOG_DEBUG, "allocating 0MQ SUB socket to talk to %s", OPTIONS.endpoint);
	void *z = zmq_socket(zmq, ZMQ_SUB);
	if (!z) {
		logger(LOG_ERR, "failed to create a SUB socket");
		return 3;
	}
	logger(LOG_DEBUG, "setting subscriber filter");
	if (zmq_setsockopt(z, ZMQ_SUBSCRIBE, "", 0) != 0) {
		logger(LOG_ERR, "failed to set subscriber filter");
		return 3;
	}
	logger(LOG_DEBUG, "connecting to %s", OPTIONS.endpoint);
	if (vzmq_connect(z, OPTIONS.endpoint) != 0) {
		logger(LOG_ERR, "failed to connect to %s", OPTIONS.endpoint);
		return 3;
	}

	logger(LOG_INFO, "connecting to redis at %s:%i", OPTIONS.redis_host, OPTIONS.redis_port);
	redisContext *redis = redisConnect(OPTIONS.redis_host, OPTIONS.redis_port);
	if (redis != NULL && redis->err) {
		logger(LOG_ERR, "failed to connect to redis running at %s:%i: %s",
				OPTIONS.redis_host, OPTIONS.redis_port, redis->err);
		return 3;
	}

	pdu_t *p;
	logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);

	signal_handlers();
	while (!signalled()) {
		while ((p = pdu_recv(z))) {
			logger(LOG_INFO, "received a [%s] PDU of %i frames", pdu_type(p), pdu_size(p));

			if (strcmp(pdu_type(p), "SET.KEYS") == 0 && pdu_size(p) % 2 == 1 ) {
				int i = 1;
				while (i < pdu_size(p)) {
					char *k = pdu_string(p, i++);
					char *v = pdu_string(p, i++);
					logger(LOG_DEBUG, "setting key `%s' = '%s'", k, v);

					redisReply *reply = redisCommand(redis, "SET %s %s", k, v);
					if (reply->type == REDIS_REPLY_ERROR) {
						logger(LOG_ERR, "received error from redis: %s", reply->str);
					}
					freeReplyObject(reply);

					free(k);
					free(v);
				}
			}

			pdu_free(p);
			logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);
		}
	}

	logger(LOG_INFO, "shutting down");
	vzmq_shutdown(z, 0);
	zmq_ctx_destroy(zmq);
	return 0;
}
Beispiel #3
0
int main(int argc, char **argv)
{
	OPTIONS.verbose   = 0;
	OPTIONS.endpoint  = strdup("tcp://127.0.0.1:2997");
	OPTIONS.daemonize = 1;
	OPTIONS.pidfile   = strdup("/var/run/" ME ".pid");
	OPTIONS.user      = strdup("root");
	OPTIONS.group     = strdup("root");
	OPTIONS.match     = strdup(".");
	OPTIONS.avatar    = strdup(":robot_face:");
	OPTIONS.username  = strdup("bolo");

	struct option long_opts[] = {
		{ "help",             no_argument, NULL, 'h' },
		{ "version",          no_argument, NULL, 'V' },
		{ "verbose",          no_argument, NULL, 'v' },
		{ "endpoint",   required_argument, NULL, 'e' },
		{ "foreground",       no_argument, NULL, 'F' },
		{ "pidfile",    required_argument, NULL, 'p' },
		{ "user",       required_argument, NULL, 'u' },
		{ "group",      required_argument, NULL, 'g' },
		{ "match",      required_argument, NULL, 'm' },
		{ "webhook",    required_argument, NULL, 'U' },
		{ "channel",    required_argument, NULL, 'C' },
		{ "botname",    required_argument, NULL, 'N' },
		{ "avatar",     required_argument, NULL, 'A' },
		{ 0, 0, 0, 0 },
	};
	for (;;) {
		int idx = 1;
		int c = getopt_long(argc, argv, "h?Vv+e:Fp:u:g:m:U:C:N:A:", long_opts, &idx);
		if (c == -1) break;

		switch (c) {
		case 'h':
		case '?':
			printf(ME " v%s\n", BOLO_VERSION);
			printf("Usage: " ME " [-h?FVv] [-e tcp://host:port]\n"
			       "                  [-l level]\n"
			       "                  [-u user] [-g group] [-p /path/to/pidfile]\n\n");
			printf("Options:\n");
			printf("  -?, -h               show this help screen\n");
			printf("  -F, --foreground     don't daemonize, stay in the foreground\n");
			printf("  -V, --version        show version information and exit\n");
			printf("  -v, --verbose        turn on debugging, to standard error\n");
			printf("  -e, --endpoint       bolo broadcast endpoint to connect to\n");
			printf("  -u, --user           user to run as (if daemonized)\n");
			printf("  -g, --group          group to run as (if daemonized)\n");
			printf("  -p, --pidfile        where to store the pidfile (if daemonized)\n");
			printf("  -U, --webhook        Slack webhook URL for integration\n");
			printf("  -C, --channel        channel (#channel or @user) to notify\n");
			printf("  -N, --botname        name to use for the notification robot\n");
			printf("  -A, --avatar         avatar image to use (either :emoji: or a URL)\n");
			exit(0);

		case 'V':
			logger(LOG_DEBUG, "handling -V/--version");
			printf(ME " v%s\n"
			       "Copyright (c) 2016 The Bolo Authors.  All Rights Reserved.\n",
			       BOLO_VERSION);
			exit(0);

		case 'v':
			OPTIONS.verbose++;
			break;

		case 'e':
			free(OPTIONS.endpoint);
			OPTIONS.endpoint = strdup(optarg);
			break;

		case 'F':
			OPTIONS.daemonize = 0;
			break;

		case 'p':
			free(OPTIONS.pidfile);
			OPTIONS.pidfile = strdup(optarg);
			break;

		case 'u':
			free(OPTIONS.user);
			OPTIONS.user = strdup(optarg);
			break;

		case 'g':
			free(OPTIONS.group);
			OPTIONS.group = strdup(optarg);
			break;

		case 'm':
			free(OPTIONS.match);
			OPTIONS.match = strdup(optarg);
			break;

		case 'U':
			free(OPTIONS.webhook);
			OPTIONS.webhook = strdup(optarg);
			break;

		case 'C':
			free(OPTIONS.channel);
			OPTIONS.channel = strdup(optarg);
			break;

		case 'N':
			free(OPTIONS.username);
			OPTIONS.username = strdup(optarg);
			break;

		case 'A':
			free(OPTIONS.avatar);
			OPTIONS.avatar = strdup(optarg);
			break;

		default:
			fprintf(stderr, "unhandled option flag %#02x\n", c);
			return 1;
		}
	}

	if (!OPTIONS.channel) {
		fprintf(stderr, "Missing required --channel flag.\n");
		return 1;
	}
	if (!OPTIONS.webhook) {
		fprintf(stderr, "Missing required --webhook flag.\n");
		return 1;
	}

	if (OPTIONS.daemonize) {
		log_open(ME, "daemon");
		log_level(LOG_ERR + OPTIONS.verbose, NULL);

		mode_t um = umask(0);
		if (daemonize(OPTIONS.pidfile, OPTIONS.user, OPTIONS.group) != 0) {
			fprintf(stderr, "daemonization failed: (%i) %s\n", errno, strerror(errno));
			return 3;
		}
		umask(um);
	} else {
		log_open(ME, "console");
		log_level(LOG_INFO + OPTIONS.verbose, NULL);
	}
	logger(LOG_NOTICE, "starting up");

	const char *re_err;
	int re_off;
	OPTIONS.re = pcre_compile(OPTIONS.match, 0, &re_err, &re_off, NULL);
	if (!OPTIONS.re) {
		fprintf(stderr, "Bad --match pattern (%s): %s\n", OPTIONS.match, re_err);
		exit(1);
	}
	OPTIONS.re_extra = pcre_study(OPTIONS.re, 0, &re_err);

	logger(LOG_DEBUG, "initializing curl subsystem");
	OPTIONS.curl = curl_easy_init();
	if (!OPTIONS.curl) {
		logger(LOG_ERR, "failed to initialize curl subsystem");
		return 3;
	}

	logger(LOG_DEBUG, "allocating 0MQ context");
	void *zmq = zmq_ctx_new();
	if (!zmq) {
		logger(LOG_ERR, "failed to initialize 0MQ context");
		return 3;
	}
	logger(LOG_DEBUG, "allocating 0MQ SUB socket to talk to %s", OPTIONS.endpoint);
	void *z = zmq_socket(zmq, ZMQ_SUB);
	if (!z) {
		logger(LOG_ERR, "failed to create a SUB socket");
		return 3;
	}
	logger(LOG_DEBUG, "setting subscriber filter");
	if (zmq_setsockopt(z, ZMQ_SUBSCRIBE, "", 0) != 0) {
		logger(LOG_ERR, "failed to set subscriber filter");
		return 3;
	}
	logger(LOG_DEBUG, "connecting to %s", OPTIONS.endpoint);
	if (vzmq_connect(z, OPTIONS.endpoint) != 0) {
		logger(LOG_ERR, "failed to connect to %s", OPTIONS.endpoint);
		return 3;
	}

	pdu_t *p;
	logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);

	signal_handlers();
	while (!signalled()) {
		while ((p = pdu_recv(z))) {
			logger(LOG_INFO, "received a [%s] PDU of %i frames", pdu_type(p), pdu_size(p));

			if (strcmp(pdu_type(p), "TRANSITION") == 0 && pdu_size(p) == 6) {
				s_notify(p);
			}

			pdu_free(p);

			logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);
		}
	}

	logger(LOG_INFO, "shutting down");
	vzmq_shutdown(z, 0);
	zmq_ctx_destroy(zmq);
	return 0;
}
Beispiel #4
0
int cmd_query(int off, int argc, char **argv)
{
	char *endpoint = strdup("tcp://127.0.0.1:2998");
	struct option long_opts[] = {
		{ "endpoint", required_argument, 0, 'e' },
		{ 0, 0, 0, 0 },
	};

	optind = ++off;
	for (;;) {
		int c = getopt_long(argc, argv, "e:", long_opts, &off);
		if (c == -1) break;

		switch (c) {
		case 'e':
			free(endpoint);
			endpoint = strdup(optarg);
		default:
			fprintf(stderr, "unhandled option flag %#02x\n", c);
			return 1;
		}
	}

	void *zmq = zmq_ctx_new();
	if (!zmq) {
		fprintf(stderr, "failed to initialize 0MQ context\n");
		return 3;
	}
	void *z = zmq_socket(zmq, ZMQ_DEALER);
	if (!z) {
		fprintf(stderr, "failed to create a DEALER socket\n");
		return 3;
	}
	if (vzmq_connect(z, endpoint) != 0) {
		fprintf(stderr, "failed to connect to %s\n", endpoint);
		return 3;
	}

	pdu_t *p;
	char *a, *b, *c, *s;
	char line[8192];
	for (;;) {
		if (isatty(0)) fprintf(stderr, "> ");
		if (fgets(line, 8192, stdin) == NULL)
			break;

		a = line;
		while (*a && isspace(*a)) a++;
		if (!*a || *a == '#') continue;
		b = a;
		while (*b && !isspace(*b)) b++;

		c = b;
		if (*c) c++;
		*b = '\0';

		if (strcasecmp(a, "stat") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing state name to `stat' call\n");
				fprintf(stderr, "usage: stat <state-name>\n");
				continue;
			}

			a = c; while (*a && !isspace(*a)) a++;
			b = a; while (*b &&  isspace(*b)) b++;
			*a = '\0';
			if (*b) {
				fprintf(stderr, "too many arguments to `stat' call\n");
				fprintf(stderr, "usage: stat <state-name>\n");
				continue;
			}

			if (pdu_send_and_free(pdu_make("GET.STATE", 1, c), z) != 0) {
				fprintf(stderr, "failed to send [GET.STATE] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				continue;
			}
			if (strcmp(pdu_type(p), "STATE") != 0) {
				fprintf(stderr, "unknown response [%s] from %s\n", pdu_type(p), endpoint);
				return 4;
			}
			fprintf(stdout, "%s ",  s = pdu_string(p, 1)); free(s); /* name      */
			fprintf(stdout, "%s ",  s = pdu_string(p, 2)); free(s); /* timestamp */
			fprintf(stdout, "%s ",  s = pdu_string(p, 3)); free(s); /* stale     */
			fprintf(stdout, "%s ",  s = pdu_string(p, 4)); free(s); /* code      */
			fprintf(stdout, "%s\n", s = pdu_string(p, 5)); free(s); /* summary   */
			pdu_free(p);

		} else if (strcasecmp(a, "set.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing arguments to `set.keys' call\n");
				fprintf(stderr, "usage: set.keys key1 value1 key2 value2 ...\n");
				continue;
			}

			int n = 0;
			p = pdu_make("SET.KEYS", 0);
			while (*c) {
				a = c; while (*a && !isspace(*a)) a++;
				b = a; while (*b &&  isspace(*b)) b++;
				*a = '\0';

				n++;
				pdu_extendf(p, "%s", a);
				c = b;
			}

			if (n % 2 != 0) {
				fprintf(stderr, "odd number of arguments to `set.keys' call\n");
				fprintf(stderr, "usage: set.keys key1 value1 key2 value2 ...\n");
				pdu_free(p);
				continue;
			}

			if (pdu_send_and_free(p, z) != 0) {
				fprintf(stderr, "failed to send [SET.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}

			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}
			pdu_free(p);

		} else if (strcasecmp(a, "get.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing arguments to `get.keys' call\n");
				fprintf(stderr, "usage: get.keys key1 key2 ...\n");
				continue;
			}

			p = pdu_make("GET.KEYS", 0);
			while (*c) {
				a = c; while (*a && !isspace(*a)) a++;
				b = a; while (*b &&  isspace(*b)) b++;
				*a = '\0';

				pdu_extendf(p, "%s", a);
				c = b;
			}

			if (pdu_send_and_free(p, z) != 0) {
				fprintf(stderr, "failed to send [GET.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}

			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}

			int i;
			for (i = 1; i < pdu_size(p); i += 2) {
				a = pdu_string(p, i);
				b = pdu_string(p, i + 1);

				fprintf(stdout, "%s = %s\n", a, b);

				free(a);
				free(b);
			}
			pdu_free(p);

		} else if (strcasecmp(a, "del.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing argument to `del.keys' call\n");
				fprintf(stderr, "usage: del.keys key1 key2 ...\n");
				continue;
			}

			p = pdu_make("DEL.KEYS", 0);
			while (*c) {
				a = c; while (*a && !isspace(*a)) a++;
				b = a; while (*b &&  isspace(*b)) b++;
				*a = '\0';

				pdu_extendf(p, "%s", a);
				c = b;
			}

			if (pdu_send_and_free(p, z) != 0) {
				fprintf(stderr, "failed to send [DEL.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}

			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}
			pdu_free(p);

		} else if (strcasecmp(a, "search.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing pattern argument to `search.keys' call\n");
				fprintf(stderr, "usage: search.keys <pattern>\n");
				continue;
			}

			a = c; while (*a && !isspace(*a)) a++;
			b = a; while (*b &&  isspace(*b)) b++;
			*a = '\0';
			if (*b) {
				fprintf(stderr, "too many arguments to `search.keys' call\n");
				fprintf(stderr, "usage: search.keys <pattern>\n");
				continue;
			}

			if (pdu_send_and_free(pdu_make("SEARCH.KEYS", 1, c), z) != 0) {
				fprintf(stderr, "failed to send [SEARCH.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}

			int i;
			for (i = 1; i < pdu_size(p); i++) {
				fprintf(stdout, "%s\n", s = pdu_string(p, i));
				free(s);
			}
			pdu_free(p);

		} else if (strcasecmp(a, "get.events") == 0) {
			char *ts = "0";
			if (*c) {
				ts = c;
				while (*c && isdigit(*c)) c++;
				*c = '\0';
			}

			if (pdu_send_and_free(pdu_make("GET.EVENTS", 1, ts), z) != 0) {
				fprintf(stderr, "failed to send [GET.EVENTS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				continue;
			}
			if (strcmp(pdu_type(p), "EVENTS") != 0) {
				fprintf(stderr, "unknown response [%s] from %s\n", pdu_type(p), endpoint);
				return 4;
			}
			fprintf(stdout, "%s", s = pdu_string(p, 1)); free(s);
			pdu_free(p);

		} else if (strcasecmp(a, "dump") == 0) {
			if (*c) fprintf(stderr, "ignoring useless arguments to `dump' command\n");

			if (pdu_send_and_free(pdu_make("DUMP", 0), z) != 0) {
				fprintf(stderr, "failed to send [DUMP] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				continue;
			}
			if (strcmp(pdu_type(p), "DUMP") != 0) {
				fprintf(stderr, "unknown response [%s] from %s\n", pdu_type(p), endpoint);
				return 4;
			}
			fprintf(stdout, "%s", s = pdu_string(p, 1)); free(s);
			pdu_free(p);

		} else {
			fprintf(stderr, "unrecognized command '%s'\n", a);
			continue;
		}
	}
	return 0;
}