5 Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
7 Copyright (C) 1997 - 2000 Pekka Riikonen
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
22 #include "clientlibincludes.h"
24 /* Client command list. */
25 SilcClientCommand silc_command_list[] =
27 SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", SILC_CF_LAG | SILC_CF_REG, 3),
28 SILC_CLIENT_CMD(whowas, WHOWAS, "WHOWAS", SILC_CF_LAG | SILC_CF_REG, 3),
29 SILC_CLIENT_CMD(identify, IDENTIFY, "IDENTIFY",
30 SILC_CF_LAG | SILC_CF_REG, 3),
31 SILC_CLIENT_CMD(nick, NICK, "NICK", SILC_CF_LAG | SILC_CF_REG, 2),
32 SILC_CLIENT_CMD(list, LIST, "LIST", SILC_CF_LAG | SILC_CF_REG, 2),
33 SILC_CLIENT_CMD(topic, TOPIC, "TOPIC", SILC_CF_LAG | SILC_CF_REG, 3),
34 SILC_CLIENT_CMD(invite, INVITE, "INVITE", SILC_CF_LAG | SILC_CF_REG, 3),
35 SILC_CLIENT_CMD(quit, QUIT, "QUIT", SILC_CF_LAG | SILC_CF_REG, 1),
36 SILC_CLIENT_CMD(kill, KILL, "KILL",
37 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
38 SILC_CLIENT_CMD(info, INFO, "INFO", SILC_CF_LAG | SILC_CF_REG, 2),
39 SILC_CLIENT_CMD(connect, CONNECT, "CONNECT",
40 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
41 SILC_CLIENT_CMD(ping, PING, "PING", SILC_CF_LAG | SILC_CF_REG, 2),
42 SILC_CLIENT_CMD(oper, OPER, "OPER",
43 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
44 SILC_CLIENT_CMD(join, JOIN, "JOIN", SILC_CF_LAG | SILC_CF_REG, 2),
45 SILC_CLIENT_CMD(motd, MOTD, "MOTD", SILC_CF_LAG | SILC_CF_REG, 2),
46 SILC_CLIENT_CMD(umode, UMODE, "UMODE", SILC_CF_LAG | SILC_CF_REG, 2),
47 SILC_CLIENT_CMD(cmode, CMODE, "CMODE", SILC_CF_LAG | SILC_CF_REG, 2),
48 SILC_CLIENT_CMD(kick, KICK, "KICK", SILC_CF_LAG | SILC_CF_REG, 2),
49 SILC_CLIENT_CMD(restart, RESTART, "RESTART",
50 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
51 SILC_CLIENT_CMD(close, CLOSE, "CLOSE",
52 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
53 SILC_CLIENT_CMD(die, DIE, "DIE",
54 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
55 SILC_CLIENT_CMD(silcoper, SILCOPER, "SILOPER",
56 SILC_CF_LAG | SILC_CF_REG | SILC_CF_SILC_OPER, 2),
57 SILC_CLIENT_CMD(leave, LEAVE, "LEAVE", SILC_CF_LAG | SILC_CF_REG, 2),
58 SILC_CLIENT_CMD(names, NAMES, "NAMES", SILC_CF_LAG | SILC_CF_REG, 2),
60 { NULL, 0, NULL, 0, 0 },
63 #define SILC_NOT_CONNECTED(x, c) \
64 x->ops->say((x), (c), \
65 "You are not connected to a server, use /SERVER to connect");
67 /* Command operation that is called at the end of all commands.
69 #define COMMAND cmd->client->ops->command(cmd->client, cmd->conn, \
70 cmd, TRUE, cmd->command->cmd)
72 /* Error to application. Usage: COMMAND_ERROR; */
73 #define COMMAND_ERROR cmd->client->ops->command(cmd->client, cmd->conn, \
74 cmd, FALSE, cmd->command->cmd)
76 /* List of pending commands. */
77 SilcClientCommandPending *silc_command_pending = NULL;
79 /* Finds and returns a pointer to the command list. Return NULL if the
80 command is not found. */
82 SilcClientCommand *silc_client_command_find(const char *name)
84 SilcClientCommand *cmd;
86 for (cmd = silc_command_list; cmd->name; cmd++) {
87 if (!strcmp(cmd->name, name))
94 /* Add new pending command to the list of pending commands. Currently
95 pending commands are executed from command replies, thus we can
96 execute any command after receiving some specific command reply.
98 The argument `reply_cmd' is the command reply from where the callback
99 function is to be called, thus, it IS NOT the command to be executed.
101 XXX: If needed in the future this support may be extended for
102 commands as well, when any command could be executed after executing
103 some specific command. */
105 void silc_client_command_pending(SilcCommand reply_cmd,
106 SilcClientCommandCallback callback,
109 SilcClientCommandPending *reply, *r;
111 reply = silc_calloc(1, sizeof(*reply));
112 reply->reply_cmd = reply_cmd;
113 reply->context = context;
114 reply->callback = callback;
116 if (silc_command_pending == NULL) {
117 silc_command_pending = reply;
121 for (r = silc_command_pending; r; r = r->next) {
122 if (r->next == NULL) {
129 /* Deletes pending command by reply command type. */
131 void silc_client_command_pending_del(SilcCommand reply_cmd)
133 SilcClientCommandPending *r, *tmp;
135 if (silc_command_pending) {
136 if (silc_command_pending->reply_cmd == reply_cmd) {
137 silc_free(silc_command_pending);
138 silc_command_pending = NULL;
142 for (r = silc_command_pending; r; r = r->next) {
143 if (r->next && r->next->reply_cmd == reply_cmd) {
145 r->next = r->next->next;
153 /* Free command context and its internals */
155 void silc_client_command_free(SilcClientCommandContext cmd)
160 for (i = 0; i < cmd->argc; i++)
161 silc_free(cmd->argv[i]);
166 /* Command WHOIS. This command is used to query information about
169 SILC_CLIENT_CMD_FUNC(whois)
171 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
172 SilcClientConnection conn = cmd->conn;
176 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
181 if (cmd->argc < 2 || cmd->argc > 3) {
182 cmd->client->ops->say(cmd->client, conn,
183 "Usage: /WHOIS <nickname>[@<server>] [<count>]");
188 buffer = silc_command_encode_payload(SILC_COMMAND_WHOIS,
189 cmd->argc - 1, ++cmd->argv,
190 ++cmd->argv_lens, ++cmd->argv_types);
191 silc_client_packet_send(cmd->client, cmd->conn->sock,
192 SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
193 buffer->data, buffer->len, TRUE);
194 silc_buffer_free(buffer);
199 /* Notify application */
203 silc_client_command_free(cmd);
206 SILC_CLIENT_CMD_FUNC(whowas)
210 /* Command IDENTIFY. This command is used to query information about
211 specific user, especially ID's. */
213 SILC_CLIENT_CMD_FUNC(identify)
215 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
216 SilcClientConnection conn = cmd->conn;
220 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
225 if (cmd->argc < 2 || cmd->argc > 3) {
226 cmd->client->ops->say(cmd->client, conn,
227 "Usage: /IDENTIFY <nickname>[@<server>] [<count>]");
232 buffer = silc_command_encode_payload(SILC_COMMAND_IDENTIFY,
233 cmd->argc - 1, ++cmd->argv,
234 ++cmd->argv_lens, ++cmd->argv_types);
235 silc_client_packet_send(cmd->client, cmd->conn->sock,
236 SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
237 buffer->data, buffer->len, TRUE);
238 silc_buffer_free(buffer);
243 /* Notify application */
247 silc_client_command_free(cmd);
250 /* Command NICK. Shows current nickname/sets new nickname on current
253 SILC_CLIENT_CMD_FUNC(nick)
255 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
256 SilcClientConnection conn = cmd->conn;
260 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
265 /* Show current nickname */
268 cmd->client->ops->say(cmd->client, conn,
269 "Your nickname is %s on server %s",
270 conn->nickname, conn->remote_host);
272 cmd->client->ops->say(cmd->client, conn,
273 "Your nickname is %s", conn->nickname);
275 /* XXX Notify application */
280 /* Set new nickname */
281 buffer = silc_command_encode_payload(SILC_COMMAND_NICK,
282 cmd->argc - 1, ++cmd->argv,
283 ++cmd->argv_lens, ++cmd->argv_types);
284 silc_client_packet_send(cmd->client, cmd->conn->sock,
285 SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
286 buffer->data, buffer->len, TRUE);
287 silc_buffer_free(buffer);
292 silc_free(conn->nickname);
293 conn->nickname = strdup(cmd->argv[1]);
295 /* Notify application */
299 silc_client_command_free(cmd);
302 SILC_CLIENT_CMD_FUNC(list)
306 /* Command TOPIC. Sets/shows topic on a channel. */
308 SILC_CLIENT_CMD_FUNC(topic)
310 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
311 SilcClientConnection conn = cmd->conn;
312 SilcIDCacheEntry id_cache = NULL;
313 SilcChannelEntry channel;
315 unsigned char *id_string;
319 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
324 if (cmd->argc < 2 || cmd->argc > 3) {
325 cmd->client->ops->say(cmd->client, conn,
326 "Usage: /TOPIC <channel> [<topic>]");
331 if (cmd->argv[1][0] == '*') {
332 if (!conn->current_channel) {
333 cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
337 name = conn->current_channel->channel_name;
342 if (!conn->current_channel) {
343 cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
348 /* Get the Channel ID of the channel */
349 if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
350 cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
355 channel = (SilcChannelEntry)id_cache->context;
357 /* Send TOPIC command to the server */
358 id_string = silc_id_id2str(id_cache->id, SILC_ID_CHANNEL);
360 buffer = silc_command_encode_payload_va(SILC_COMMAND_TOPIC, 2,
361 1, id_string, SILC_ID_CHANNEL_LEN,
363 strlen(cmd->argv[2]));
365 buffer = silc_command_encode_payload_va(SILC_COMMAND_TOPIC, 1,
366 1, id_string, SILC_ID_CHANNEL_LEN);
367 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
368 0, NULL, NULL, buffer->data, buffer->len, TRUE);
369 silc_buffer_free(buffer);
371 /* Notify application */
375 silc_client_command_free(cmd);
378 /* Command INVITE. Invites specific client to join a channel. */
380 SILC_CLIENT_CMD_FUNC(invite)
382 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
383 SilcClient client = cmd->client;
384 SilcClientConnection conn = cmd->conn;
385 SilcClientEntry client_entry;
386 SilcChannelEntry channel_entry;
388 unsigned int num = 0;
389 char *nickname = NULL, *server = NULL;
390 unsigned char *client_id, *channel_id;
393 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
398 if (cmd->argc != 3) {
399 cmd->client->ops->say(cmd->client, conn,
400 "Usage: /INVITE <nickname>[@<server>] <channel>");
405 /* Parse the typed nickname. */
406 if (!silc_parse_nickname(cmd->argv[1], &nickname, &server, &num)) {
407 cmd->client->ops->say(cmd->client, conn, "Bad nickname");
412 /* Find client entry */
413 client_entry = silc_idlist_get_client(client, conn, nickname, server, num);
415 /* Client entry not found, it was requested thus mark this to be
417 silc_client_command_pending(SILC_COMMAND_IDENTIFY,
418 silc_client_command_invite, context);
422 client_id = silc_id_id2str(client_entry->id, SILC_ID_CLIENT);
424 /* Find channel entry */
425 channel_entry = silc_idlist_get_channel(client, conn, cmd->argv[2]);
426 if (!channel_entry) {
427 cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
428 silc_free(client_id);
433 channel_id = silc_id_id2str(channel_entry->id, SILC_ID_CHANNEL);
435 buffer = silc_command_encode_payload_va(SILC_COMMAND_INVITE, 2,
436 1, client_id, SILC_ID_CLIENT_LEN,
437 2, channel_id, SILC_ID_CHANNEL_LEN);
438 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
439 0, NULL, NULL, buffer->data, buffer->len, TRUE);
440 silc_buffer_free(buffer);
442 cmd->client->ops->say(cmd->client, conn,
443 "Inviting %s to channel %s", cmd->argv[1],
446 /* Notify application */
450 silc_client_command_free(cmd);
453 /* Command QUIT. Closes connection with current server. */
455 SILC_CLIENT_CMD_FUNC(quit)
457 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
461 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
466 buffer = silc_command_encode_payload(SILC_COMMAND_QUIT, cmd->argc - 1,
467 ++cmd->argv, ++cmd->argv_lens,
469 silc_client_packet_send(cmd->client, cmd->conn->sock, SILC_PACKET_COMMAND,
471 buffer->data, buffer->len, TRUE);
472 silc_buffer_free(buffer);
477 /* Close connection */
478 silc_client_close_connection(cmd->client, cmd->conn->sock);
479 cmd->client->ops->disconnect(cmd->client, cmd->conn);
481 /* Notify application */
485 silc_client_command_free(cmd);
488 SILC_CLIENT_CMD_FUNC(kill)
492 /* Command INFO. Request information about specific server. If specific
493 server is not provided the current server is used. */
495 SILC_CLIENT_CMD_FUNC(info)
497 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
498 SilcClientConnection conn = cmd->conn;
503 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
509 name = strdup(conn->remote_host);
511 name = strdup(cmd->argv[1]);
513 /* Send the command */
514 buffer = silc_command_encode_payload_va(SILC_COMMAND_INFO, 1,
515 1, name, strlen(name));
516 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
517 0, NULL, NULL, buffer->data, buffer->len, TRUE);
518 silc_buffer_free(buffer);
520 /* Notify application */
524 silc_client_command_free(cmd);
527 SILC_CLIENT_CMD_FUNC(connect)
531 /* Command PING. Sends ping to server. This is used to test the
532 communication channel. */
534 SILC_CLIENT_CMD_FUNC(ping)
536 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
537 SilcClientConnection conn = cmd->conn;
544 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
549 if (cmd->argc == 1 || !strcmp(cmd->argv[1], conn->remote_host))
550 name = strdup(conn->remote_host);
552 id = silc_id_str2id(conn->remote_id_data, SILC_ID_SERVER);
554 /* Send the command */
555 buffer = silc_command_encode_payload_va(SILC_COMMAND_PING, 1,
556 1, conn->remote_id_data,
558 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
559 0, NULL, NULL, buffer->data, buffer->len, TRUE);
560 silc_buffer_free(buffer);
562 /* Start counting time */
563 for (i = 0; i < conn->ping_count; i++) {
564 if (conn->ping[i].dest_id == NULL) {
565 conn->ping[i].start_time = time(NULL);
566 conn->ping[i].dest_id = id;
567 conn->ping[i].dest_name = name;
572 if (i >= conn->ping_count) {
573 i = conn->ping_count;
574 conn->ping = silc_realloc(conn->ping, sizeof(*conn->ping) * (i + 1));
575 conn->ping[i].start_time = time(NULL);
576 conn->ping[i].dest_id = id;
577 conn->ping[i].dest_name = name;
581 /* Notify application */
585 silc_client_command_free(cmd);
588 SILC_CLIENT_CMD_FUNC(oper)
592 SILC_CLIENT_CMD_FUNC(trace)
596 SILC_CLIENT_CMD_FUNC(notice)
600 /* Command JOIN. Joins to a channel. */
602 SILC_CLIENT_CMD_FUNC(join)
604 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
605 SilcClientConnection conn = cmd->conn;
606 SilcIDCacheEntry id_cache = NULL;
610 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
616 /* Show channels currently joined to */
621 /* See if we have joined to the requested channel already */
622 if (silc_idcache_find_by_data_one(conn->channel_cache, cmd->argv[1],
624 cmd->client->ops->say(cmd->client, conn,
625 "You are talking to channel %s", cmd->argv[1]);
626 conn->current_channel = (SilcChannelEntry)id_cache->context;
628 cmd->client->screen->bottom_line->channel = cmd->argv[1];
629 silc_screen_print_bottom_line(cmd->client->screen, 0);
634 /* Send JOIN command to the server */
635 buffer = silc_command_encode_payload(SILC_COMMAND_JOIN,
636 cmd->argc - 1, ++cmd->argv,
637 ++cmd->argv_lens, ++cmd->argv_types);
638 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
639 0, NULL, NULL, buffer->data, buffer->len, TRUE);
640 silc_buffer_free(buffer);
645 /* Notify application */
649 silc_client_command_free(cmd);
652 SILC_CLIENT_CMD_FUNC(motd)
654 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
655 SilcClientConnection conn = cmd->conn;
659 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
664 if (cmd->argc < 1 || cmd->argc > 1) {
665 cmd->client->ops->say(cmd->client, conn,
671 /* Send TOPIC command to the server */
672 buffer = silc_command_encode_payload_va(SILC_COMMAND_MOTD, 1,
673 2, conn->remote_host,
674 strlen(conn->remote_host));
675 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
676 0, NULL, NULL, buffer->data, buffer->len, TRUE);
677 silc_buffer_free(buffer);
679 /* Notify application */
683 silc_client_command_free(cmd);
686 SILC_CLIENT_CMD_FUNC(umode)
690 SILC_CLIENT_CMD_FUNC(cmode)
694 SILC_CLIENT_CMD_FUNC(kick)
698 SILC_CLIENT_CMD_FUNC(restart)
702 SILC_CLIENT_CMD_FUNC(close)
706 SILC_CLIENT_CMD_FUNC(die)
710 SILC_CLIENT_CMD_FUNC(silcoper)
714 /* LEAVE command. Leaves a channel. Client removes itself from a channel. */
716 SILC_CLIENT_CMD_FUNC(leave)
718 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
719 SilcClientConnection conn = cmd->conn;
720 SilcIDCacheEntry id_cache = NULL;
721 SilcChannelEntry channel;
723 unsigned char *id_string;
727 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
732 if (cmd->argc != 2) {
733 cmd->client->ops->say(cmd->client, conn, "Usage: /LEAVE <channel>");
738 if (cmd->argv[1][0] == '*') {
739 if (!conn->current_channel) {
740 cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
744 name = conn->current_channel->channel_name;
749 if (!conn->current_channel) {
750 cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
755 /* Get the Channel ID of the channel */
756 if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
757 cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
762 channel = (SilcChannelEntry)id_cache->context;
764 /* Send LEAVE command to the server */
765 id_string = silc_id_id2str(id_cache->id, SILC_ID_CHANNEL);
766 buffer = silc_command_encode_payload_va(SILC_COMMAND_LEAVE, 1,
767 1, id_string, SILC_ID_CHANNEL_LEN);
768 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
769 0, NULL, NULL, buffer->data, buffer->len, TRUE);
770 silc_buffer_free(buffer);
772 /* We won't talk anymore on this channel */
773 cmd->client->ops->say(cmd->client, conn, "You have left channel %s", name);
775 conn->current_channel = NULL;
777 silc_idcache_del_by_id(conn->channel_cache, SILC_ID_CHANNEL, channel->id);
778 silc_free(channel->channel_name);
779 silc_free(channel->id);
780 silc_free(channel->key);
781 silc_cipher_free(channel->channel_key);
783 silc_free(id_string);
785 /* Notify application */
789 silc_client_command_free(cmd);
792 /* Command NAMES. Requests the names of the clients joined on requested
795 SILC_CLIENT_CMD_FUNC(names)
797 SilcClientCommandContext cmd = (SilcClientCommandContext)context;
798 SilcClientConnection conn = cmd->conn;
799 SilcIDCacheEntry id_cache = NULL;
802 unsigned char *id_string;
805 SILC_NOT_CONNECTED(cmd->client, cmd->conn);
810 if (cmd->argc != 2) {
811 cmd->client->ops->say(cmd->client, conn, "Usage: /NAMES <channel>");
816 if (cmd->argv[1][0] == '*')
817 name = conn->current_channel->channel_name;
821 /* Get the Channel ID of the channel */
822 if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
823 /* XXX should resolve the channel ID; LIST command */
824 cmd->client->ops->say(cmd->client, conn,
825 "You are not on that channel", name);
830 /* Send NAMES command to the server */
831 id_string = silc_id_id2str(id_cache->id, SILC_ID_CHANNEL);
832 buffer = silc_command_encode_payload_va(SILC_COMMAND_NAMES, 1,
833 1, id_string, SILC_ID_CHANNEL_LEN);
834 silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
835 0, NULL, NULL, buffer->data, buffer->len, TRUE);
836 silc_buffer_free(buffer);
837 silc_free(id_string);
839 /* Register dummy pending command that will tell the reply command
840 that user called this command. Server may send reply to this command
841 even if user did not send this command thus we want to handle things
842 differently when user sent the command. This is dummy and won't be
844 /* XXX this is kludge and should be removed after pending command reply
845 support is added. Currently only commands may be pending not command
847 silc_client_command_pending(SILC_COMMAND_NAMES,
848 silc_client_command_names, NULL);
850 /* Notify application */
854 silc_client_command_free(cmd);