| |
1 /* |
| |
2 * This program is free software; you can redistribute it and/or modify |
| |
3 * it under the terms of the GNU General Public License as published by |
| |
4 * the Free Software Foundation; either version 2 of the License, or |
| |
5 * (at your option) any later version. |
| |
6 * |
| |
7 * This program is distributed in the hope that it will be useful, |
| |
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
10 * GNU Library General Public License for more details. |
| |
11 * |
| |
12 * You should have received a copy of the GNU General Public License |
| |
13 * along with this program; if not, write to the Free Software |
| |
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| |
15 */ |
| |
16 |
| |
17 #include <string.h> |
| |
18 |
| |
19 #include "dns_sd.h" |
| |
20 #include "bonjour.h" |
| |
21 #include "buddy.h" |
| |
22 #include "debug.h" |
| |
23 |
| |
24 /* Private functions */ |
| |
25 |
| |
26 static sw_result HOWL_API |
| |
27 _publish_reply(sw_discovery discovery, sw_discovery_oid oid, |
| |
28 sw_discovery_publish_status status, sw_opaque extra) |
| |
29 { |
| |
30 gaim_debug_warning("bonjour", "_publish_reply --> Start\n"); |
| |
31 |
| |
32 /* Check the answer from the mDNS daemon */ |
| |
33 switch (status) |
| |
34 { |
| |
35 case SW_DISCOVERY_PUBLISH_STARTED : |
| |
36 gaim_debug_info("bonjour", "_publish_reply --> Service started\n"); |
| |
37 break; |
| |
38 case SW_DISCOVERY_PUBLISH_STOPPED : |
| |
39 gaim_debug_info("bonjour", "_publish_reply --> Service stopped\n"); |
| |
40 break; |
| |
41 case SW_DISCOVERY_PUBLISH_NAME_COLLISION : |
| |
42 gaim_debug_info("bonjour", "_publish_reply --> Name collision\n"); |
| |
43 break; |
| |
44 case SW_DISCOVERY_PUBLISH_INVALID : |
| |
45 gaim_debug_info("bonjour", "_publish_reply --> Service invalid\n"); |
| |
46 break; |
| |
47 } |
| |
48 |
| |
49 return SW_OKAY; |
| |
50 } |
| |
51 |
| |
52 static sw_result HOWL_API |
| |
53 _resolve_reply(sw_discovery discovery, sw_discovery_oid oid, |
| |
54 sw_uint32 interface_index, sw_const_string name, |
| |
55 sw_const_string type, sw_const_string domain, |
| |
56 sw_ipv4_address address, sw_port port, |
| |
57 sw_octets text_record, sw_ulong text_record_len, |
| |
58 sw_opaque extra) |
| |
59 { |
| |
60 BonjourBuddy *buddy; |
| |
61 GaimAccount *account = (GaimAccount*)extra; |
| |
62 gchar *txtvers = NULL; |
| |
63 gchar *version = NULL; |
| |
64 gchar *first = NULL; |
| |
65 gchar *phsh = NULL; |
| |
66 gchar *status = NULL; |
| |
67 gchar *email = NULL; |
| |
68 gchar *last = NULL; |
| |
69 gchar *jid = NULL; |
| |
70 gchar *AIM = NULL; |
| |
71 gchar *vc = NULL; |
| |
72 gchar *msg = NULL; |
| |
73 gint address_length = 16; |
| |
74 gchar *ip = NULL; |
| |
75 sw_text_record_iterator iterator; |
| |
76 char key[SW_TEXT_RECORD_MAX_LEN]; |
| |
77 char value[SW_TEXT_RECORD_MAX_LEN]; |
| |
78 sw_uint32 value_length; |
| |
79 |
| |
80 sw_discovery_cancel(discovery, oid); |
| |
81 |
| |
82 /* Get the ip as a string */ |
| |
83 ip = malloc(address_length); |
| |
84 sw_ipv4_address_name(address, ip, address_length); |
| |
85 |
| |
86 /* Obtain the parameters from the text_record */ |
| |
87 if ((text_record_len > 0) && (text_record) && (*text_record != '\0')) |
| |
88 { |
| |
89 sw_text_record_iterator_init(&iterator, text_record, text_record_len); |
| |
90 while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY) |
| |
91 { |
| |
92 /* Compare the keys with the possible ones and save them on */ |
| |
93 /* the appropiate place of the buddy_list */ |
| |
94 if (strcmp(key, "txtvers") == 0) { |
| |
95 txtvers = g_strdup(value); |
| |
96 } else if (strcmp(key, "version") == 0) { |
| |
97 version = g_strdup(value); |
| |
98 } else if (strcmp(key, "1st") == 0) { |
| |
99 first = g_strdup(value); |
| |
100 } else if (strcmp(key, "status") == 0) { |
| |
101 status = g_strdup(value); |
| |
102 } else if (strcmp(key, "email") == 0) { |
| |
103 email = g_strdup(value); |
| |
104 } else if (strcmp(key, "last") == 0) { |
| |
105 last = g_strdup(value); |
| |
106 } else if (strcmp(key, "jid") == 0) { |
| |
107 jid = g_strdup(value); |
| |
108 } else if (strcmp(key, "AIM") == 0) { |
| |
109 AIM = g_strdup(value); |
| |
110 } else if (strcmp(key, "vc") == 0) { |
| |
111 vc = g_strdup(value); |
| |
112 } else if (strcmp(key, "phsh") == 0) { |
| |
113 phsh = g_strdup(value); |
| |
114 } else if (strcmp(key, "msg") == 0) { |
| |
115 msg = g_strdup(value); |
| |
116 } |
| |
117 } |
| |
118 } |
| |
119 |
| |
120 /* Put the parameters of the text_record in a buddy and add the buddy to */ |
| |
121 /* the buddy list */ |
| |
122 buddy = bonjour_buddy_new(name, first, port, phsh, |
| |
123 status, email, last, jid, AIM, vc, ip, msg); |
| |
124 |
| |
125 if (bonjour_buddy_check(buddy) == FALSE) |
| |
126 { |
| |
127 bonjour_buddy_delete(buddy); |
| |
128 return SW_DISCOVERY_E_UNKNOWN; |
| |
129 } |
| |
130 |
| |
131 /* Add or update the buddy in our buddy list */ |
| |
132 bonjour_buddy_add_to_gaim(account, buddy); |
| |
133 |
| |
134 /* Free all the temporal strings */ |
| |
135 g_free(txtvers); |
| |
136 g_free(version); |
| |
137 g_free(first); |
| |
138 g_free(last); |
| |
139 g_free(status); |
| |
140 g_free(email); |
| |
141 g_free(jid); |
| |
142 g_free(AIM); |
| |
143 g_free(vc); |
| |
144 g_free(phsh); |
| |
145 g_free(msg); |
| |
146 |
| |
147 return SW_OKAY; |
| |
148 } |
| |
149 |
| |
150 static sw_result HOWL_API |
| |
151 _browser_reply(sw_discovery discovery, sw_discovery_oid oid, |
| |
152 sw_discovery_browse_status status, |
| |
153 sw_uint32 interface_index, sw_const_string name, |
| |
154 sw_const_string type, sw_const_string domain, |
| |
155 sw_opaque_t extra) |
| |
156 { |
| |
157 sw_discovery_resolve_id rid; |
| |
158 GaimAccount *account = (GaimAccount*)extra; |
| |
159 GaimBuddy *gb = NULL; |
| |
160 |
| |
161 switch (status) |
| |
162 { |
| |
163 case SW_DISCOVERY_BROWSE_INVALID: |
| |
164 gaim_debug_warning("bonjour", "_browser_reply --> Invalid\n"); |
| |
165 break; |
| |
166 case SW_DISCOVERY_BROWSE_RELEASE: |
| |
167 gaim_debug_warning("bonjour", "_browser_reply --> Release\n"); |
| |
168 break; |
| |
169 case SW_DISCOVERY_BROWSE_ADD_DOMAIN: |
| |
170 gaim_debug_warning("bonjour", "_browser_reply --> Add domain\n"); |
| |
171 break; |
| |
172 case SW_DISCOVERY_BROWSE_ADD_DEFAULT_DOMAIN: |
| |
173 gaim_debug_warning("bonjour", "_browser_reply --> Add default domain\n"); |
| |
174 break; |
| |
175 case SW_DISCOVERY_BROWSE_REMOVE_DOMAIN: |
| |
176 gaim_debug_warning("bonjour", "_browser_reply --> Remove domain\n"); |
| |
177 break; |
| |
178 case SW_DISCOVERY_BROWSE_ADD_SERVICE: |
| |
179 /* A new peer has joined the network and uses iChat bonjour */ |
| |
180 gaim_debug_info("bonjour", "_browser_reply --> Add service\n"); |
| |
181 if (g_ascii_strcasecmp(name, account->username) != 0) |
| |
182 { |
| |
183 if (sw_discovery_resolve(discovery, interface_index, name, type, |
| |
184 domain, _resolve_reply, extra, &rid) != SW_OKAY) |
| |
185 { |
| |
186 gaim_debug_warning("bonjour", "_browser_reply --> Cannot send resolve\n"); |
| |
187 } |
| |
188 } |
| |
189 break; |
| |
190 case SW_DISCOVERY_BROWSE_REMOVE_SERVICE: |
| |
191 gaim_debug_info("bonjour", "_browser_reply --> Remove service\n"); |
| |
192 gb = gaim_find_buddy((GaimAccount*)extra, name); |
| |
193 if (gb != NULL) |
| |
194 { |
| |
195 bonjour_buddy_delete(gb->proto_data); |
| |
196 gaim_blist_remove_buddy(gb); |
| |
197 } |
| |
198 break; |
| |
199 case SW_DISCOVERY_BROWSE_RESOLVED: |
| |
200 gaim_debug_info("bonjour", "_browse_reply --> Resolved\n"); |
| |
201 break; |
| |
202 default: |
| |
203 break; |
| |
204 } |
| |
205 |
| |
206 return SW_OKAY; |
| |
207 } |
| |
208 |
| |
209 static int |
| |
210 _dns_sd_publish(BonjourDnsSd *data, PublishType type) |
| |
211 { |
| |
212 sw_text_record dns_data; |
| |
213 sw_result publish_result = SW_OKAY; |
| |
214 char portstring[6]; |
| |
215 |
| |
216 /* Fill the data for the service */ |
| |
217 if (sw_text_record_init(&dns_data) != SW_OKAY) |
| |
218 { |
| |
219 gaim_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n"); |
| |
220 return -1; |
| |
221 } |
| |
222 |
| |
223 /* Convert the port to a string */ |
| |
224 snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); |
| |
225 |
| |
226 /* Publish standard records */ |
| |
227 sw_text_record_add_key_and_string_value(dns_data, "txtvers", data->txtvers); |
| |
228 sw_text_record_add_key_and_string_value(dns_data, "version", data->version); |
| |
229 sw_text_record_add_key_and_string_value(dns_data, "1st", data->first); |
| |
230 sw_text_record_add_key_and_string_value(dns_data, "last", data->last); |
| |
231 sw_text_record_add_key_and_string_value(dns_data, "port.p2pj", portstring); |
| |
232 sw_text_record_add_key_and_string_value(dns_data, "phsh", data->phsh); |
| |
233 sw_text_record_add_key_and_string_value(dns_data, "status", data->status); |
| |
234 sw_text_record_add_key_and_string_value(dns_data, "vc", data->vc); |
| |
235 |
| |
236 /* Publish extra records */ |
| |
237 if ((data->email != NULL) && (*data->email != '\0')) |
| |
238 sw_text_record_add_key_and_string_value(dns_data, "email", data->email); |
| |
239 |
| |
240 if ((data->jid != NULL) && (*data->jid != '\0')) |
| |
241 sw_text_record_add_key_and_string_value(dns_data, "jid", data->jid); |
| |
242 |
| |
243 if ((data->AIM != NULL) && (*data->AIM != '\0')) |
| |
244 sw_text_record_add_key_and_string_value(dns_data, "AIM", data->AIM); |
| |
245 |
| |
246 if ((data->msg != NULL) && (*data->msg != '\0')) |
| |
247 sw_text_record_add_key_and_string_value(dns_data, "msg", data->msg); |
| |
248 |
| |
249 /* Publish the service */ |
| |
250 switch (type) |
| |
251 { |
| |
252 case PUBLISH_START: |
| |
253 publish_result = sw_discovery_publish(data->session, 0, data->name, ICHAT_SERVICE, NULL, |
| |
254 NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data), |
| |
255 _publish_reply, NULL, &data->session_id); |
| |
256 break; |
| |
257 case PUBLISH_UPDATE: |
| |
258 publish_result = sw_discovery_publish_update(data->session, data->session_id, |
| |
259 sw_text_record_bytes(dns_data), sw_text_record_len(dns_data)); |
| |
260 break; |
| |
261 } |
| |
262 if (publish_result != SW_OKAY) |
| |
263 { |
| |
264 gaim_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n"); |
| |
265 return -1; |
| |
266 } |
| |
267 |
| |
268 /* Free the memory used by temp data */ |
| |
269 sw_text_record_fina(dns_data); |
| |
270 |
| |
271 return 0; |
| |
272 } |
| |
273 |
| |
274 static void |
| |
275 _dns_sd_handle_packets(gpointer data, gint source, GaimInputCondition condition) |
| |
276 { |
| |
277 sw_discovery_read_socket((sw_discovery)data); |
| |
278 } |
| |
279 |
| |
280 /* End private functions */ |
| |
281 |
| |
282 /** |
| |
283 * Allocate space for the dns-sd data. |
| |
284 */ |
| |
285 BonjourDnsSd * |
| |
286 bonjour_dns_sd_new() |
| |
287 { |
| |
288 BonjourDnsSd *data = g_new0(BonjourDnsSd, 1); |
| |
289 |
| |
290 return data; |
| |
291 } |
| |
292 |
| |
293 /** |
| |
294 * Deallocate the space of the dns-sd data. |
| |
295 */ |
| |
296 void |
| |
297 bonjour_dns_sd_free(BonjourDnsSd *data) |
| |
298 { |
| |
299 g_free(data->first); |
| |
300 g_free(data->last); |
| |
301 g_free(data->email); |
| |
302 g_free(data); |
| |
303 } |
| |
304 |
| |
305 /** |
| |
306 * Send a new dns-sd packet updating our status. |
| |
307 */ |
| |
308 void |
| |
309 bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) |
| |
310 { |
| |
311 g_free(data->status); |
| |
312 g_free(data->msg); |
| |
313 |
| |
314 data->status = g_strdup(status); |
| |
315 data->msg = g_strdup(status_message); |
| |
316 |
| |
317 /* Update our text record with the new status */ |
| |
318 _dns_sd_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */ |
| |
319 } |
| |
320 |
| |
321 /** |
| |
322 * Advertise our presence within the dns-sd daemon and start browsing |
| |
323 * for other bonjour peers. |
| |
324 */ |
| |
325 gboolean |
| |
326 bonjour_dns_sd_start(BonjourDnsSd *data) |
| |
327 { |
| |
328 GaimAccount *account; |
| |
329 GaimConnection *gc; |
| |
330 gint dns_sd_socket; |
| |
331 sw_discovery_oid session_id; |
| |
332 |
| |
333 account = data->account; |
| |
334 gc = gaim_account_get_connection(account); |
| |
335 |
| |
336 /* Initialize the dns-sd data and session */ |
| |
337 if (sw_discovery_init(&data->session) != SW_OKAY) |
| |
338 { |
| |
339 gaim_debug_error("bonjour", "Unable to initialize an mDNS session.\n"); |
| |
340 |
| |
341 /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */ |
| |
342 data->session = NULL; |
| |
343 |
| |
344 return FALSE; |
| |
345 } |
| |
346 |
| |
347 /* Publish our bonjour IM client at the mDNS daemon */ |
| |
348 _dns_sd_publish(data, PUBLISH_START); /* <--We must control the errors */ |
| |
349 |
| |
350 /* Advise the daemon that we are waiting for connections */ |
| |
351 if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply, |
| |
352 data->account, &session_id) != SW_OKAY) |
| |
353 { |
| |
354 gaim_debug_error("bonjour", "Unable to get service."); |
| |
355 return FALSE; |
| |
356 } |
| |
357 |
| |
358 /* Get the socket that communicates with the mDNS daemon and bind it to a */ |
| |
359 /* callback that will handle the dns_sd packets */ |
| |
360 dns_sd_socket = sw_discovery_socket(data->session); |
| |
361 gc->inpa = gaim_input_add(dns_sd_socket, GAIM_INPUT_READ, |
| |
362 _dns_sd_handle_packets, data->session); |
| |
363 |
| |
364 return TRUE; |
| |
365 } |
| |
366 |
| |
367 /** |
| |
368 * Unregister the "_presence._tcp" service at the mDNS daemon. |
| |
369 */ |
| |
370 void |
| |
371 bonjour_dns_sd_stop(BonjourDnsSd *data) |
| |
372 { |
| |
373 GaimAccount *account; |
| |
374 GaimConnection *gc; |
| |
375 |
| |
376 if (data->session == NULL) |
| |
377 return; |
| |
378 |
| |
379 sw_discovery_cancel(data->session, data->session_id); |
| |
380 |
| |
381 account = data->account; |
| |
382 gc = gaim_account_get_connection(account); |
| |
383 gaim_input_remove(gc->inpa); |
| |
384 |
| |
385 g_free(data->session); |
| |
386 data->session = NULL; |
| |
387 } |