Debian packaging: 0.0.7-1
[tor-status] / src / torcontrol.vala
1 /* This file is part of libtorcontrol.
2  *
3  * Copyright (C) 2010 Philipp Zabel
4  *
5  * libtorcontrol is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * libtorcontrol is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with libtorcontrol. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 namespace TorControl {
20         // There are no Vala bindings for getaddrinfo, open socket in C code.
21         [CCode (cname = "tor_control_open_socket")]
22         extern int open_socket (int port) throws GLib.Error;
23
24         errordomain TorError {
25                 UNRECOGNIZED_COMMAND_ARGUMENT = 513,
26                 AUTHENTICATION_REQUIRED = 514,
27                 BAD_AUTHENTICATION = 515,
28                 UNSPECIFIED = 550,
29                 UNRECOGNIZED_EVENT = 552
30         }
31
32         public class Connection : GLib.Object {
33                 int control_port;
34                 int socket_fd;
35                 GLib.IOChannel channel;
36                 bool async_lock = false;
37
38                 public Connection () throws GLib.Error {
39                         control_port = 9051;
40                         socket_fd = TorControl.open_socket (control_port);
41                         channel = new GLib.IOChannel.unix_new (socket_fd);
42                         channel.set_encoding (null);
43                         channel.set_buffered (false);
44                 }
45
46                 public Connection.with_port (int port) throws GLib.Error {
47                         control_port = port;
48                         socket_fd = TorControl.open_socket (control_port);
49                         channel = new GLib.IOChannel.unix_new (socket_fd);
50                         channel.set_encoding (null);
51                         channel.set_buffered (false);
52                 }
53
54                 private void send_command (string command) throws GLib.ConvertError, GLib.IOChannelError {
55                         size_t bytes_written;
56                         unowned char[] buf = (char[]) command;
57
58                         buf.length = (int) command.length;
59
60                         channel.write_chars (buf, out bytes_written);
61                 }
62
63                 private string receive_result () throws GLib.ConvertError, GLib.IOChannelError, TorError {
64                         size_t bytes_read;
65                         char[] buf = new char[4096];
66
67                         channel.read_chars (buf, out bytes_read);
68
69                         buf[bytes_read] = 0;
70
71                         // FIXME
72                         unowned string p = (string) buf;
73                         while (p.has_prefix ("6")) {
74                                 char *crlf = p.str ("\r\n");
75                                 if (crlf == null) {
76                                         print ("ERROR - missing newline\n");
77                                         return "";
78                                 }
79                                 crlf[0] = '\0';
80                                 print ("ASYNC: \"%s\"\n", p);
81                                 p = (string) (crlf + 2);
82                         }
83
84                         if (p.has_prefix ("2")) {
85                                 return "%s".printf (p);
86                         } if (p.has_prefix ("5")) {
87                                 int code = p.to_int ();
88                                 string message = "%s".printf (p.offset (4));
89
90                                 switch (code) {
91                                 case 513:
92                                         throw new TorError.UNRECOGNIZED_COMMAND_ARGUMENT (message);
93                                 case 514:
94                                         throw new TorError.AUTHENTICATION_REQUIRED (message);
95                                 case 515:
96                                         throw new TorError.BAD_AUTHENTICATION (message);
97                                 case 550:
98                                         throw new TorError.UNSPECIFIED (message);
99                                 case 552:
100                                         throw new TorError.UNRECOGNIZED_EVENT (message);
101                                 default:
102                                         print ("Unknown error %d: \"%s\"\n", code, message);
103                                         return "";
104                                 }
105                         } else {
106                                 print ("Error: \"%s\"\n", p);
107                                 return "";
108                         }
109                 }
110
111                 private SourceFunc continuation;
112                 private async string receive_result_async () throws GLib.ConvertError, GLib.IOChannelError, TorError {
113                         channel.add_watch (IOCondition.IN | IOCondition.PRI, tor_io_func);
114                         continuation = receive_result_async.callback;
115                         yield;
116                         string result = receive_result ();
117                         async_lock = false;
118                         return result;
119                 }
120
121                 private bool tor_io_func (IOChannel source, IOCondition condition) {
122                         if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
123                                 if (async_lock)
124                                         continuation ();
125                         }
126                         return false;
127                 }
128
129                 public void authenticate (string password = "") throws GLib.ConvertError, GLib.IOChannelError, TorError {
130                         send_command ("AUTHENTICATE \"%s\"\r\n".printf (password));
131
132                         receive_result ();
133                 }
134
135                 public async void authenticate_async (string password = "") throws GLib.ConvertError, GLib.IOChannelError, TorError {
136                         if (async_lock) {
137                                 throw new TorError.UNSPECIFIED ("only one async command at a time!");
138                         }
139                         async_lock = true;
140                         send_command ("AUTHENTICATE \"%s\"\r\n".printf (password));
141
142                         yield receive_result_async ();
143                 }
144
145                 public string get_conf (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
146                         send_command ("GETCONF " + conf + "\r\n");
147
148                         string result = receive_result ();
149
150                         if (result[3] == '-') {
151                                 return result;
152                         } if (result.offset (4).has_prefix (conf + "=")) {
153                                 return "%s".printf (result.offset (4 + conf.length + 1));
154                         } else {
155                                 print ("get_conf error: \"%s\"\n", result);
156                                 return "";
157                         }
158                 }
159
160                 public async string get_conf_async (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
161                         if (async_lock) {
162                                 throw new TorError.UNSPECIFIED ("only one async command at a time!");
163                         }
164                         async_lock = true;
165                         send_command ("GETCONF " + conf + "\r\n");
166
167                         string result = yield receive_result_async ();
168
169                         if (result[3] == '-') {
170                                 return result;
171                         } if (result.offset (4).has_prefix (conf + "=")) {
172                                 return "%s".printf (result.offset (4 + conf.length + 1));
173                         } else {
174                                 print ("get_conf error: \"%s\"\n", result);
175                                 return "";
176                         }
177                 }
178
179
180                 public bool get_conf_bool (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
181                         return (bool) get_conf (conf).to_int ();
182                 }
183
184                 public async bool get_conf_bool_async (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
185                         var result = yield get_conf_async (conf).to_int ();
186                         return (bool) result;
187                 }
188
189                 public SList<string> get_conf_list (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
190                         string result = get_conf (conf);
191                         return evaluate_list (conf, result);
192                 }
193
194                 public async SList<string> get_conf_list_async (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
195                         string result = yield get_conf_async (conf);
196                         return evaluate_list (conf, result);
197                 }
198
199                 private SList<string> evaluate_list (string conf, string result) {
200                         string prefix = conf + "=";
201                         SList<string> list = new SList<string> ();
202                         unowned string p = result;
203                         for (char *crlf = p.str ("\r\n"); crlf != null; crlf = p.str ("\r\n")) {
204                                 crlf[0] = '\0';
205                                 if (p.offset (4).has_prefix (prefix)) {
206                                         list.append (p.offset (4 + prefix.length));
207                                 }
208                                 p = (string) (crlf + 2);
209                         }
210                         return list;
211                 }
212
213                 public void set_conf (string conf, string val) throws GLib.ConvertError, GLib.IOChannelError, TorError {
214                         send_command ("SETCONF " + conf + "=" + val + "\r\n");
215
216                         string result = receive_result ();
217
218                         if (!result.offset (4).has_prefix ("OK")) {
219                                 print ("set_conf error: \"%s\"\n", result);
220                         }
221                 }
222
223                 public void set_conf_bool (string conf, bool val) throws GLib.ConvertError, GLib.IOChannelError, TorError {
224                         set_conf (conf, val ? "1" : "0");
225                 }
226
227                 public void set_conf_list (string conf, SList<string> values) throws GLib.ConvertError, GLib.IOChannelError, TorError {
228                         string command = "SETCONF";
229                         foreach (string val in values) {
230                                 command += " %s=%s".printf (conf, val);
231                         }
232                         send_command (command + "\r\n");
233
234                         string result = receive_result ();
235
236                         if (!result.offset (4).has_prefix ("OK")) {
237                                 print ("set_conf_list error: \"%s\"\n", result);
238                         }
239                 }
240
241                 public void set_events (string events) throws GLib.ConvertError, GLib.IOChannelError, TorError {
242                         send_command ("SETEVENTS " + events + "\r\n");
243
244                         receive_result ();
245                 }
246
247         }
248 }