/** * @file msh.c * @brief program to launch remote processes through MSH. This program is * to be started by the MSH Daemon or by any process in the process * subtree having the MSH Daemon as a parent process. * @author Sree Harsha Totakura */ #include "common.h" #include #include "termios.h" #include "mtypes.h" #include "util.h" #define LOG(kind,...) \ GNUNET_log (kind, __VA_ARGS__) #define LOG_DEBUG(...) LOG(GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) #define LOG_ERROR(...) LOG(GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__) /** * Configuration section for connection to local MSHD */ #define MSHD_LOCAL "MSHD-LOCAL" /** * Configuration section for connection to remote MSHD */ #define MSHD_REMOTE "MSHD-REMOTE" /** * The message queue for sending messages to the controller service */ struct MessageQueue { /** * next pointer for DLL */ struct MessageQueue *next; /** * prev pointer for DLL */ struct MessageQueue *prev; /** * The message to be sent */ struct GNUNET_MessageHeader *msg; }; /** * Context information for a connection */ struct ConnCtx { /** * The transmit handle */ struct GNUNET_CLIENT_TransmitHandle *tx; /** * The client connection handle */ struct GNUNET_CLIENT_Connection *conn; /** * configuration used to obtain this connection */ struct GNUNET_CONFIGURATION_Handle *cfg; /** * DLL head for the message queue */ struct MessageQueue *mq_head; /** * DLL tail for the message queue */ struct MessageQueue *mq_tail; /** * retry backoff time for this connection */ struct GNUNET_TIME_Relative backoff; }; /** * context for the connection to parent MSHD */ static struct ConnCtx ctx_local; /** * context for the connection to remote MSHD */ static struct ConnCtx ctx_remote; /** * file handle for the stdin */ static struct GNUNET_DISK_FileHandle *fh_stdin; /** * file handle for stdout */ static struct GNUNET_DISK_FileHandle *fh_stdout; /** * file handle for stdout */ static struct GNUNET_DISK_FileHandle *fh_stderr; /** * The command string (cmd + parameters) */ static char *cmdstr; /** * Program help text */ static char *help_text = "MSH utility to lauch remote programs\n" "Usage: msh [arg] ... \n"; /** * Buffer used to read data from input streams */ static char recv_buf[GNUNET_SERVER_MAX_MESSAGE_SIZE]; /** * Handle for a test to check if MSHD is running in a mode accepting remote commands. */ struct GNUNET_CLIENT_TestHandle *test; /** * length of the cmdstr */ static size_t cmdstr_len; /** * Task to forward stdin to MSHD */ static GNUNET_SCHEDULER_TaskIdentifier task_fwd_stdin; /** * Shutdown task to disconnect client connections */ static GNUNET_SCHEDULER_TaskIdentifier task_shutdown; /** * flag to indicate successful completion by setting it to GNUNET_OK */ static int result; /** * different states */ enum State { /** * Address lookup of the remote instance */ STATE_LOOKUP, /** * Remote command is delivered to remote MSHD */ STATE_DELIVER_CMD, /** * Get authorization credential from parent MSHD */ STATE_GET_CRED, /** * stdin and stdout streams are forwarded to and from the MSHD */ STATE_FORWARD_STREAMS, } state; /** * Terminal window size information */ static struct winsize ws; /** * Terminal settings */ static struct termios tio; /** * The ip address of the remote node */ static in_addr_t target; /** * The port number where the MSHD is listening on target host */ static uint16_t target_port; /** * Do not allocate pseudo-tty */ static int disable_pty; /** * Are we capable of allocating pseudo-tty */ static int can_pty; /** * Do we need to allocate a pseudo-tty */ static int need_pty; /** * Did we set our terminal to raw mode */ static int tty_rawed; /** * Destroys a connection context * * @param ctx connection context */ static void destroy_conn_ctx (struct ConnCtx *ctx) { struct MessageQueue *mq; if (NULL != ctx->tx) { GNUNET_CLIENT_notify_transmit_ready_cancel (ctx->tx); ctx->tx = NULL; } if (NULL != ctx->conn) { GNUNET_CLIENT_disconnect (ctx->conn); ctx->conn = NULL; } if (NULL != ctx->cfg) { GNUNET_CONFIGURATION_destroy (ctx->cfg); ctx->cfg = NULL; } while (NULL != (mq = ctx->mq_head)) { GNUNET_CONTAINER_DLL_remove (ctx->mq_head, ctx->mq_tail, mq); GNUNET_free (mq->msg); GNUNET_free (mq); } } /** * Shutdown task. Disconnect both client connections by destroying both * contexts * * @param cls NULL * @param tc scheduler task context */ static void do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { task_shutdown = GNUNET_SCHEDULER_NO_TASK; LOG_DEBUG ("Shutting down\n"); destroy_conn_ctx (&ctx_local); destroy_conn_ctx (&ctx_remote); if ( can_pty && tty_rawed ) { if (tcsetattr(0, TCSADRAIN, &tio) == -1) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "tcsetattr"); LOG_ERROR ("Failed to set terminal back from raw mode. " "The terminal may behave wierdly\n"); } } if (NULL != fh_stdin) GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh_stdin)); if (NULL != fh_stdout) GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh_stdout)); if (NULL != fh_stderr) GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh_stderr)); } /** * Function called to notify a client about the connection * begin ready to queue more data. "buf" will be * NULL and "size" zero if the connection was closed for * writing in the meantime. * * @param cls closure * @param size number of bytes available in buf * @param buf where the callee should write the message * @return number of bytes written to buf */ static size_t conn_transmit_ready (void *cls, size_t size, void *buf) { struct ConnCtx *ctx = cls; struct MessageQueue *mq; uint16_t msg_size; size_t wrote; wrote = 0; GNUNET_assert (NULL != ctx->tx); ctx->tx = NULL; if ((0 == size) && (NULL == buf)) { LOG_ERROR ("Connection to MSHD broken\n"); destroy_conn_ctx (ctx); return 0; } mq = ctx->mq_head; GNUNET_assert (NULL != mq); if ((0 == size) || (NULL == buf)) { LOG_DEBUG ("Message sending timed out -- retrying\n"); ctx->backoff = GNUNET_TIME_STD_BACKOFF (ctx->backoff); ctx->tx = GNUNET_CLIENT_notify_transmit_ready (ctx->conn, ntohs (mq->msg->size), ctx->backoff, GNUNET_NO, &conn_transmit_ready, ctx); return 0; } ctx->backoff = GNUNET_TIME_UNIT_ZERO; msg_size = ntohs (mq->msg->size); GNUNET_assert ( msg_size <= size); (void) memcpy (buf, mq->msg, msg_size); wrote = msg_size; LOG_DEBUG ("Message of type %u, size %u sent\n", ntohs (mq->msg->type), ntohs (mq->msg->size)); GNUNET_free (mq->msg); GNUNET_CONTAINER_DLL_remove (ctx->mq_head, ctx->mq_tail, mq); GNUNET_free (mq); if (NULL != (mq = ctx->mq_head)) { ctx->backoff = GNUNET_TIME_STD_BACKOFF (ctx->backoff); ctx->tx = GNUNET_CLIENT_notify_transmit_ready (ctx->conn, ntohs (mq->msg->size), ctx->backoff, GNUNET_NO, &conn_transmit_ready, ctx); } return wrote; } /** * Queues a message in send queue assocaited with conn * * @param msg the message to queue */ static void queue_message (struct ConnCtx *ctx, struct GNUNET_MessageHeader *msg) { struct MessageQueue *mq; uint16_t type; uint16_t size; type = ntohs (msg->type); size = ntohs (msg->size); mq = GNUNET_malloc (sizeof (struct MessageQueue)); mq->msg = msg; LOG_DEBUG ("Queueing message of type %u, size %u for sending\n", type, size); GNUNET_CONTAINER_DLL_insert_tail (ctx->mq_head, ctx->mq_tail, mq); if (NULL == ctx->tx) { ctx->backoff = GNUNET_TIME_STD_BACKOFF (ctx->backoff); ctx->tx = GNUNET_CLIENT_notify_transmit_ready (ctx->conn, size, ctx->backoff, GNUNET_NO, &conn_transmit_ready, ctx); } } /** * Dispatcher for received messages * * @param cls connection context * @param msg message received, NULL on timeout or fatal error */ static void dispatch (void *cls, const struct GNUNET_MessageHeader *msg); /** * forward stdin data to MSHD * * @param cls NULL * @param tc scheduler task context */ static void fwd_stdin (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct MSH_MSG_CmdIO *msg; ssize_t size; uint16_t msg_size; task_fwd_stdin = GNUNET_SCHEDULER_NO_TASK; if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) { return; } size = GNUNET_DISK_file_read (fh_stdin, recv_buf, GNUNET_SERVER_MAX_MESSAGE_SIZE - sizeof (struct MSH_MSG_CmdIO)); if (GNUNET_SYSERR == size) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } if (0 == size) { GNUNET_SCHEDULER_shutdown (); return; } msg_size = sizeof (struct MSH_MSG_CmdIO) + size; msg = GNUNET_malloc (msg_size); msg->header.size = htons (msg_size); msg->header.type = htons (MSH_MTYPE_CMD_STREAM_STDIN); (void) memcpy (msg->data, recv_buf, size); queue_message (&ctx_remote, &msg->header); task_fwd_stdin = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, fh_stdin, &fwd_stdin, NULL); } /** * Type of a function to call when we receive a message * from the service. * * @param ctx the connection context * @param msg message received, NULL on timeout or fatal error * @return GNUNET_OK to keep the connection open; any other value to close it */ static int handle_cmd_output (struct ConnCtx *ctx, const struct GNUNET_MessageHeader *msg_) { const struct MSH_MSG_CmdIO *msg; uint16_t size; uint16_t mtype; msg = (const struct MSH_MSG_CmdIO *) msg_; mtype = ntohs (msg->header.type); if ( (MSH_MTYPE_CMD_STREAM_STDOUT != mtype) && (MSH_MTYPE_CMD_STREAM_STDERR != mtype) ) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return GNUNET_SYSERR;; } size = ntohs (msg->header.size); size -= sizeof (struct MSH_MSG_CmdIO); switch (mtype) { case MSH_MTYPE_CMD_STREAM_STDOUT: GNUNET_break (size == GNUNET_DISK_file_write (fh_stdout, msg->data, size)); break; case MSH_MTYPE_CMD_STREAM_STDERR: if (NULL == fh_stderr) break; GNUNET_break (size == GNUNET_DISK_file_write (fh_stderr, msg->data, size)); break; default: GNUNET_assert (0); } return GNUNET_OK; } /** * Type of a function to call when we receive a message * from the service. * * @param ctx the connection context * @param msg message received, NULL on timeout or fatal error * @return GNUNET_OK to keep the connection open; any other value to close it */ static int handle_exec_begin (struct ConnCtx *ctx, const struct GNUNET_MessageHeader *msg_) { LOG (GNUNET_ERROR_TYPE_INFO, "Executing remote command\n"); fh_stdin = GNUNET_DISK_get_handle_from_native (stdin); fh_stdout = GNUNET_DISK_get_handle_from_native (stdout); fh_stderr = GNUNET_DISK_get_handle_from_native (stderr); GNUNET_assert (NULL != fh_stdin); GNUNET_assert (NULL != fh_stdout); state = STATE_FORWARD_STREAMS; task_fwd_stdin = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, fh_stdin, &fwd_stdin, NULL); result = GNUNET_OK; return GNUNET_OK; } /** * Set the terminal into raw mode */ void enter_raw_mode(int quiet) { struct termios raw_tio; raw_tio = tio; raw_tio.c_iflag |= IGNPAR; raw_tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); #ifdef IUCLC raw_tio.c_iflag &= ~IUCLC; #endif raw_tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); #ifdef IEXTEN raw_tio.c_lflag &= ~IEXTEN; #endif raw_tio.c_oflag &= ~OPOST; raw_tio.c_cc[VMIN] = 1; raw_tio.c_cc[VTIME] = 0; if (tcsetattr(fileno(stdin), TCSADRAIN, &raw_tio) == -1) { if (!quiet) GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "tcsetattr"); } else tty_rawed = 1; } /** * Generate PTY_MODE message containing the settings to be used for the created * pseudo-tty * * @return the PTY_MODE message; NULL upon error */ static struct MSH_MSG_PtyMode * gen_pty_msg () { struct MSH_MSG_PtyMode *tmsg; char *cp; uint16_t *ts; size_t size; unsigned int count; cp = getenv ("TERM"); /* count the number of terminal settings */ count = 0; #define TTYCHAR(NAME,OP) \ count++; #define TTYMODE(NAME,FIELD,OP) \ count++; #include "ttymodes.h" #undef TTYCHAR #undef TTYMODE /* Build the terminal mode message */ size = sizeof (struct MSH_MSG_PtyMode) + (sizeof (uint16_t) * 2 * count) + ((NULL != cp) ? (strlen(cp) + 1) : 0); tmsg = GNUNET_malloc (size); tmsg->header.type = htons (MSH_MTYPE_PTY_MODE); tmsg->header.size = htons (size); tmsg->ws_row = htons (ws.ws_row); tmsg->ws_col = htons (ws.ws_col); tmsg->ws_xpixel = htons (ws.ws_xpixel); tmsg->ws_ypixel = htons (ws.ws_ypixel); tmsg->ospeed = htonl (speed_to_baud (cfgetospeed (&tio))); tmsg->ispeed = htonl (speed_to_baud (cfgetispeed (&tio))); tmsg->nsettings = htons (count); /* Populate the message with settings */ ts = (uint16_t *) &tmsg[1]; #define TTYCHAR(NAME,OP) \ *(ts++) = htons (OP); \ *(ts++) = htons (tio.c_cc[NAME]); #define TTYMODE(NAME, FIELD, OP) \ *(ts++) = htons (OP); \ *(ts++) = htons ((tio.FIELD & NAME) != 0); #include "ttymodes.h" #undef TTYCHAR #undef TTYMODE /* copy the TERM env variable's value */ if (NULL != cp) (void) memcpy ((void *)ts, cp, strlen (cp)); return tmsg; } /** * Type of a function to call when we receive a message * from the service. * * @param ctx the connection context * @param msg message received, NULL on timeout or fatal error * @return GNUNET_OK to keep the connection open; any other value to close it */ static int handle_challenge_response (struct ConnCtx *ctx, const struct GNUNET_MessageHeader *msg_) { struct GNUNET_MessageHeader *msg; struct MSH_MSG_RunCmd *rmsg; struct MSH_MSG_PtyMode *ptymsg; uint16_t size; LOG_DEBUG ("Received CHALLENGE_RESPONSE. Forwarding it to remote MSHD\n"); msg = GNUNET_copy_message (msg_); queue_message (&ctx_remote, msg); destroy_conn_ctx (&ctx_local); if (can_pty && !disable_pty) { ptymsg = gen_pty_msg (); queue_message (&ctx_remote, &ptymsg->header); enter_raw_mode (1); } /* now queue RUN_CMD message */ size = sizeof (struct MSH_MSG_RunCmd) + cmdstr_len; rmsg = GNUNET_malloc (size); rmsg->header.size = htons (size); rmsg->header.type = htons (MSH_MTYPE_RUNCMD); (void) memcpy (rmsg->cmd, cmdstr, cmdstr_len); queue_message (&ctx_remote, &rmsg->header); return GNUNET_OK; } /** * Type of a function to call when we receive a message * from the service. * * @param ctx the connection context * @param msg message received, NULL on timeout or fatal error * @return GNUNET_OK to keep the connection open; any other value to close it */ static int handle_challenge (struct ConnCtx *ctx, const struct GNUNET_MessageHeader *msg_) { struct GNUNET_MessageHeader *msg; LOG_DEBUG ("Received CHALLENGE message. Forwarding it to parent MSHD\n"); if (sizeof (struct MSH_MSG_Challenge) > ntohs (msg_->size)) { GNUNET_break (0); return GNUNET_SYSERR; } msg = GNUNET_copy_message (msg_); queue_message (&ctx_local, msg); return GNUNET_OK; } /** * Tries to establish a connection to the target remote MSHD */ static void target_connect () { struct MSH_MSG_SessionOpen *msg; struct GNUNET_CONFIGURATION_Handle *cfg; uint16_t size; cfg = GNUNET_CONFIGURATION_create (); GNUNET_CONFIGURATION_set_value_string (cfg, MSHD_REMOTE, "HOSTNAME", ip2str (target)); GNUNET_CONFIGURATION_set_value_number (cfg, MSHD_REMOTE, "PORT", target_port); ctx_remote.cfg = cfg; ctx_remote.conn = GNUNET_CLIENT_connect (MSHD_REMOTE, ctx_remote.cfg); GNUNET_assert (NULL != ctx_remote.conn); size = sizeof (struct MSH_MSG_SessionOpen); msg = GNUNET_malloc (size); msg->header.size = htons (size); msg->header.type = htons (MSH_MTYPE_SESSION_OPEN); //(void) memcpy (msg->cmd, cmdstr, cmdstr_len); if (can_pty) msg->type = htonl (MSH_SESSION_TYPE_INTERACTIVE); else msg->type = htonl (MSH_SESSION_TYPE_NONINTERACTIVE); queue_message (&ctx_remote, &msg->header); GNUNET_CLIENT_receive (ctx_remote.conn, &dispatch, &ctx_remote, GNUNET_TIME_UNIT_FOREVER_REL); } /** * Type of a function to call when we receive a message * from the service. * * @param ctx the connection context * @param msg message received, NULL on timeout or fatal error * @return GNUNET_OK to keep the connection open; any other value to close it */ static int handle_lookup_reply (struct ConnCtx *ctx, const struct GNUNET_MessageHeader *msg_) { const struct MSH_MSG_AddressLookupReply *msg; uint16_t size; size = ntohs (msg_->size); msg = (const struct MSH_MSG_AddressLookupReply *) msg_; if (0 == (target_port = ntohs (msg->port))) { if ('\0' != msg->emsg[(size - sizeof (struct MSH_MSG_AddressLookup)) - 1]) { GNUNET_break (0); return GNUNET_SYSERR; } LOG_ERROR ("Address lookup for IP:%s failed with error: %s\n", ip2str (target), msg->emsg); return GNUNET_SYSERR; } LOG_DEBUG ("ADDRESS_LOOKUP_REPLY: Service available on %s:%u\n", ip2str (target), target_port); target_connect (); return GNUNET_OK; } /** * Dispatcher for received messages * * @param cls connection context * @param msg message received, NULL on timeout or fatal error */ static void dispatch (void *cls, const struct GNUNET_MessageHeader *msg) { struct ConnCtx *ctx = cls; int ret; if (NULL == msg) { LOG_DEBUG ("Broken IPC connection\n"); goto err_ret; } switch (ntohs (msg->type)) { case MSH_MTYPE_ADDRESS_LOOKUP_REPLY: ret = handle_lookup_reply (ctx, msg); break; case MSH_MTYPE_CHALLENGE: ret = handle_challenge (ctx, msg); break; case MSH_MTYPE_CHALLENGE_RESPONSE: ret = handle_challenge_response (ctx, msg); break; case MSH_MTYPE_EXEC_BEGIN: ret = handle_exec_begin (ctx, msg); break; case MSH_MTYPE_CMD_STREAM_STDOUT: case MSH_MTYPE_CMD_STREAM_STDERR: ret = handle_cmd_output (ctx, msg); break; default: LOG (GNUNET_ERROR_TYPE_WARNING, "Unrecognised message of type %u received\n", ntohs (msg->type)); goto err_ret; } if (GNUNET_SYSERR == ret) goto err_ret; /* We destroy local_ctx in handle_challenge_response, hence this check */ if (NULL != ctx->conn) GNUNET_CLIENT_receive (ctx->conn, &dispatch, ctx, GNUNET_TIME_UNIT_FOREVER_REL); return; err_ret: destroy_conn_ctx (ctx); GNUNET_SCHEDULER_shutdown (); } /** * Function called with the result on the service test. * * @param cls NULL * @param status GNUNET_YES if the service is running, * GNUNET_NO if the service is not running * GNUNET_SYSERR if the configuration is invalid */ static void status_cb (void *cls, int status) { struct MSH_MSG_AddressLookup *msg; uint16_t size; test = NULL; if (GNUNET_YES != status) { LOG_ERROR ("Service not running\n"); return; } ctx_local.conn = GNUNET_CLIENT_connect (MSHD_LOCAL, ctx_local.cfg); GNUNET_assert (NULL != ctx_local.conn); GNUNET_assert (0 != cmdstr_len); size = sizeof (struct MSH_MSG_AddressLookup); msg = GNUNET_malloc (size); msg->header.size = htons (size); msg->header.type = htons (MSH_MTYPE_ADDRESS_LOOKUP); msg->ip = htonl ((uint32_t) target); queue_message (&ctx_local, &msg->header); GNUNET_CLIENT_receive (ctx_local.conn, &dispatch, &ctx_local, GNUNET_TIME_UNIT_FOREVER_REL); task_shutdown = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown, NULL); } /** * Reads terminal settings and window size * * @return GNUNET_OK upon success; GNUNET_SYSERR upon error */ static int read_tty_settings () { if (ioctl(0, TIOCGWINSZ, &ws) < 0) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "ioctl"); return GNUNET_SYSERR; } if (-1 == tcgetattr (0, &tio)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "tcgetattr"); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Main function that will be run. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param cfg_ configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg_) { char *ipstr; char *psock_path; size_t arg_len; unsigned int cnt; if (!disable_pty && isatty (0)) can_pty = 1; if (need_pty && !can_pty) { LOG_ERROR ("Not attached to a terminal but pseudo-tty is requested (-t) option"); return; } if (can_pty) { if (GNUNET_OK != read_tty_settings ()) return; } state = STATE_DELIVER_CMD; if (NULL == (ipstr = args[0])) { LOG_ERROR ("Require an IP address\n"); fprintf (stderr, "%s", help_text); return; } target = inet_network (ipstr); if ((uint32_t) -1 == target) { LOG_ERROR ("Invalid IP address given"); return; } for (cnt = 1; NULL != args[cnt]; cnt++) { arg_len = strlen (args[cnt]) + 1; cmdstr = GNUNET_realloc (cmdstr, cmdstr_len + arg_len); (void) memcpy (cmdstr + cmdstr_len, args[cnt], arg_len); cmdstr_len += arg_len; } if (0 == cmdstr_len) { LOG_ERROR ("Require command to execute remotely\n"); fprintf (stderr, "%s", help_text); return; } if (NULL == (psock_path = getenv("MSHD_SOCK")) ) { LOG_ERROR ("Could not find socket for local MSHD. Are we running under MSHD?\n"); return; } LOG_DEBUG ("Unix socket path: %s\n", psock_path); ctx_local.cfg = GNUNET_CONFIGURATION_dup (cfg_); GNUNET_CONFIGURATION_set_value_string (ctx_local.cfg, MSHD_LOCAL, "UNIXPATH", psock_path); GNUNET_CONFIGURATION_set_value_string (ctx_local.cfg, "TESTING", "USE_ABSTRACT_SOCKETS", "YES"); test = GNUNET_CLIENT_service_test (MSHD_LOCAL, ctx_local.cfg, GNUNET_TIME_UNIT_SECONDS, &status_cb, NULL); } /** * Program entry point */ int main (int argc, char * const argv[]) { static const struct GNUNET_GETOPT_CommandLineOption options[] = { {'T', "disable-pty", NULL, _("do not allocate a pseudo-tty"), 0, &GNUNET_GETOPT_set_one, (void *) &disable_pty}, {'t', "require-pty", NULL, _("exit with error if a pseudo-tty cannot be allocated"), 0, &GNUNET_GETOPT_set_one, (void *) &need_pty}, GNUNET_GETOPT_OPTION_END }; result = GNUNET_SYSERR; if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "msh", help_text, options, &run, NULL)) { GNUNET_break (0); return 1; } return (GNUNET_OK == result) ? 0 : 2; }