aboutsummaryrefslogtreecommitdiff
path: root/src/chat/gnunet-chat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/chat/gnunet-chat.c')
-rw-r--r--src/chat/gnunet-chat.c674
1 files changed, 674 insertions, 0 deletions
diff --git a/src/chat/gnunet-chat.c b/src/chat/gnunet-chat.c
new file mode 100644
index 000000000..b960db043
--- /dev/null
+++ b/src/chat/gnunet-chat.c
@@ -0,0 +1,674 @@
1/*
2 This file is part of GNUnet.
3 (C) 2007, 2008, 2011 Christian Grothoff (and other contributing authors)
4
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19*/
20
21/**
22 * @file chat/gnunet-chat.c
23 * @brief Minimal chat command line tool
24 * @author Christian Grothoff
25 * @author Nathan Evans
26 * @author Vitaly Minko
27 */
28
29#include "platform.h"
30#include "gnunet_getopt_lib.h"
31#include "gnunet_program_lib.h"
32#include "gnunet_chat_service.h"
33#include <fcntl.h>
34
35static int ret;
36
37static const struct GNUNET_CONFIGURATION_Handle *cfg;
38
39static char *nickname;
40
41static char *room_name;
42
43static struct GNUNET_CONTAINER_MetaData *meta;
44
45static struct GNUNET_CHAT_Room *room;
46
47static GNUNET_SCHEDULER_TaskIdentifier handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
48
49struct ChatCommand
50{
51 const char *command;
52 int (*Action) (const char *arguments, const void *xtra);
53 const char *helptext;
54};
55
56struct UserList
57{
58 struct UserList *next;
59 struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
60 int ignored;
61};
62
63static struct UserList *users;
64
65static void
66free_user_list ()
67{
68 struct UserList *next;
69 while (NULL != users)
70 {
71 next = users->next;
72 GNUNET_free (users);
73 users = next;
74 }
75}
76
77static int do_help (const char *args, const void *xtra);
78
79
80/**
81 * Callback used for notification about incoming messages.
82 *
83 * @param cls closure, NULL
84 * @param room in which room was the message received?
85 * @param sender what is the ID of the sender? (maybe NULL)
86 * @param member_info information about the joining member
87 * @param message the message text
88 * @param options options for the message
89 * @return GNUNET_OK to accept the message now, GNUNET_NO to
90 * accept (but user is away), GNUNET_SYSERR to signal denied delivery
91 */
92static int
93receive_cb (void *cls,
94 struct GNUNET_CHAT_Room *room,
95 const GNUNET_HashCode *sender,
96 const struct GNUNET_CONTAINER_MetaData *meta,
97 const char *message,
98 enum GNUNET_CHAT_MsgOptions options)
99{
100 char *nick;
101 const char *fmt;
102
103 if (NULL != sender)
104 nick = GNUNET_PSEUDONYM_id_to_name (cfg, sender);
105 else
106 nick = GNUNET_strdup (_("anonymous"));
107 fmt = NULL;
108 switch (options)
109 {
110 case GNUNET_CHAT_MSG_OPTION_NONE:
111 case GNUNET_CHAT_MSG_ANONYMOUS:
112 fmt = _("`%s' said: %s\n");
113 break;
114 case GNUNET_CHAT_MSG_PRIVATE:
115 fmt = _("`%s' said to you: %s\n");
116 break;
117 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ANONYMOUS:
118 fmt = _("`%s' said to you: %s\n");
119 break;
120 case GNUNET_CHAT_MSG_AUTHENTICATED:
121 fmt = _("`%s' said for sure: %s\n");
122 break;
123 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_AUTHENTICATED:
124 fmt = _("`%s' said to you for sure: %s\n");
125 break;
126 case GNUNET_CHAT_MSG_ACKNOWLEDGED:
127 fmt = _("`%s' was confirmed that you received: %s\n");
128 break;
129 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
130 fmt = _("`%s' was confirmed that you and only you received: %s\n");
131 break;
132 case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_ACKNOWLEDGED:
133 fmt = _("`%s' was confirmed that you received from him or her: %s\n");
134 break;
135 case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
136 fmt =
137 _
138 ("`%s' was confirmed that you and only you received from him or her: %s\n");
139 break;
140 case GNUNET_CHAT_MSG_OFF_THE_RECORD:
141 fmt = _("`%s' said off the record: %s\n");
142 break;
143 default:
144 fmt = _("<%s> said using an unknown message type: %s\n");
145 break;
146 }
147 fprintf (stdout, fmt, nick, message);
148 GNUNET_free (nick);
149 return GNUNET_OK;
150}
151
152
153/**
154 * Callback used for message delivery confirmations.
155 *
156 * @param cls closure, NULL
157 * @param room in which room was the message received?
158 * @param orig_seq_number sequence number of the original message
159 * @param timestamp when was the message received?
160 * @param receiver who is confirming the receipt?
161 * @param msg_hash hash of the original message
162 * @param receipt signature confirming delivery
163 * @return GNUNET_OK to continue, GNUNET_SYSERR to refuse processing further
164 * confirmations from anyone for this message
165 */
166static int
167confirmation_cb (void *cls,
168 struct GNUNET_CHAT_Room *room,
169 uint32_t orig_seq_number,
170 struct GNUNET_TIME_Absolute timestamp,
171 const GNUNET_HashCode *receiver,
172 const GNUNET_HashCode *msg_hash,
173 const struct GNUNET_CRYPTO_RsaSignature *receipt)
174{
175 char *nick;
176
177 nick = GNUNET_PSEUDONYM_id_to_name (cfg, receiver);
178 fprintf (stdout, _("'%s' acknowledged message #%d\n"), nick, orig_seq_number);
179 return GNUNET_OK;
180}
181
182
183/**
184 * Callback used for notification that another room member has joined or left.
185 *
186 * @param member_info will be non-null if the member is joining, NULL if he is
187 * leaving
188 * @param member_id hash of public key of the user (for unique identification)
189 * @param options what types of messages is this member willing to receive?
190 * @return GNUNET_OK
191 */
192static int
193member_list_cb (void *cls,
194 const struct GNUNET_CONTAINER_MetaData *member_info,
195 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id,
196 enum GNUNET_CHAT_MsgOptions options)
197{
198 char *nick;
199 GNUNET_HashCode id;
200 struct UserList *pos;
201 struct UserList *prev;
202
203 GNUNET_CRYPTO_hash (member_id,
204 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
205 &id);
206 nick = GNUNET_PSEUDONYM_id_to_name (cfg, &id);
207 fprintf (stdout, member_info != NULL
208 ? _("`%s' entered the room\n") : _("`%s' left the room\n"), nick);
209 GNUNET_free (nick);
210 if (NULL != member_info)
211 {
212 /* user joining */
213 pos = GNUNET_malloc (sizeof (struct UserList));
214 pos->next = users;
215 pos->pkey = *member_id;
216 pos->ignored = GNUNET_NO;
217 users = pos;
218 }
219 else
220 {
221 /* user leaving */
222 prev = NULL;
223 pos = users;
224 while ((NULL != pos) &&
225 (0 != memcmp (&pos->pkey,
226 member_id,
227 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded))))
228 {
229 prev = pos;
230 pos = pos->next;
231 }
232 if (NULL == pos)
233 {
234 GNUNET_break (0);
235 }
236 else
237 {
238 if (NULL == prev)
239 users = pos->next;
240 else
241 prev->next = pos->next;
242 GNUNET_free (pos);
243 }
244 }
245 return GNUNET_OK;
246}
247
248
249static int
250do_transmit (const char *msg, const void *xtra)
251{
252 uint32_t seq;
253 GNUNET_CHAT_send_message (room,
254 msg,
255 GNUNET_CHAT_MSG_OPTION_NONE,
256 NULL, &seq);
257 return GNUNET_OK;
258}
259
260
261static int
262do_join (const char *arg, const void *xtra)
263{
264 char *my_name;
265 GNUNET_HashCode me;
266
267 if (arg[0] == '#')
268 arg++; /* ignore first hash */
269 GNUNET_CHAT_leave_room (room);
270 free_user_list ();
271 GNUNET_free (room_name);
272 room_name = GNUNET_strdup (arg);
273 room = GNUNET_CHAT_join_room (cfg,
274 nickname,
275 meta,
276 room_name,
277 -1,
278 &receive_cb, NULL,
279 &member_list_cb, NULL,
280 &confirmation_cb, NULL, &me);
281 if (NULL == room)
282 {
283 fprintf (stdout, _("Could not change username\n"));
284 return GNUNET_SYSERR;
285 }
286 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
287 fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
288 GNUNET_free (my_name);
289 return GNUNET_OK;
290}
291
292
293static int
294do_nick (const char *msg, const void *xtra)
295{
296 char *my_name;
297 GNUNET_HashCode me;
298
299 GNUNET_CHAT_leave_room (room);
300 free_user_list ();
301 GNUNET_free (nickname);
302 GNUNET_CONTAINER_meta_data_destroy (meta);
303 nickname = GNUNET_strdup (msg);
304 meta = GNUNET_CONTAINER_meta_data_create ();
305 GNUNET_CONTAINER_meta_data_insert (meta,
306 "<gnunet>",
307 EXTRACTOR_METATYPE_TITLE,
308 EXTRACTOR_METAFORMAT_UTF8,
309 "text/plain",
310 nickname,
311 strlen(nickname)+1);
312 room = GNUNET_CHAT_join_room (cfg,
313 nickname,
314 meta,
315 room_name,
316 -1,
317 &receive_cb, NULL,
318 &member_list_cb, NULL,
319 &confirmation_cb, NULL, &me);
320 if (NULL == room)
321 {
322 fprintf (stdout, _("Could not change username\n"));
323 return GNUNET_SYSERR;
324 }
325 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
326 fprintf (stdout, _("Changed username to `%s'\n"), my_name);
327 GNUNET_free (my_name);
328 return GNUNET_OK;
329}
330
331
332static int
333do_names (const char *msg, const void *xtra)
334{
335 char *name;
336 struct UserList *pos;
337 GNUNET_HashCode pid;
338
339 fprintf (stdout, _("Users in room `%s': "), room_name);
340 pos = users;
341 while (NULL != pos)
342 {
343 GNUNET_CRYPTO_hash (&pos->pkey,
344 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
345 &pid);
346 name = GNUNET_PSEUDONYM_id_to_name (cfg, &pid);
347 fprintf (stdout, "`%s' ", name);
348 GNUNET_free (name);
349 pos = pos->next;
350 }
351 fprintf (stdout, "\n");
352 return GNUNET_OK;
353}
354
355
356static int
357do_pm (const char *msg, const void *xtra)
358{
359 char *user;
360 GNUNET_HashCode uid;
361 GNUNET_HashCode pid;
362 uint32_t seq;
363 struct UserList *pos;
364
365 if (NULL == strstr (msg, " "))
366 {
367 fprintf (stderr, _("Syntax: /msg USERNAME MESSAGE"));
368 return GNUNET_OK;
369 }
370 user = GNUNET_strdup (msg);
371 strstr (user, " ")[0] = '\0';
372 msg += strlen (user) + 1;
373 if (GNUNET_OK != GNUNET_PSEUDONYM_name_to_id (cfg, user, &uid))
374 {
375 fprintf (stderr, _("Unknown user `%s'\n"), user);
376 GNUNET_free (user);
377 return GNUNET_OK;
378 }
379 pos = users;
380 while (NULL != pos)
381 {
382 GNUNET_CRYPTO_hash (&pos->pkey,
383 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
384 &pid);
385 if (0 == memcmp (&pid, &uid, sizeof (GNUNET_HashCode)))
386 break;
387 pos = pos->next;
388 }
389 if (NULL == pos)
390 {
391 fprintf (stderr, _("User `%s' is currently not in the room!\n"), user);
392 GNUNET_free (user);
393 return GNUNET_OK;
394 }
395 GNUNET_CHAT_send_message (room,
396 msg,
397 GNUNET_CHAT_MSG_PRIVATE,
398 &pos->pkey,
399 &seq);
400 GNUNET_free (user);
401 return GNUNET_OK;
402}
403
404
405static int
406do_transmit_sig (const char *msg, const void *xtra)
407{
408 uint32_t seq;
409 GNUNET_CHAT_send_message (room,
410 msg,
411 GNUNET_CHAT_MSG_AUTHENTICATED,
412 NULL, &seq);
413 return GNUNET_OK;
414}
415
416
417static int
418do_transmit_ack (const char *msg, const void *xtra)
419{
420 uint32_t seq;
421 GNUNET_CHAT_send_message (room,
422 msg,
423 GNUNET_CHAT_MSG_ACKNOWLEDGED,
424 NULL, &seq);
425 return GNUNET_OK;
426}
427
428
429static int
430do_quit (const char *args, const void *xtra)
431{
432 return GNUNET_SYSERR;
433}
434
435
436static int
437do_unknown (const char *msg, const void *xtra)
438{
439 fprintf (stderr, _("Unknown command `%s'\n"), msg);
440 return GNUNET_OK;
441}
442
443
444/**
445 * List of supported IRC commands. The order matters!
446 */
447static struct ChatCommand commands[] = {
448 {"/join ", &do_join,
449 gettext_noop
450 ("Use `/join #roomname' to join a chat room. Joining a room will cause you"
451 " to leave the current room")},
452 {"/nick ", &do_nick,
453 gettext_noop
454 ("Use `/nick nickname' to change your nickname. This will cause you to"
455 " leave the current room and immediately rejoin it with the new name.")},
456 {"/msg ", &do_pm,
457 gettext_noop
458 ("Use `/msg nickname message' to send a private message to the specified"
459 " user")},
460 {"/notice ", &do_pm,
461 gettext_noop ("The `/notice' command is an alias for `/msg'")},
462 {"/query ", &do_pm,
463 gettext_noop ("The `/query' command is an alias for `/msg'")},
464 {"/sig ", &do_transmit_sig,
465 gettext_noop ("Use `/sig message' to send a signed public message")},
466 {"/ack ", &do_transmit_ack,
467 gettext_noop
468 ("Use `/ack message' to require signed acknowledgment of the message")},
469 {"/quit", &do_quit,
470 gettext_noop ("Use `/quit' to terminate gnunet-chat")},
471 {"/leave", &do_quit,
472 gettext_noop ("The `/leave' command is an alias for `/quit'")},
473 {"/names", &do_names,
474 gettext_noop
475 ("Use `/names' to list all of the current members in the chat room")},
476 {"/help", &do_help,
477 gettext_noop ("Use `/help command' to get help for a specific command")},
478 /* Add standard commands:
479 /whois (print metadata),
480 /ignore (set flag, check on receive!) */
481 /* Add special commands (currently supported):
482 + anonymous msgs
483 + authenticated msgs
484 */
485 /* the following three commands must be last! */
486 {"/", &do_unknown, NULL},
487 {"", &do_transmit, NULL},
488 {NULL, NULL, NULL},
489};
490
491
492static int
493do_help (const char *args, const void *xtra)
494{
495 int i;
496 i = 0;
497 while ((NULL != args) &&
498 (0 != strlen (args)) && (commands[i].Action != &do_help))
499 {
500 if (0 ==
501 strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
502 {
503 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
504 return GNUNET_OK;
505 }
506 i++;
507 }
508 i = 0;
509 fprintf (stdout, "Available commands:");
510 while (commands[i].Action != &do_help)
511 {
512 fprintf (stdout, " %s", gettext (commands[i].command));
513 i++;
514 }
515 fprintf (stdout, "\n");
516 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
517 return GNUNET_OK;
518}
519
520
521static void
522do_stop_task (void *cls,
523 const struct GNUNET_SCHEDULER_TaskContext *tc)
524{
525 GNUNET_CHAT_leave_room (room);
526 if (handle_cmd_task != GNUNET_SCHEDULER_NO_TASK)
527 {
528 GNUNET_SCHEDULER_cancel (handle_cmd_task);
529 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
530 }
531 free_user_list ();
532 GNUNET_CONTAINER_meta_data_destroy (meta);
533 GNUNET_free (room_name);
534 GNUNET_free (nickname);
535}
536
537
538void
539handle_command (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
540{
541 char message[MAX_MESSAGE_LENGTH + 1];
542 int i;
543
544 /* read message from command line and handle it */
545 memset (message, 0, MAX_MESSAGE_LENGTH + 1);
546 if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin))
547 goto next;
548 if (strlen (message) == 0)
549 goto next;
550 if (message[strlen (message) - 1] == '\n')
551 message[strlen (message) - 1] = '\0';
552 if (strlen (message) == 0)
553 goto next;
554 i = 0;
555 while ((NULL != commands[i].command) &&
556 (0 != strncasecmp (commands[i].command,
557 message, strlen (commands[i].command))))
558 i++;
559 if (GNUNET_OK !=
560 commands[i].Action (&message[strlen (commands[i].command)], NULL))
561 goto out;
562
563next:
564 handle_cmd_task =
565 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
566 100),
567 &handle_command,
568 NULL);
569 return;
570
571out:
572 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
573 GNUNET_SCHEDULER_shutdown ();
574}
575
576
577/**
578 * Main function that will be run by the scheduler.
579 *
580 * @param cls closure, NULL
581 * @param args remaining command-line arguments
582 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
583 * @param cfg configuration
584 */
585static void
586run (void *cls,
587 char *const *args,
588 const char *cfgfile,
589 const struct GNUNET_CONFIGURATION_Handle *c)
590{
591 GNUNET_HashCode me;
592 char *my_name;
593
594 cfg = c;
595 /* check arguments */
596 if (NULL == nickname)
597 {
598 fprintf (stderr, _("You must specify a nickname\n"));
599 ret = -1;
600 return;
601 }
602 if (NULL == room_name)
603 room_name = GNUNET_strdup ("gnunet");
604 meta = GNUNET_CONTAINER_meta_data_create ();
605 GNUNET_CONTAINER_meta_data_insert (meta,
606 "<gnunet>",
607 EXTRACTOR_METATYPE_TITLE,
608 EXTRACTOR_METAFORMAT_UTF8,
609 "text/plain",
610 nickname,
611 strlen(nickname)+1);
612 room = GNUNET_CHAT_join_room (cfg,
613 nickname,
614 meta,
615 room_name,
616 -1,
617 &receive_cb, NULL,
618 &member_list_cb, NULL,
619 &confirmation_cb, NULL, &me);
620 if (NULL == room)
621 {
622 fprintf (stderr, _("Failed to join room `%s'\n"), room_name);
623 GNUNET_free (room_name);
624 GNUNET_free (nickname);
625 GNUNET_CONTAINER_meta_data_destroy (meta);
626 ret = -1;
627 return;
628 }
629 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
630 fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
631 GNUNET_free (my_name);
632 handle_cmd_task =
633 GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
634 &handle_command,
635 NULL);
636 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
637 &do_stop_task,
638 NULL);
639}
640
641
642/**
643 * The main function to chat via GNUnet.
644 *
645 * @param argc number of arguments from the command line
646 * @param argv command line arguments
647 * @return 0 ok, 1 on error
648 */
649int
650main (int argc, char *const *argv)
651{
652 int flags;
653 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
654 {'n', "nick", "NAME",
655 gettext_noop ("set the nickname to use (required)"),
656 1, &GNUNET_GETOPT_set_string, &nickname},
657 {'r', "room", "NAME",
658 gettext_noop ("set the chat room to join"),
659 1, &GNUNET_GETOPT_set_string, &room_name},
660 GNUNET_GETOPT_OPTION_END
661 };
662
663 flags = fcntl (0, F_GETFL, 0);
664 flags |= O_NONBLOCK;
665 fcntl (0, F_SETFL, flags);
666 return (GNUNET_OK ==
667 GNUNET_PROGRAM_run (argc,
668 argv,
669 "gnunet-chat",
670 gettext_noop ("Join a chat on GNUnet."),
671 options, &run, NULL)) ? ret : 1;
672}
673
674/* end of gnunet-chat.c */