diff options
Diffstat (limited to 'src/cli/messenger')
-rw-r--r-- | src/cli/messenger/.gitignore | 1 | ||||
-rw-r--r-- | src/cli/messenger/Makefile.am | 25 | ||||
-rw-r--r-- | src/cli/messenger/gnunet-messenger.c | 521 | ||||
-rw-r--r-- | src/cli/messenger/meson.build | 9 |
4 files changed, 556 insertions, 0 deletions
diff --git a/src/cli/messenger/.gitignore b/src/cli/messenger/.gitignore new file mode 100644 index 000000000..1c1447be8 --- /dev/null +++ b/src/cli/messenger/.gitignore | |||
@@ -0,0 +1 @@ | |||
gnunet-messenger | |||
diff --git a/src/cli/messenger/Makefile.am b/src/cli/messenger/Makefile.am new file mode 100644 index 000000000..741e2b7b9 --- /dev/null +++ b/src/cli/messenger/Makefile.am | |||
@@ -0,0 +1,25 @@ | |||
1 | # This Makefile.am is in the public domain | ||
2 | AM_CPPFLAGS = -I$(top_srcdir)/src/include | ||
3 | |||
4 | if USE_COVERAGE | ||
5 | AM_CFLAGS = --coverage -O0 | ||
6 | XLIB = -lgcov | ||
7 | endif | ||
8 | |||
9 | pkgcfgdir= $(pkgdatadir)/config.d/ | ||
10 | |||
11 | libexecdir= $(pkglibdir)/libexec/ | ||
12 | |||
13 | AM_CLFAGS = -g | ||
14 | |||
15 | bin_PROGRAMS = \ | ||
16 | gnunet-messenger | ||
17 | |||
18 | gnunet_messenger_SOURCES = \ | ||
19 | gnunet-messenger.c | ||
20 | gnunet_messenger_LDADD = \ | ||
21 | $(top_builddir)/src/service/messenger/libgnunetmessenger.la \ | ||
22 | $(top_builddir)/src/service/identity/libgnunetidentity.la \ | ||
23 | $(top_builddir)/src/lib/util/libgnunetutil.la | ||
24 | gnunet_messenger_LDFLAGS = \ | ||
25 | $(GN_LIBINTL) | ||
diff --git a/src/cli/messenger/gnunet-messenger.c b/src/cli/messenger/gnunet-messenger.c new file mode 100644 index 000000000..7b2b8c918 --- /dev/null +++ b/src/cli/messenger/gnunet-messenger.c | |||
@@ -0,0 +1,521 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | Copyright (C) 2020--2024 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your 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 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | /** | ||
21 | * @author Tobias Frisch | ||
22 | * @file src/messenger/gnunet-messenger.c | ||
23 | * @brief Print information about messenger groups. | ||
24 | */ | ||
25 | |||
26 | #include <stdio.h> | ||
27 | #include <unistd.h> | ||
28 | |||
29 | #include "gnunet_identity_service.h" | ||
30 | #include "gnunet_messenger_service.h" | ||
31 | #include "gnunet_util_lib.h" | ||
32 | |||
33 | const struct GNUNET_CONFIGURATION_Handle *config; | ||
34 | struct GNUNET_MESSENGER_Handle *messenger; | ||
35 | |||
36 | uint64_t waiting; | ||
37 | |||
38 | struct GNUNET_SCHEDULER_Task *read_task; | ||
39 | int talk_mode; | ||
40 | |||
41 | /** | ||
42 | * Delay forced shutdown by input to wait for data processing. | ||
43 | * | ||
44 | * @param[in,out] cls Closure | ||
45 | */ | ||
46 | static void | ||
47 | delay_shutdown (void *cls) | ||
48 | { | ||
49 | read_task = NULL; | ||
50 | |||
51 | if (waiting) | ||
52 | return; | ||
53 | |||
54 | GNUNET_SCHEDULER_shutdown (); | ||
55 | } | ||
56 | |||
57 | static void | ||
58 | idle (void *cls); | ||
59 | |||
60 | /** | ||
61 | * Function called whenever a message is received or sent. | ||
62 | * | ||
63 | * @param[in,out] cls Closure | ||
64 | * @param[in] room Room | ||
65 | * @param[in] sender Sender of message | ||
66 | * @param[in] message Message | ||
67 | * @param[in] hash Hash of message | ||
68 | * @param[in] flags Flags of message | ||
69 | */ | ||
70 | void | ||
71 | on_message (void *cls, | ||
72 | struct GNUNET_MESSENGER_Room *room, | ||
73 | const struct GNUNET_MESSENGER_Contact *sender, | ||
74 | const struct GNUNET_MESSENGER_Contact *recipient, | ||
75 | const struct GNUNET_MESSENGER_Message *message, | ||
76 | const struct GNUNET_HashCode *hash, | ||
77 | enum GNUNET_MESSENGER_MessageFlags flags) | ||
78 | { | ||
79 | const uint64_t waited = waiting; | ||
80 | |||
81 | if (GNUNET_YES == talk_mode) | ||
82 | { | ||
83 | if (GNUNET_MESSENGER_KIND_TALK == message->header.kind) | ||
84 | { | ||
85 | if (flags & GNUNET_MESSENGER_FLAG_SENT) | ||
86 | { | ||
87 | waiting = waiting > message->body.talk.length? | ||
88 | waiting - message->body.talk.length : 0; | ||
89 | } | ||
90 | else | ||
91 | { | ||
92 | write (1, message->body.talk.data, message->body.talk.length); | ||
93 | fflush (stdout); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | goto skip_printing; | ||
98 | } | ||
99 | |||
100 | const char *sender_name = GNUNET_MESSENGER_contact_get_name (sender); | ||
101 | const char *recipient_name = GNUNET_MESSENGER_contact_get_name (recipient); | ||
102 | |||
103 | if (! sender_name) | ||
104 | sender_name = "anonymous"; | ||
105 | |||
106 | if (! recipient_name) | ||
107 | recipient_name = "anonymous"; | ||
108 | |||
109 | printf ("[%s ->", GNUNET_h2s (&(message->header.previous))); | ||
110 | printf (" %s]", GNUNET_h2s (hash)); | ||
111 | printf ("[%s] ", GNUNET_sh2s (&(message->header.sender_id))); | ||
112 | |||
113 | if (flags & GNUNET_MESSENGER_FLAG_PRIVATE) | ||
114 | printf ("*( '%s' ) ", recipient_name); | ||
115 | |||
116 | switch (message->header.kind) | ||
117 | { | ||
118 | case GNUNET_MESSENGER_KIND_JOIN: | ||
119 | { | ||
120 | printf ("* '%s' joined the room!\n", sender_name); | ||
121 | break; | ||
122 | } | ||
123 | case GNUNET_MESSENGER_KIND_NAME: | ||
124 | { | ||
125 | printf ("* '%s' gets renamed to '%s'\n", sender_name, | ||
126 | message->body.name.name); | ||
127 | break; | ||
128 | } | ||
129 | case GNUNET_MESSENGER_KIND_LEAVE: | ||
130 | { | ||
131 | printf ("* '%s' leaves the room!\n", sender_name); | ||
132 | break; | ||
133 | } | ||
134 | case GNUNET_MESSENGER_KIND_PEER: | ||
135 | { | ||
136 | printf ("* '%s' opened the room on: %s\n", sender_name, | ||
137 | GNUNET_i2s_full (&(message->body.peer.peer))); | ||
138 | break; | ||
139 | } | ||
140 | case GNUNET_MESSENGER_KIND_TEXT: | ||
141 | { | ||
142 | const uint16_t len = strlen (message->body.text.text) + 1; | ||
143 | |||
144 | if (flags & GNUNET_MESSENGER_FLAG_SENT) | ||
145 | { | ||
146 | waiting = waiting > len? waiting - len : 0; | ||
147 | |||
148 | printf (">"); | ||
149 | } | ||
150 | else | ||
151 | printf ("<"); | ||
152 | |||
153 | printf (" '%s' says: \"%s\"\n", sender_name, | ||
154 | message->body.text.text); | ||
155 | break; | ||
156 | } | ||
157 | case GNUNET_MESSENGER_KIND_FILE: | ||
158 | { | ||
159 | if (flags & GNUNET_MESSENGER_FLAG_SENT) | ||
160 | printf (">"); | ||
161 | else | ||
162 | printf ("<"); | ||
163 | |||
164 | printf(" '%s' shares: \"%s\"\n%s\n", sender_name, | ||
165 | message->body.file.name, message->body.file.uri); | ||
166 | break; | ||
167 | } | ||
168 | default: | ||
169 | { | ||
170 | printf ("~ message: %s\n", | ||
171 | GNUNET_MESSENGER_name_of_kind (message->header.kind)); | ||
172 | break; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | skip_printing: | ||
177 | if ((! read_task) && (! waiting) && (waited)) | ||
178 | read_task = GNUNET_SCHEDULER_add_with_priority ( | ||
179 | GNUNET_SCHEDULER_PRIORITY_IDLE, | ||
180 | delay_shutdown, NULL); | ||
181 | |||
182 | if ((GNUNET_MESSENGER_KIND_JOIN == message->header.kind) && | ||
183 | (flags & GNUNET_MESSENGER_FLAG_SENT)) | ||
184 | { | ||
185 | if (! read_task) | ||
186 | read_task = GNUNET_SCHEDULER_add_with_priority ( | ||
187 | GNUNET_SCHEDULER_PRIORITY_IDLE, | ||
188 | idle, room); | ||
189 | |||
190 | const char *name = GNUNET_MESSENGER_get_name (messenger); | ||
191 | |||
192 | if (! name) | ||
193 | return; | ||
194 | |||
195 | struct GNUNET_MESSENGER_Message response; | ||
196 | response.header.kind = GNUNET_MESSENGER_KIND_NAME; | ||
197 | response.body.name.name = GNUNET_strdup (name); | ||
198 | |||
199 | GNUNET_MESSENGER_send_message (room, &response, NULL); | ||
200 | |||
201 | GNUNET_free (response.body.name.name); | ||
202 | |||
203 | if (GNUNET_YES != talk_mode) | ||
204 | return; | ||
205 | |||
206 | response.header.kind = GNUNET_MESSENGER_KIND_SUBSCRIBE; | ||
207 | response.body.subscribe.flags = GNUNET_MESSENGER_FLAG_SUBSCRIPTION_KEEP_ALIVE; | ||
208 | response.body.subscribe.time = | ||
209 | GNUNET_TIME_relative_hton (GNUNET_TIME_relative_get_second_()); | ||
210 | |||
211 | memset(&(response.body.subscribe.discourse), 0, | ||
212 | sizeof(response.body.subscribe.discourse)); | ||
213 | |||
214 | GNUNET_MESSENGER_send_message (room, &response, NULL); | ||
215 | } | ||
216 | } | ||
217 | |||
218 | |||
219 | struct GNUNET_IDENTITY_EgoLookup *ego_lookup; | ||
220 | |||
221 | /** | ||
222 | * Task to shut down this application. | ||
223 | * | ||
224 | * @param[in,out] cls Closure | ||
225 | */ | ||
226 | static void | ||
227 | shutdown_hook (void *cls) | ||
228 | { | ||
229 | struct GNUNET_MESSENGER_Room *room = cls; | ||
230 | |||
231 | if (read_task) | ||
232 | GNUNET_SCHEDULER_cancel (read_task); | ||
233 | |||
234 | if (room) | ||
235 | GNUNET_MESSENGER_close_room (room); | ||
236 | |||
237 | if (messenger) | ||
238 | GNUNET_MESSENGER_disconnect (messenger); | ||
239 | |||
240 | if (ego_lookup) | ||
241 | GNUNET_IDENTITY_ego_lookup_cancel (ego_lookup); | ||
242 | } | ||
243 | |||
244 | |||
245 | static void | ||
246 | listen_stdio (void *cls); | ||
247 | |||
248 | #define MAX_BUFFER_SIZE 57345 | ||
249 | |||
250 | static int | ||
251 | iterate_send_private_message (void *cls, | ||
252 | struct GNUNET_MESSENGER_Room *room, | ||
253 | const struct GNUNET_MESSENGER_Contact *contact) | ||
254 | { | ||
255 | struct GNUNET_MESSENGER_Message *message = cls; | ||
256 | |||
257 | if (GNUNET_MESSENGER_contact_get_key (contact)) | ||
258 | GNUNET_MESSENGER_send_message (room, message, contact); | ||
259 | |||
260 | return GNUNET_YES; | ||
261 | } | ||
262 | |||
263 | |||
264 | int private_mode; | ||
265 | |||
266 | /** | ||
267 | * Task run in stdio mode, after some data is available at stdin. | ||
268 | * | ||
269 | * @param[in,out] cls Closure | ||
270 | */ | ||
271 | static void | ||
272 | read_stdio (void *cls) | ||
273 | { | ||
274 | struct GNUNET_MESSENGER_Room *room = cls; | ||
275 | struct GNUNET_MESSENGER_Message message; | ||
276 | |||
277 | read_task = NULL; | ||
278 | |||
279 | char buffer[MAX_BUFFER_SIZE]; | ||
280 | ssize_t length; | ||
281 | |||
282 | length = read (0, buffer, MAX_BUFFER_SIZE - 1); | ||
283 | |||
284 | if ((length <= 0) || (length >= MAX_BUFFER_SIZE)) | ||
285 | { | ||
286 | delay_shutdown (NULL); | ||
287 | return; | ||
288 | } | ||
289 | |||
290 | waiting += length; | ||
291 | |||
292 | if (GNUNET_YES == talk_mode) | ||
293 | { | ||
294 | message.header.kind = GNUNET_MESSENGER_KIND_TALK; | ||
295 | message.body.talk.length = length; | ||
296 | message.body.talk.data = buffer; | ||
297 | |||
298 | memset(&(message.body.talk.discourse), 0, | ||
299 | sizeof(message.body.talk.discourse)); | ||
300 | } | ||
301 | else | ||
302 | { | ||
303 | if (buffer[length - 1] == '\n') | ||
304 | buffer[length - 1] = '\0'; | ||
305 | else | ||
306 | buffer[length] = '\0'; | ||
307 | |||
308 | message.header.kind = GNUNET_MESSENGER_KIND_TEXT; | ||
309 | message.body.text.text = buffer; | ||
310 | } | ||
311 | |||
312 | if (GNUNET_YES == private_mode) | ||
313 | GNUNET_MESSENGER_iterate_members (room, iterate_send_private_message, | ||
314 | &message); | ||
315 | else | ||
316 | GNUNET_MESSENGER_send_message (room, &message, NULL); | ||
317 | |||
318 | read_task = GNUNET_SCHEDULER_add_now (listen_stdio, cls); | ||
319 | } | ||
320 | |||
321 | |||
322 | /** | ||
323 | * Wait for input on STDIO and send it out over the #ch. | ||
324 | * | ||
325 | * @param[in,out] cls Closure | ||
326 | */ | ||
327 | static void | ||
328 | listen_stdio (void *cls) | ||
329 | { | ||
330 | read_task = NULL; | ||
331 | |||
332 | struct GNUNET_NETWORK_FDSet *rs = GNUNET_NETWORK_fdset_create (); | ||
333 | |||
334 | GNUNET_NETWORK_fdset_set_native (rs, 0); | ||
335 | |||
336 | read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, | ||
337 | GNUNET_TIME_UNIT_FOREVER_REL, rs, | ||
338 | NULL, &read_stdio, cls); | ||
339 | |||
340 | GNUNET_NETWORK_fdset_destroy (rs); | ||
341 | } | ||
342 | |||
343 | |||
344 | /** | ||
345 | * Initial task to startup application. | ||
346 | * | ||
347 | * @param[in,out] cls Closure | ||
348 | */ | ||
349 | static void | ||
350 | idle (void *cls) | ||
351 | { | ||
352 | struct GNUNET_MESSENGER_Room *room = cls; | ||
353 | |||
354 | if (GNUNET_YES != talk_mode) | ||
355 | printf ("* You joined the room.\n"); | ||
356 | |||
357 | read_task = GNUNET_SCHEDULER_add_now (listen_stdio, room); | ||
358 | } | ||
359 | |||
360 | |||
361 | char *door_id; | ||
362 | char *ego_name; | ||
363 | char *room_key; | ||
364 | |||
365 | struct GNUNET_SCHEDULER_Task *shutdown_task; | ||
366 | |||
367 | /** | ||
368 | * Function called when an identity is retrieved. | ||
369 | * | ||
370 | * @param[in,out] cls Closure | ||
371 | * @param[in,out] handle Handle of messenger service | ||
372 | */ | ||
373 | static void | ||
374 | on_identity (void *cls, | ||
375 | struct GNUNET_MESSENGER_Handle *handle) | ||
376 | { | ||
377 | struct GNUNET_HashCode key; | ||
378 | memset (&key, 0, sizeof(key)); | ||
379 | |||
380 | if (room_key) | ||
381 | GNUNET_CRYPTO_hash (room_key, strlen (room_key), &key); | ||
382 | |||
383 | struct GNUNET_PeerIdentity door_peer; | ||
384 | struct GNUNET_PeerIdentity *door = NULL; | ||
385 | |||
386 | if ((door_id) && | ||
387 | (GNUNET_OK == GNUNET_CRYPTO_eddsa_public_key_from_string (door_id, | ||
388 | strlen ( | ||
389 | door_id), | ||
390 | &(door_peer. | ||
391 | public_key)))) | ||
392 | door = &door_peer; | ||
393 | |||
394 | struct GNUNET_MESSENGER_Room *room; | ||
395 | |||
396 | if (GNUNET_YES == talk_mode) | ||
397 | goto skip_welcome; | ||
398 | |||
399 | const char *name = GNUNET_MESSENGER_get_name (handle); | ||
400 | |||
401 | if (! name) | ||
402 | name = "anonymous"; | ||
403 | |||
404 | printf ("* Welcome to the messenger, '%s'!\n", name); | ||
405 | |||
406 | skip_welcome: | ||
407 | if (door) | ||
408 | { | ||
409 | if (GNUNET_YES != talk_mode) | ||
410 | printf ("* You try to entry a room...\n"); | ||
411 | |||
412 | room = GNUNET_MESSENGER_enter_room (messenger, door, &key); | ||
413 | } | ||
414 | else | ||
415 | { | ||
416 | if (GNUNET_YES != talk_mode) | ||
417 | printf ("* You try to open a room...\n"); | ||
418 | |||
419 | room = GNUNET_MESSENGER_open_room (messenger, &key); | ||
420 | } | ||
421 | |||
422 | GNUNET_SCHEDULER_cancel (shutdown_task); | ||
423 | |||
424 | shutdown_task = GNUNET_SCHEDULER_add_shutdown (shutdown_hook, room); | ||
425 | |||
426 | waiting = 0; | ||
427 | |||
428 | if (! room) | ||
429 | GNUNET_SCHEDULER_shutdown (); | ||
430 | else | ||
431 | read_task = NULL; | ||
432 | } | ||
433 | |||
434 | |||
435 | static void | ||
436 | on_ego_lookup (void *cls, | ||
437 | struct GNUNET_IDENTITY_Ego *ego) | ||
438 | { | ||
439 | ego_lookup = NULL; | ||
440 | |||
441 | const struct GNUNET_CRYPTO_PrivateKey *key; | ||
442 | key = ego ? GNUNET_IDENTITY_ego_get_private_key (ego) : NULL; | ||
443 | |||
444 | messenger = GNUNET_MESSENGER_connect (config, ego_name, key, &on_message, | ||
445 | NULL); | ||
446 | |||
447 | on_identity (NULL, messenger); | ||
448 | } | ||
449 | |||
450 | |||
451 | /** | ||
452 | * Main function that will be run by the scheduler. | ||
453 | * | ||
454 | * @param[in/out] cls closure | ||
455 | * @param[in] args remaining command-line arguments | ||
456 | * @param[in] cfgfile name of the configuration file used (for saving, can be NULL!) | ||
457 | * @param[in] cfg configuration | ||
458 | */ | ||
459 | static void | ||
460 | run (void *cls, | ||
461 | char *const *args, | ||
462 | const char *cfgfile, | ||
463 | const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
464 | { | ||
465 | config = cfg; | ||
466 | |||
467 | if (ego_name) | ||
468 | { | ||
469 | ego_lookup = GNUNET_IDENTITY_ego_lookup (cfg, ego_name, &on_ego_lookup, | ||
470 | NULL); | ||
471 | messenger = NULL; | ||
472 | } | ||
473 | else | ||
474 | { | ||
475 | ego_lookup = NULL; | ||
476 | messenger = GNUNET_MESSENGER_connect (cfg, NULL, NULL, &on_message, NULL); | ||
477 | } | ||
478 | |||
479 | shutdown_task = GNUNET_SCHEDULER_add_shutdown (shutdown_hook, NULL); | ||
480 | |||
481 | if (messenger) | ||
482 | on_identity (NULL, messenger); | ||
483 | } | ||
484 | |||
485 | |||
486 | /** | ||
487 | * The main function to obtain messenger information. | ||
488 | * | ||
489 | * @param[in] argc number of arguments from the command line | ||
490 | * @param[in] argv command line arguments | ||
491 | * @return #EXIT_SUCCESS ok, #EXIT_FAILURE on error | ||
492 | */ | ||
493 | int | ||
494 | main (int argc, | ||
495 | char **argv) | ||
496 | { | ||
497 | const char *description = | ||
498 | "Open and connect to rooms using the MESSENGER to chat."; | ||
499 | |||
500 | struct GNUNET_GETOPT_CommandLineOption options[] = { | ||
501 | GNUNET_GETOPT_option_string ('d', "door", "PEERIDENTITY", | ||
502 | "peer identity to entry into the room", | ||
503 | &door_id), | ||
504 | GNUNET_GETOPT_option_string ('e', "ego", "IDENTITY", | ||
505 | "identity to use for messaging", | ||
506 | &ego_name), | ||
507 | GNUNET_GETOPT_option_string ('r', "room", "ROOMKEY", | ||
508 | "key of the room to connect to", | ||
509 | &room_key), | ||
510 | GNUNET_GETOPT_option_flag ('p', "private", "flag to enable private mode", | ||
511 | &private_mode), | ||
512 | GNUNET_GETOPT_option_flag ('t', "talk", "flag to enable talk mode", | ||
513 | &talk_mode), | ||
514 | GNUNET_GETOPT_OPTION_END | ||
515 | }; | ||
516 | |||
517 | return (GNUNET_OK == GNUNET_PROGRAM_run (argc, argv, "gnunet-messenger\0", | ||
518 | gettext_noop (description), options, | ||
519 | &run, | ||
520 | NULL) ? EXIT_SUCCESS : EXIT_FAILURE); | ||
521 | } | ||
diff --git a/src/cli/messenger/meson.build b/src/cli/messenger/meson.build new file mode 100644 index 000000000..3a3870c9d --- /dev/null +++ b/src/cli/messenger/meson.build | |||
@@ -0,0 +1,9 @@ | |||
1 | executable ('gnunet-messenger', | ||
2 | 'gnunet-messenger.c', | ||
3 | dependencies: [libgnunetmessenger_dep, | ||
4 | libgnunetidentity_dep, | ||
5 | libgnunetutil_dep], | ||
6 | include_directories: [incdir, configuration_inc], | ||
7 | install: true, | ||
8 | install_dir: get_option('bindir')) | ||
9 | |||