stem_rs/interpreter/
help.rs1use crate::controller::Controller;
51
52pub async fn response(controller: &mut Controller, arg: &str) -> String {
93 let arg = normalize(arg);
94
95 if arg.is_empty() {
96 return general_help();
97 }
98
99 match arg.as_str() {
100 "HELP" => help_help(),
101 "EVENTS" => help_events(),
102 "INFO" => help_info(),
103 "PYTHON" => help_python(),
104 "QUIT" => help_quit(),
105 "GETINFO" => help_getinfo(controller).await,
106 "GETCONF" => help_getconf(controller).await,
107 "SETCONF" => help_setconf(),
108 "RESETCONF" => help_resetconf(),
109 "SIGNAL" => help_signal(),
110 "SETEVENTS" => help_setevents(controller).await,
111 "USEFEATURE" => help_usefeature(controller).await,
112 "SAVECONF" => help_saveconf(),
113 "LOADCONF" => help_loadconf(),
114 "MAPADDRESS" => help_mapaddress(),
115 "POSTDESCRIPTOR" => help_postdescriptor(),
116 "EXTENDCIRCUIT" => help_extendcircuit(),
117 "SETCIRCUITPURPOSE" => help_setcircuitpurpose(),
118 "CLOSECIRCUIT" => help_closecircuit(),
119 "ATTACHSTREAM" => help_attachstream(),
120 "REDIRECTSTREAM" => help_redirectstream(),
121 "CLOSESTREAM" => help_closestream(),
122 "ADD_ONION" => help_add_onion(),
123 "DEL_ONION" => help_del_onion(),
124 "HSFETCH" => help_hsfetch(),
125 "HSPOST" => help_hspost(),
126 "RESOLVE" => help_resolve(),
127 "TAKEOWNERSHIP" => help_takeownership(),
128 "PROTOCOLINFO" => help_protocolinfo(),
129 _ => format!("No help information available for '{}'...", arg),
130 }
131}
132
133fn normalize(arg: &str) -> String {
137 let arg = arg.to_uppercase();
138 let arg = arg.split_whitespace().next().unwrap_or("");
139 arg.trim_start_matches('/').to_string()
140}
141
142fn general_help() -> String {
144 r#"Interpreter commands include:
145 /help - provides information for interpreter and tor commands
146 /events - prints events that we've received
147 /info - general information for a relay
148 /python - enable or disable support for running python commands
149 /quit - shuts down the interpreter
150
151Tor commands include:
152 GETINFO - queries information from tor
153 GETCONF, SETCONF, RESETCONF - show or edit a configuration option
154 SIGNAL - issues control signal to the process (for resetting, stopping, etc)
155 SETEVENTS - configures the events tor will notify us of
156
157 USEFEATURE - enables custom behavior for the controller
158 SAVECONF - writes tor's current configuration to our torrc
159 LOADCONF - loads the given input like it was part of our torrc
160 MAPADDRESS - replaces requests for one address with another
161 POSTDESCRIPTOR - adds a relay descriptor to our cache
162 EXTENDCIRCUIT - create or extend a tor circuit
163 SETCIRCUITPURPOSE - configures the purpose associated with a circuit
164 CLOSECIRCUIT - closes the given circuit
165 ATTACHSTREAM - associates an application's stream with a tor circuit
166 REDIRECTSTREAM - sets a stream's destination
167 CLOSESTREAM - closes the given stream
168 ADD_ONION - create a new hidden service
169 DEL_ONION - delete a hidden service that was created with ADD_ONION
170 HSFETCH - retrieve a hidden service descriptor
171 HSPOST - uploads a hidden service descriptor
172 RESOLVE - issues an asynchronous dns or rdns request over tor
173 TAKEOWNERSHIP - instructs tor to quit when this control connection is closed
174 PROTOCOLINFO - queries version and controller authentication information
175 QUIT - disconnect the control connection
176
177For more information use '/help [OPTION]'."#
178 .to_string()
179}
180
181fn help_help() -> String {
183 r#"/help [OPTION]
184
185Provides usage information for the given interpreter, tor command, or tor
186configuration option.
187
188Example:
189 /help info # provides a description of the '/info' option
190 /help GETINFO # usage information for tor's GETINFO controller option"#
191 .to_string()
192}
193
194fn help_events() -> String {
196 r#"/events [types]
197
198Provides events that we've received belonging to the given event types. If
199no types are specified then this provides all the messages that we've
200received.
201
202You can also run '/events clear' to clear the backlog of events we've
203received."#
204 .to_string()
205}
206
207fn help_info() -> String {
209 r#"/info [relay fingerprint, nickname, or IP address]
210
211Provides information for a relay that's currently in the consensus. If no
212relay is specified then this provides information on ourselves."#
213 .to_string()
214}
215
216fn help_python() -> String {
218 r#"/python [enable,disable]
219
220Enables or disables support for running python commands. This determines how
221we treat commands this interpreter doesn't recognize...
222
223* If enabled then unrecognized commands are executed as python.
224* If disabled then unrecognized commands are passed along to tor."#
225 .to_string()
226}
227
228fn help_quit() -> String {
230 "/quit\n\nTerminates the interpreter.".to_string()
231}
232
233async fn help_getinfo(controller: &mut Controller) -> String {
235 let mut output =
236 "GETINFO OPTION\n\nQueries the tor process for information. Options are...\n\n".to_string();
237
238 if let Ok(results) = controller.get_info("info/names").await {
239 for line in results.lines() {
240 if let Some((opt, summary)) = line.split_once(" -- ") {
241 output.push_str(&format!("{:<33} - {}\n", opt, summary));
242 }
243 }
244 }
245
246 output
247}
248
249async fn help_getconf(controller: &mut Controller) -> String {
251 let mut output = "GETCONF OPTION\n\nProvides the current value for a given configuration value. Options include...\n\n".to_string();
252
253 if let Ok(results) = controller.get_info("config/names").await {
254 let options: Vec<&str> = results
255 .lines()
256 .filter_map(|line| line.split_whitespace().next())
257 .collect();
258
259 for chunk in options.chunks(2) {
260 let line = chunk
261 .iter()
262 .map(|s| format!("{:<42}", s))
263 .collect::<Vec<_>>()
264 .join("");
265 output.push_str(&format!("{}\n", line.trim_end()));
266 }
267 }
268
269 output
270}
271
272fn help_setconf() -> String {
274 r#"SETCONF PARAM[=VALUE]
275
276Sets the given configuration parameters. Values can be quoted or non-quoted
277strings, and reverts the option to 0 or NULL if not provided.
278
279Examples:
280 * Sets a contact address and resets our family to NULL
281 SETCONF MyFamily ContactInfo=foo@bar.com
282
283 * Sets an exit policy that only includes port 80/443
284 SETCONF ExitPolicy="accept *:80, accept *:443, reject *:*""#
285 .to_string()
286}
287
288fn help_resetconf() -> String {
290 r#"RESETCONF PARAM[=VALUE]
291
292Reverts the given configuration options to their default values. If a value
293is provided then this behaves in the same way as SETCONF.
294
295Examples:
296 * Returns both of our accounting parameters to their defaults
297 RESETCONF AccountingMax AccountingStart
298
299 * Uses the default exit policy and sets our nickname to be 'Goomba'
300 RESETCONF ExitPolicy Nickname=Goomba"#
301 .to_string()
302}
303
304fn help_signal() -> String {
306 r#"SIGNAL SIG
307
308Issues a signal that tells the tor process to reload its torrc, dump its
309stats, halt, etc.
310
311RELOAD / HUP - reload our torrc
312SHUTDOWN / INT - gracefully shut down, waiting 30 seconds if we're a relay
313DUMP / USR1 - logs information about open connections and circuits
314DEBUG / USR2 - makes us log at the DEBUG runlevel
315HALT / TERM - immediately shut down
316CLEARDNSCACHE - clears any cached DNS results
317NEWNYM - clears the DNS cache and uses new circuits for future connections"#
318 .to_string()
319}
320
321async fn help_setevents(controller: &mut Controller) -> String {
323 let mut output = r#"SETEVENTS [EXTENDED] [EVENTS]
324
325Sets the events that we will receive. This turns off any events that aren't
326listed so sending 'SETEVENTS' without any values will turn off all event reporting.
327
328Events include...
329
330"#
331 .to_string();
332
333 if let Ok(results) = controller.get_info("events/names").await {
334 let entries: Vec<&str> = results.split_whitespace().collect();
335 for chunk in entries.chunks(4) {
336 let line = chunk
337 .iter()
338 .map(|s| format!("{:<20}", s))
339 .collect::<Vec<_>>()
340 .join("");
341 output.push_str(&format!("{}\n", line.trim_end()));
342 }
343 }
344
345 output
346}
347
348async fn help_usefeature(controller: &mut Controller) -> String {
350 let mut output =
351 "USEFEATURE OPTION\n\nCustomizes the behavior of the control port. Options include...\n\n"
352 .to_string();
353
354 if let Ok(results) = controller.get_info("features/names").await {
355 output.push_str(&results);
356 output.push('\n');
357 }
358
359 output
360}
361
362fn help_saveconf() -> String {
364 "SAVECONF\n\nWrites Tor's current configuration to its torrc.".to_string()
365}
366
367fn help_loadconf() -> String {
369 r#"LOADCONF...
370
371Reads the given text like it belonged to our torrc.
372
373Example:
374 +LOADCONF
375 # sets our exit policy to just accept ports 80 and 443
376 ExitPolicy accept *:80
377 ExitPolicy accept *:443
378 ExitPolicy reject *:*
379 .
380
381Multi-line control options like this are not yet implemented."#
382 .to_string()
383}
384
385fn help_mapaddress() -> String {
387 r#"MAPADDRESS SOURCE_ADDR=DESTINATION_ADDR
388
389Replaces future requests for one address with another.
390
391Example:
392 MAPADDRESS 0.0.0.0=torproject.org 1.2.3.4=tor.freehaven.net"#
393 .to_string()
394}
395
396fn help_postdescriptor() -> String {
398 r#"POSTDESCRIPTOR [purpose=general/controller/bridge] [cache=yes/no]...
399
400Simulates getting a new relay descriptor.
401
402Multi-line control options like this are not yet implemented."#
403 .to_string()
404}
405
406fn help_extendcircuit() -> String {
408 r#"EXTENDCIRCUIT CircuitID [PATH] [purpose=general/controller]
409
410Extends the given circuit or create a new one if the CircuitID is zero. The
411PATH is a comma separated list of fingerprints. If it isn't set then this
412uses Tor's normal path selection."#
413 .to_string()
414}
415
416fn help_setcircuitpurpose() -> String {
418 "SETCIRCUITPURPOSE CircuitID purpose=general/controller\n\nSets the purpose attribute for a circuit.".to_string()
419}
420
421fn help_closecircuit() -> String {
423 r#"CLOSECIRCUIT CircuitID [IfUnused]
424
425Closes the given circuit. If "IfUnused" is included then this only closes
426the circuit if it isn't currently being used."#
427 .to_string()
428}
429
430fn help_attachstream() -> String {
432 r#"ATTACHSTREAM StreamID CircuitID [HOP=HopNum]
433
434Attaches a stream with the given built circuit (tor picks one on its own if
435CircuitID is zero). If HopNum is given then this hop is used to exit the
436circuit, otherwise the last relay is used."#
437 .to_string()
438}
439
440fn help_redirectstream() -> String {
442 r#"REDIRECTSTREAM StreamID Address [Port]
443
444Sets the destination for a given stream. This can only be done after a
445stream is created but before it's attached to a circuit."#
446 .to_string()
447}
448
449fn help_closestream() -> String {
451 r#"CLOSESTREAM StreamID Reason [Flag]
452
453Closes the given stream, the reason being an integer matching a reason as
454per section 6.3 of the tor-spec."#
455 .to_string()
456}
457
458fn help_add_onion() -> String {
460 r#"KeyType:KeyBlob [Flags=Flag] (Port=Port [,Target])...
461
462Creates a new hidden service. Unlike 'SETCONF HiddenServiceDir...' this
463doesn't persist the service to disk."#
464 .to_string()
465}
466
467fn help_del_onion() -> String {
469 "DEL_ONION ServiceID\n\nDelete a hidden service that was created with ADD_ONION.".to_string()
470}
471
472fn help_hsfetch() -> String {
474 r#"HSFETCH (HSAddress/v2-DescId) [SERVER=Server]...
475
476Retrieves the descriptor for a hidden service. This is an asynchronous
477request, with the descriptor provided by a HS_DESC_CONTENT event."#
478 .to_string()
479}
480
481fn help_hspost() -> String {
483 "HSPOST [SERVER=Server] DESCRIPTOR\n\nUploads a descriptor to a hidden service directory."
484 .to_string()
485}
486
487fn help_resolve() -> String {
489 r#"RESOLVE [mode=reverse] address
490
491Performs IPv4 DNS resolution over tor, doing a reverse lookup instead if
492"mode=reverse" is included. This request is processed in the background and
493results in a ADDRMAP event with the response."#
494 .to_string()
495}
496
497fn help_takeownership() -> String {
499 "TAKEOWNERSHIP\n\nInstructs Tor to gracefully shut down when this control connection is closed."
500 .to_string()
501}
502
503fn help_protocolinfo() -> String {
505 r#"PROTOCOLINFO [ProtocolVersion]
506
507Provides bootstrapping information that a controller might need when first
508starting, like Tor's version and controller authentication. This can be done
509before authenticating to the control port."#
510 .to_string()
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn test_normalize_uppercase() {
519 assert_eq!(normalize("getinfo"), "GETINFO");
520 }
521
522 #[test]
523 fn test_normalize_strips_slash() {
524 assert_eq!(normalize("/help"), "HELP");
525 }
526
527 #[test]
528 fn test_normalize_takes_first_word() {
529 assert_eq!(normalize("GETINFO version"), "GETINFO");
530 }
531
532 #[test]
533 fn test_normalize_empty() {
534 assert_eq!(normalize(""), "");
535 }
536
537 #[test]
538 fn test_general_help_contains_commands() {
539 let help = general_help();
540 assert!(help.contains("/help"));
541 assert!(help.contains("/events"));
542 assert!(help.contains("/info"));
543 assert!(help.contains("/python"));
544 assert!(help.contains("/quit"));
545 assert!(help.contains("GETINFO"));
546 assert!(help.contains("GETCONF"));
547 assert!(help.contains("SETCONF"));
548 assert!(help.contains("SIGNAL"));
549 }
550
551 #[test]
552 fn test_help_help() {
553 let help = help_help();
554 assert!(help.contains("/help"));
555 assert!(help.contains("Example"));
556 }
557
558 #[test]
559 fn test_help_events() {
560 let help = help_events();
561 assert!(help.contains("/events"));
562 assert!(help.contains("clear"));
563 }
564
565 #[test]
566 fn test_help_signal() {
567 let help = help_signal();
568 assert!(help.contains("RELOAD"));
569 assert!(help.contains("SHUTDOWN"));
570 assert!(help.contains("NEWNYM"));
571 }
572
573 #[test]
574 fn test_help_info() {
575 let help = help_info();
576 assert!(help.contains("/info"));
577 assert!(help.contains("fingerprint"));
578 assert!(help.contains("nickname"));
579 }
580
581 #[test]
582 fn test_help_python() {
583 let help = help_python();
584 assert!(help.contains("/python"));
585 assert!(help.contains("enable"));
586 assert!(help.contains("disable"));
587 }
588
589 #[test]
590 fn test_help_quit() {
591 let help = help_quit();
592 assert!(help.contains("/quit"));
593 assert!(help.contains("Terminates"));
594 }
595
596 #[test]
597 fn test_help_setconf() {
598 let help = help_setconf();
599 assert!(help.contains("SETCONF"));
600 assert!(help.contains("Example"));
601 assert!(help.contains("MyFamily"));
602 }
603
604 #[test]
605 fn test_help_resetconf() {
606 let help = help_resetconf();
607 assert!(help.contains("RESETCONF"));
608 assert!(help.contains("default"));
609 assert!(help.contains("Example"));
610 }
611
612 #[test]
613 fn test_help_saveconf() {
614 let help = help_saveconf();
615 assert!(help.contains("SAVECONF"));
616 assert!(help.contains("torrc"));
617 }
618
619 #[test]
620 fn test_help_loadconf() {
621 let help = help_loadconf();
622 assert!(help.contains("LOADCONF"));
623 assert!(help.contains("Multi-line"));
624 }
625
626 #[test]
627 fn test_help_mapaddress() {
628 let help = help_mapaddress();
629 assert!(help.contains("MAPADDRESS"));
630 assert!(help.contains("Example"));
631 }
632
633 #[test]
634 fn test_help_postdescriptor() {
635 let help = help_postdescriptor();
636 assert!(help.contains("POSTDESCRIPTOR"));
637 assert!(help.contains("Multi-line"));
638 }
639
640 #[test]
641 fn test_help_extendcircuit() {
642 let help = help_extendcircuit();
643 assert!(help.contains("EXTENDCIRCUIT"));
644 assert!(help.contains("CircuitID"));
645 assert!(help.contains("PATH"));
646 }
647
648 #[test]
649 fn test_help_setcircuitpurpose() {
650 let help = help_setcircuitpurpose();
651 assert!(help.contains("SETCIRCUITPURPOSE"));
652 assert!(help.contains("purpose"));
653 }
654
655 #[test]
656 fn test_help_closecircuit() {
657 let help = help_closecircuit();
658 assert!(help.contains("CLOSECIRCUIT"));
659 assert!(help.contains("IfUnused"));
660 }
661
662 #[test]
663 fn test_help_attachstream() {
664 let help = help_attachstream();
665 assert!(help.contains("ATTACHSTREAM"));
666 assert!(help.contains("StreamID"));
667 assert!(help.contains("CircuitID"));
668 }
669
670 #[test]
671 fn test_help_redirectstream() {
672 let help = help_redirectstream();
673 assert!(help.contains("REDIRECTSTREAM"));
674 assert!(help.contains("Address"));
675 }
676
677 #[test]
678 fn test_help_closestream() {
679 let help = help_closestream();
680 assert!(help.contains("CLOSESTREAM"));
681 assert!(help.contains("Reason"));
682 }
683
684 #[test]
685 fn test_help_add_onion() {
686 let help = help_add_onion();
687 assert!(help.contains("KeyType"));
688 assert!(help.contains("hidden service"));
689 }
690
691 #[test]
692 fn test_help_del_onion() {
693 let help = help_del_onion();
694 assert!(help.contains("DEL_ONION"));
695 assert!(help.contains("ServiceID"));
696 }
697
698 #[test]
699 fn test_help_hsfetch() {
700 let help = help_hsfetch();
701 assert!(help.contains("HSFETCH"));
702 assert!(help.contains("descriptor"));
703 }
704
705 #[test]
706 fn test_help_hspost() {
707 let help = help_hspost();
708 assert!(help.contains("HSPOST"));
709 assert!(help.contains("DESCRIPTOR"));
710 }
711
712 #[test]
713 fn test_help_resolve() {
714 let help = help_resolve();
715 assert!(help.contains("RESOLVE"));
716 assert!(help.contains("DNS"));
717 assert!(help.contains("reverse"));
718 }
719
720 #[test]
721 fn test_help_takeownership() {
722 let help = help_takeownership();
723 assert!(help.contains("TAKEOWNERSHIP"));
724 assert!(help.contains("shut down"));
725 }
726
727 #[test]
728 fn test_help_protocolinfo() {
729 let help = help_protocolinfo();
730 assert!(help.contains("PROTOCOLINFO"));
731 assert!(help.contains("authentication"));
732 }
733
734 #[test]
735 fn test_normalize_mixed_case() {
736 assert_eq!(normalize("GetInfo"), "GETINFO");
737 assert_eq!(normalize("setConf"), "SETCONF");
738 }
739
740 #[test]
741 fn test_normalize_with_multiple_spaces() {
742 assert_eq!(normalize("GETINFO version extra"), "GETINFO");
743 }
744
745 #[test]
746 fn test_normalize_slash_command() {
747 assert_eq!(normalize("/EVENTS"), "EVENTS");
748 assert_eq!(normalize("/events"), "EVENTS");
749 }
750}