Two SIMPLE changes:
[pidgin-git.git] / libpurple / protocols / simple / simple.c
blob17905096c2d492732201aaa8c5e1c6310a7e178d
1 /**
2 * @file simple.c
4 * purple
6 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
8 * ***
9 * Thanks to Google's Summer of Code Program and the helpful mentors
10 * ***
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "internal.h"
29 #include "accountopt.h"
30 #include "blist.h"
31 #include "conversation.h"
32 #include "dnsquery.h"
33 #include "debug.h"
34 #include "notify.h"
35 #include "privacy.h"
36 #include "prpl.h"
37 #include "plugin.h"
38 #include "util.h"
39 #include "version.h"
40 #include "network.h"
41 #include "xmlnode.h"
43 #include "simple.h"
44 #include "sipmsg.h"
45 #include "dnssrv.h"
46 #include "ntlm.h"
48 static char *gentag(void) {
49 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
52 static char *genbranch(void) {
53 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
54 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
55 rand() & 0xFFFF, rand() & 0xFFFF);
58 static char *gencallid(void) {
59 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
60 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
61 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
62 rand() & 0xFFFF, rand() & 0xFFFF);
65 static const char *simple_list_icon(PurpleAccount *a, PurpleBuddy *b) {
66 return "simple";
69 static void simple_keep_alive(PurpleConnection *gc) {
70 struct simple_account_data *sip = gc->proto_data;
71 if(sip->udp) { /* in case of UDP send a packet only with a 0 byte to
72 remain in the NAT table */
73 gchar buf[2] = {0, 0};
74 purple_debug_info("simple", "sending keep alive\n");
75 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
77 return;
80 static gboolean process_register_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc);
81 static void send_notify(struct simple_account_data *sip, struct simple_watcher *);
83 static void send_open_publish(struct simple_account_data *sip);
84 static void send_closed_publish(struct simple_account_data *sip);
86 static void do_notifies(struct simple_account_data *sip) {
87 GSList *tmp = sip->watcher;
88 purple_debug_info("simple", "do_notifies()\n");
89 if((sip->republish != -1) || sip->republish < time(NULL)) {
90 if(purple_account_get_bool(sip->account, "dopublish", TRUE)) {
91 send_open_publish(sip);
95 while(tmp) {
96 purple_debug_info("simple", "notifying %s\n", ((struct simple_watcher*)tmp->data)->name);
97 send_notify(sip, tmp->data);
98 tmp = tmp->next;
102 static void simple_set_status(PurpleAccount *account, PurpleStatus *status) {
103 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(purple_status_get_type(status));
104 struct simple_account_data *sip = NULL;
106 if (!purple_status_is_active(status))
107 return;
109 if (account->gc)
110 sip = account->gc->proto_data;
112 if (sip)
114 g_free(sip->status);
115 if (primitive == PURPLE_STATUS_AVAILABLE)
116 sip->status = g_strdup("available");
117 else
118 sip->status = g_strdup("busy");
120 do_notifies(sip);
124 static struct sip_connection *connection_find(struct simple_account_data *sip, int fd) {
125 struct sip_connection *ret = NULL;
126 GSList *entry = sip->openconns;
127 while(entry) {
128 ret = entry->data;
129 if(ret->fd == fd) return ret;
130 entry = entry->next;
132 return NULL;
135 static struct simple_watcher *watcher_find(struct simple_account_data *sip,
136 const gchar *name) {
137 struct simple_watcher *watcher;
138 GSList *entry = sip->watcher;
139 while(entry) {
140 watcher = entry->data;
141 if(!strcmp(name, watcher->name)) return watcher;
142 entry = entry->next;
144 return NULL;
147 static struct simple_watcher *watcher_create(struct simple_account_data *sip,
148 const gchar *name, const gchar *callid, const gchar *ourtag,
149 const gchar *theirtag, gboolean needsxpidf) {
150 struct simple_watcher *watcher = g_new0(struct simple_watcher, 1);
151 watcher->name = g_strdup(name);
152 watcher->dialog.callid = g_strdup(callid);
153 watcher->dialog.ourtag = g_strdup(ourtag);
154 watcher->dialog.theirtag = g_strdup(theirtag);
155 watcher->needsxpidf = needsxpidf;
156 sip->watcher = g_slist_append(sip->watcher, watcher);
157 return watcher;
160 static void watcher_remove(struct simple_account_data *sip, const gchar *name) {
161 struct simple_watcher *watcher = watcher_find(sip, name);
162 sip->watcher = g_slist_remove(sip->watcher, watcher);
163 g_free(watcher->name);
164 g_free(watcher->dialog.callid);
165 g_free(watcher->dialog.ourtag);
166 g_free(watcher->dialog.theirtag);
167 g_free(watcher);
170 static struct sip_connection *connection_create(struct simple_account_data *sip, int fd) {
171 struct sip_connection *ret = g_new0(struct sip_connection, 1);
172 ret->fd = fd;
173 sip->openconns = g_slist_append(sip->openconns, ret);
174 return ret;
177 static void connection_remove(struct simple_account_data *sip, int fd) {
178 struct sip_connection *conn = connection_find(sip, fd);
179 sip->openconns = g_slist_remove(sip->openconns, conn);
180 if(conn->inputhandler) purple_input_remove(conn->inputhandler);
181 g_free(conn->inbuf);
182 g_free(conn);
185 static void connection_free_all(struct simple_account_data *sip) {
186 struct sip_connection *ret = NULL;
187 GSList *entry = sip->openconns;
188 while(entry) {
189 ret = entry->data;
190 connection_remove(sip, ret->fd);
191 entry = sip->openconns;
195 static void simple_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
197 struct simple_account_data *sip = (struct simple_account_data *)gc->proto_data;
198 struct simple_buddy *b;
199 const char *name = purple_buddy_get_name(buddy);
200 if(strncmp(name, "sip:", 4)) {
201 gchar *buf = g_strdup_printf("sip:%s", name);
202 purple_blist_rename_buddy(buddy, buf);
203 g_free(buf);
205 if(!g_hash_table_lookup(sip->buddies, name)) {
206 b = g_new0(struct simple_buddy, 1);
207 purple_debug_info("simple", "simple_add_buddy %s\n", name);
208 b->name = g_strdup(name);
209 g_hash_table_insert(sip->buddies, b->name, b);
210 } else {
211 purple_debug_info("simple", "buddy %s already in internal list\n", name);
215 static void simple_get_buddies(PurpleConnection *gc) {
216 GSList *buddies;
217 PurpleAccount *account;
219 purple_debug_info("simple", "simple_get_buddies\n");
221 account = purple_connection_get_account(gc);
222 buddies = purple_find_buddies(account, NULL);
223 while (buddies) {
224 PurpleBuddy *buddy = buddies->data;
225 simple_add_buddy(gc, buddy, purple_buddy_get_group(buddy));
227 buddies = g_slist_delete_link(buddies, buddies);
231 static void simple_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
233 const char *name = purple_buddy_get_name(buddy);
234 struct simple_account_data *sip = (struct simple_account_data *)gc->proto_data;
235 struct simple_buddy *b = g_hash_table_lookup(sip->buddies, name);
236 g_hash_table_remove(sip->buddies, name);
237 g_free(b->name);
238 g_free(b);
241 static GList *simple_status_types(PurpleAccount *acc) {
242 PurpleStatusType *type;
243 GList *types = NULL;
245 type = purple_status_type_new_with_attrs(
246 PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
247 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
248 NULL);
249 types = g_list_append(types, type);
251 type = purple_status_type_new_full(
252 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
253 types = g_list_append(types, type);
255 return types;
258 static gchar *auth_header(struct simple_account_data *sip,
259 struct sip_auth *auth, const gchar *method, const gchar *target) {
260 gchar noncecount[9];
261 gchar *response;
262 gchar *ret;
263 gchar *tmp;
264 const char *authdomain;
265 const char *authuser;
267 authdomain = purple_account_get_string(sip->account, "authdomain", "");
268 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
270 if(!authuser || strlen(authuser) < 1) {
271 authuser = sip->username;
274 if(auth->type == 1) { /* Digest */
275 sprintf(noncecount, "%08d", auth->nc++);
276 response = purple_cipher_http_digest_calculate_response(
277 "md5", method, target, NULL, NULL,
278 auth->nonce, noncecount, NULL, auth->digest_session_key);
279 purple_debug(PURPLE_DEBUG_MISC, "simple", "response %s\n", response);
281 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
282 g_free(response);
283 return ret;
284 } else if(auth->type == 2) { /* NTLM */
285 if(auth->nc == 3 && auth->nonce) {
286 /* TODO: Don't hardcode "purple" as the hostname */
287 ret = purple_ntlm_gen_type3(authuser, sip->password, "purple", authdomain, (const guint8 *)auth->nonce, &auth->flags);
288 tmp = g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth->opaque, auth->realm, auth->target, ret);
289 g_free(ret);
290 return tmp;
292 tmp = g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth->realm, auth->target);
293 return tmp;
296 sprintf(noncecount, "%08d", auth->nc++);
297 response = purple_cipher_http_digest_calculate_response(
298 "md5", method, target, NULL, NULL,
299 auth->nonce, noncecount, NULL, auth->digest_session_key);
300 purple_debug(PURPLE_DEBUG_MISC, "simple", "response %s\n", response);
302 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->nonce, target, noncecount, response);
303 g_free(response);
304 return ret;
307 static char *parse_attribute(const char *attrname, const char *source) {
308 const char *tmp, *tmp2;
309 char *retval = NULL;
310 int len = strlen(attrname);
312 /* we know that source is NULL-terminated.
313 * Therefore this loop won't be infinite.
315 while (source[0] == ' ')
316 source++;
318 if(!strncmp(source, attrname, len)) {
319 tmp = source + len;
320 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
321 if(tmp2)
322 retval = g_strndup(tmp, tmp2 - tmp);
323 else
324 retval = g_strdup(tmp);
327 return retval;
330 static void fill_auth(struct simple_account_data *sip, const gchar *hdr, struct sip_auth *auth) {
331 int i = 0;
332 const char *authuser;
333 char *tmp;
334 gchar **parts;
336 authuser = purple_account_get_string(sip->account, "authuser", sip->username);
338 if(!authuser || strlen(authuser) < 1) {
339 authuser = sip->username;
342 if(!hdr) {
343 purple_debug_error("simple", "fill_auth: hdr==NULL\n");
344 return;
347 if(!g_ascii_strncasecmp(hdr, "NTLM", 4)) {
348 purple_debug_info("simple", "found NTLM\n");
349 auth->type = 2;
350 parts = g_strsplit(hdr+5, "\",", 0);
351 i = 0;
352 while(parts[i]) {
353 purple_debug_info("simple", "parts[i] %s\n", parts[i]);
354 if((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
355 auth->nonce = g_memdup(purple_ntlm_parse_type2(tmp, &auth->flags), 8);
356 g_free(tmp);
358 if((tmp = parse_attribute("targetname=\"",
359 parts[i]))) {
360 auth->target = tmp;
362 else if((tmp = parse_attribute("realm=\"",
363 parts[i]))) {
364 auth->realm = tmp;
366 else if((tmp = parse_attribute("opaque=\"", parts[i]))) {
367 auth->opaque = tmp;
369 i++;
371 g_strfreev(parts);
372 auth->nc = 1;
373 if(!strstr(hdr, "gssapi-data")) {
374 auth->nc = 1;
375 } else {
376 auth->nc = 3;
379 return;
380 } else if(!g_ascii_strncasecmp(hdr, "DIGEST", 6)) {
382 purple_debug_info("simple", "found DIGEST\n");
384 auth->type = 1;
385 parts = g_strsplit(hdr+7, ",", 0);
386 while(parts[i]) {
387 if((tmp = parse_attribute("nonce=\"", parts[i]))) {
388 auth->nonce = tmp;
390 else if((tmp = parse_attribute("realm=\"", parts[i]))) {
391 auth->realm = tmp;
393 i++;
395 g_strfreev(parts);
396 purple_debug(PURPLE_DEBUG_MISC, "simple", "nonce: %s realm: %s\n",
397 auth->nonce ? auth->nonce : "(null)",
398 auth->realm ? auth->realm : "(null)");
400 if(auth->realm) {
401 auth->digest_session_key = purple_cipher_http_digest_calculate_session_key(
402 "md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
404 auth->nc = 1;
407 } else {
408 purple_debug_error("simple", "Unsupported or bad WWW-Authenticate header (%s).\n", hdr);
413 static void simple_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond) {
414 PurpleConnection *gc = data;
415 struct simple_account_data *sip = gc->proto_data;
416 gsize max_write;
417 gssize written;
419 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
421 if(max_write == 0) {
422 purple_input_remove(sip->tx_handler);
423 sip->tx_handler = 0;
424 return;
427 written = write(sip->fd, sip->txbuf->outptr, max_write);
429 if(written < 0 && errno == EAGAIN)
430 written = 0;
431 else if (written <= 0) {
432 /*TODO: do we really want to disconnect on a failure to write?*/
433 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
434 g_strerror(errno));
435 purple_connection_error_reason(gc,
436 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
437 g_free(tmp);
438 return;
441 purple_circ_buffer_mark_read(sip->txbuf, written);
444 static void simple_input_cb(gpointer data, gint source, PurpleInputCondition cond);
446 static void send_later_cb(gpointer data, gint source, const gchar *error_message) {
447 PurpleConnection *gc = data;
448 struct simple_account_data *sip;
449 struct sip_connection *conn;
451 if(source < 0) {
452 gchar *tmp = g_strdup_printf(_("Unable to connect: %s"),
453 error_message);
454 purple_connection_error_reason(gc,
455 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
456 g_free(tmp);
457 return;
460 sip = gc->proto_data;
461 sip->fd = source;
462 sip->connecting = FALSE;
464 simple_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
466 /* If there is more to write now, we need to register a handler */
467 if(sip->txbuf->bufused > 0)
468 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE,
469 simple_canwrite_cb, gc);
471 conn = connection_create(sip, source);
472 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, simple_input_cb, gc);
476 static void sendlater(PurpleConnection *gc, const char *buf) {
477 struct simple_account_data *sip = gc->proto_data;
479 if(!sip->connecting) {
480 purple_debug_info("simple", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
481 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
482 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect"));
484 sip->connecting = TRUE;
487 if(purple_circ_buffer_get_max_read(sip->txbuf) > 0)
488 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
490 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
493 static void sendout_pkt(PurpleConnection *gc, const char *buf) {
494 struct simple_account_data *sip = gc->proto_data;
495 time_t currtime = time(NULL);
496 int writelen = strlen(buf);
498 purple_debug(PURPLE_DEBUG_MISC, "simple", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
499 if(sip->udp) {
500 if(sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
501 purple_debug_info("simple", "could not send packet\n");
503 } else {
504 int ret;
505 if(sip->fd < 0) {
506 sendlater(gc, buf);
507 return;
510 if(sip->tx_handler) {
511 ret = -1;
512 errno = EAGAIN;
513 } else
514 ret = write(sip->fd, buf, writelen);
516 if (ret < 0 && errno == EAGAIN)
517 ret = 0;
518 else if(ret <= 0) { /* XXX: When does this happen legitimately? */
519 sendlater(gc, buf);
520 return;
523 if (ret < writelen) {
524 if(!sip->tx_handler)
525 sip->tx_handler = purple_input_add(sip->fd,
526 PURPLE_INPUT_WRITE, simple_canwrite_cb,
527 gc);
529 /* XXX: is it OK to do this? You might get part of a request sent
530 with part of another. */
531 if(sip->txbuf->bufused > 0)
532 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
534 purple_circ_buffer_append(sip->txbuf, buf + ret,
535 writelen - ret);
540 static int simple_send_raw(PurpleConnection *gc, const char *buf, int len)
542 sendout_pkt(gc, buf);
543 return len;
546 static void sendout_sipmsg(struct simple_account_data *sip, struct sipmsg *msg) {
547 GSList *tmp = msg->headers;
548 gchar *name;
549 gchar *value;
550 GString *outstr = g_string_new("");
551 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
552 while(tmp) {
553 name = ((struct siphdrelement*) (tmp->data))->name;
554 value = ((struct siphdrelement*) (tmp->data))->value;
555 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
556 tmp = g_slist_next(tmp);
558 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
559 sendout_pkt(sip->gc, outstr->str);
560 g_string_free(outstr, TRUE);
563 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
564 const char *text, const char *body) {
565 GSList *tmp = msg->headers;
566 gchar *name;
567 gchar *value;
568 GString *outstr = g_string_new("");
570 /* When sending the acknowlegements and errors, the content length from the original
571 message is still here, but there is no body; we need to make sure we're sending the
572 correct content length */
573 sipmsg_remove_header(msg, "Content-Length");
574 if(body) {
575 gchar len[12];
576 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
577 sipmsg_add_header(msg, "Content-Length", len);
579 else
580 sipmsg_add_header(msg, "Content-Length", "0");
581 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
582 while(tmp) {
583 name = ((struct siphdrelement*) (tmp->data))->name;
584 value = ((struct siphdrelement*) (tmp->data))->value;
586 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
587 tmp = g_slist_next(tmp);
589 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
590 sendout_pkt(gc, outstr->str);
591 g_string_free(outstr, TRUE);
594 static void transactions_remove(struct simple_account_data *sip, struct transaction *trans) {
595 if(trans->msg) sipmsg_free(trans->msg);
596 sip->transactions = g_slist_remove(sip->transactions, trans);
597 g_free(trans);
600 static void transactions_add_buf(struct simple_account_data *sip, const gchar *buf, void *callback) {
601 struct transaction *trans = g_new0(struct transaction, 1);
602 trans->time = time(NULL);
603 trans->msg = sipmsg_parse_msg(buf);
604 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
605 trans->callback = callback;
606 sip->transactions = g_slist_append(sip->transactions, trans);
609 static struct transaction *transactions_find(struct simple_account_data *sip, struct sipmsg *msg) {
610 struct transaction *trans;
611 GSList *transactions = sip->transactions;
612 const gchar *cseq = sipmsg_find_header(msg, "CSeq");
614 if (cseq) {
615 while(transactions) {
616 trans = transactions->data;
617 if(!strcmp(trans->cseq, cseq)) {
618 return trans;
620 transactions = transactions->next;
622 } else {
623 purple_debug(PURPLE_DEBUG_MISC, "simple", "Received message contains no CSeq header.\n");
626 return NULL;
629 static void send_sip_request(PurpleConnection *gc, const gchar *method,
630 const gchar *url, const gchar *to, const gchar *addheaders,
631 const gchar *body, struct sip_dialog *dialog, TransCallback tc) {
632 struct simple_account_data *sip = gc->proto_data;
633 char *callid = dialog ? g_strdup(dialog->callid) : gencallid();
634 char *auth = NULL;
635 const char *addh = "";
636 gchar *branch = genbranch();
637 gchar *tag = NULL;
638 char *buf;
640 if(!strcmp(method, "REGISTER")) {
641 if(sip->regcallid) {
642 g_free(callid);
643 callid = g_strdup(sip->regcallid);
645 else sip->regcallid = g_strdup(callid);
648 if(addheaders) addh = addheaders;
649 if(sip->registrar.type && !strcmp(method, "REGISTER")) {
650 buf = auth_header(sip, &sip->registrar, method, url);
651 auth = g_strdup_printf("Authorization: %s\r\n", buf);
652 g_free(buf);
653 purple_debug(PURPLE_DEBUG_MISC, "simple", "header %s", auth);
654 } else if(sip->proxy.type && strcmp(method, "REGISTER")) {
655 buf = auth_header(sip, &sip->proxy, method, url);
656 auth = g_strdup_printf("Proxy-Authorization: %s\r\n", buf);
657 g_free(buf);
658 purple_debug(PURPLE_DEBUG_MISC, "simple", "header %s", auth);
661 if (!dialog)
662 tag = gentag();
664 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
665 "Via: SIP/2.0/%s %s:%d;branch=%s\r\n"
666 /* Don't know what epid is, but LCS wants it */
667 "From: <sip:%s@%s>;tag=%s;epid=1234567890\r\n"
668 "To: <%s>%s%s\r\n"
669 "Max-Forwards: 10\r\n"
670 "CSeq: %d %s\r\n"
671 "User-Agent: Purple/" VERSION "\r\n"
672 "Call-ID: %s\r\n"
673 "%s%s"
674 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
675 method,
676 url,
677 sip->udp ? "UDP" : "TCP",
678 purple_network_get_my_ip(-1),
679 sip->listenport,
680 branch,
681 sip->username,
682 sip->servername,
683 dialog ? dialog->ourtag : tag,
685 dialog ? ";tag=" : "",
686 dialog ? dialog->theirtag : "",
687 ++sip->cseq,
688 method,
689 callid,
690 auth ? auth : "",
691 addh,
692 strlen(body),
693 body);
695 g_free(tag);
696 g_free(auth);
697 g_free(branch);
698 g_free(callid);
700 /* add to ongoing transactions */
702 transactions_add_buf(sip, buf, tc);
704 sendout_pkt(gc, buf);
706 g_free(buf);
709 static char *get_contact(struct simple_account_data *sip) {
710 return g_strdup_printf("<sip:%s@%s:%d;transport=%s>;methods=\"MESSAGE, SUBSCRIBE, NOTIFY\"",
711 sip->username, purple_network_get_my_ip(-1),
712 sip->listenport,
713 sip->udp ? "udp" : "tcp");
716 static void do_register_exp(struct simple_account_data *sip, int expire) {
717 char *uri, *to, *contact, *hdr;
719 sip->reregister = time(NULL) + expire - 50;
721 uri = g_strdup_printf("sip:%s", sip->servername);
722 to = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
723 contact = get_contact(sip);
724 hdr = g_strdup_printf("Contact: %s\r\nExpires: %d\r\n", contact, expire);
725 g_free(contact);
727 sip->registerstatus = SIMPLE_REGISTER_SENT;
729 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
730 process_register_response);
732 g_free(hdr);
733 g_free(uri);
734 g_free(to);
737 static void do_register(struct simple_account_data *sip) {
738 do_register_exp(sip, sip->registerexpire);
741 static gchar *parse_from(const gchar *hdr) {
742 gchar *from;
743 const gchar *tmp, *tmp2 = hdr;
745 if(!hdr) return NULL;
746 purple_debug_info("simple", "parsing address out of %s\n", hdr);
747 tmp = strchr(hdr, '<');
749 /* i hate the different SIP UA behaviours... */
750 if(tmp) { /* sip address in <...> */
751 tmp2 = tmp + 1;
752 tmp = strchr(tmp2, '>');
753 if(tmp) {
754 from = g_strndup(tmp2, tmp - tmp2);
755 } else {
756 purple_debug_info("simple", "found < without > in From\n");
757 return NULL;
759 } else {
760 tmp = strchr(tmp2, ';');
761 if(tmp) {
762 from = g_strndup(tmp2, tmp - tmp2);
763 } else {
764 from = g_strdup(tmp2);
767 purple_debug_info("simple", "got %s\n", from);
768 return from;
770 static gchar *find_tag(const gchar *);
772 static gboolean process_subscribe_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
773 gchar *to = NULL;
774 struct simple_buddy *b = NULL;
775 gchar *theirtag = NULL, *ourtag = NULL;
776 const gchar *callid = NULL;
778 purple_debug_info("simple", "process subscribe response\n");
780 if(msg->response == 200 || msg->response == 202) {
781 if ( (to = parse_from(sipmsg_find_header(msg, "To"))) &&
782 (b = g_hash_table_lookup(sip->buddies, to)) &&
783 !(b->dialog))
785 purple_debug_info("simple", "creating dialog"
786 " information for a subscription.\n");
788 theirtag = find_tag(sipmsg_find_header(msg, "To"));
789 ourtag = find_tag(sipmsg_find_header(msg, "From"));
790 callid = sipmsg_find_header(msg, "Call-ID");
792 if (theirtag && ourtag && callid)
794 b->dialog = g_new0(struct sip_dialog, 1);
795 b->dialog->ourtag = g_strdup(ourtag);
796 b->dialog->theirtag = g_strdup(theirtag);
797 b->dialog->callid = g_strdup(callid);
799 purple_debug_info("simple", "ourtag: %s\n",
800 ourtag);
801 purple_debug_info("simple", "theirtag: %s\n",
802 theirtag);
803 purple_debug_info("simple", "callid: %s\n",
804 callid);
805 g_free(theirtag);
806 g_free(ourtag);
809 else
811 purple_debug_info("simple", "cannot create dialog!\n");
813 return TRUE;
816 to = parse_from(sipmsg_find_header(tc->msg, "To")); /* cant be NULL since it is our own msg */
818 /* we can not subscribe -> user is offline (TODO unknown status?) */
820 purple_prpl_got_user_status(sip->account, to, "offline", NULL);
821 g_free(to);
822 return TRUE;
825 static void simple_subscribe_exp(struct simple_account_data *sip, struct simple_buddy *buddy, int expiration) {
826 gchar *contact, *to, *tmp, *tmp2;
828 tmp2 = g_strdup_printf(
829 "Expires: %d\r\n"
830 "Accept: application/pidf+xml, application/xpidf+xml\r\n"
831 "Event: presence\r\n",
832 expiration);
834 if(strncmp(buddy->name, "sip:", 4))
835 to = g_strdup_printf("sip:%s", buddy->name);
836 else
837 to = g_strdup(buddy->name);
839 tmp = get_contact(sip);
840 contact = g_strdup_printf("%sContact: %s\r\n", tmp2, tmp);
841 g_free(tmp);
842 g_free(tmp2);
844 send_sip_request(sip->gc, "SUBSCRIBE", to, to, contact,"",buddy->dialog,
845 (expiration > 0) ? process_subscribe_response : NULL);
847 g_free(to);
848 g_free(contact);
850 /* resubscribe before subscription expires */
851 /* add some jitter */
852 if (expiration > 60)
853 buddy->resubscribe = time(NULL) + (expiration - 60) + (rand() % 50);
854 else if (expiration > 0)
855 buddy->resubscribe = time(NULL) + ((int) (expiration / 2));
858 static void simple_subscribe(struct simple_account_data *sip, struct simple_buddy *buddy) {
859 simple_subscribe_exp(sip, buddy, SUBSCRIBE_EXPIRATION);
862 static void simple_unsubscribe(char *name, struct simple_buddy *buddy, struct simple_account_data *sip) {
863 if (buddy->dialog)
865 purple_debug_info("simple", "Unsubscribing from %s\n", name);
866 simple_subscribe_exp(sip, buddy, 0);
870 static gboolean simple_add_lcs_contacts(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
871 const gchar *tmp;
872 xmlnode *item, *group, *isc;
873 const char *name_group;
874 PurpleBuddy *b;
875 PurpleGroup *g = NULL;
876 struct simple_buddy *bs;
877 int len = msg->bodylen;
880 tmp = sipmsg_find_header(msg, "Event");
881 if(tmp && !strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)){
883 purple_debug_info("simple", "simple_add_lcs_contacts->%s-%d\n", msg->body, len);
884 /*Convert the contact from XML to Purple Buddies*/
885 isc = xmlnode_from_str(msg->body, len);
887 /* ToDo. Find for all groups */
888 if ((group = xmlnode_get_child(isc, "group"))) {
889 name_group = xmlnode_get_attrib(group, "name");
890 purple_debug_info("simple", "name_group->%s\n", name_group);
891 g = purple_find_group(name_group);
892 if(!g)
893 g = purple_group_new(name_group);
896 if (!g) {
897 g = purple_find_group("Buddies");
898 if(!g)
899 g = purple_group_new("Buddies");
902 for(item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item))
904 const char *uri, *name, *groups;
905 char *buddy_name;
906 uri = xmlnode_get_attrib(item, "uri");
907 name = xmlnode_get_attrib(item, "name");
908 groups = xmlnode_get_attrib(item, "groups");
909 purple_debug_info("simple", "URI->%s\n", uri);
911 buddy_name = g_strdup_printf("sip:%s", uri);
913 b = purple_find_buddy(sip->account, buddy_name);
914 if(!b){
915 b = purple_buddy_new(sip->account, buddy_name, uri);
917 g_free(buddy_name);
919 purple_blist_add_buddy(b, NULL, g, NULL);
920 purple_blist_alias_buddy(b, uri);
921 bs = g_new0(struct simple_buddy, 1);
922 bs->name = g_strdup(purple_buddy_get_name(b));
923 g_hash_table_insert(sip->buddies, bs->name, bs);
925 xmlnode_free(isc);
927 return 0;
930 static void simple_subscribe_buddylist(struct simple_account_data *sip) {
931 gchar *contact = "Event: vnd-microsoft-roaming-contacts\r\nAccept: application/vnd-microsoft-roaming-contacts+xml\r\nSupported: com.microsoft.autoextend\r\nSupported: ms-benotify\r\nProxy-Require: ms-benotify\r\nSupported: ms-piggyback-first-notify\r\n";
932 gchar *to;
933 gchar *tmp;
934 to = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
936 tmp = get_contact(sip);
938 contact = g_strdup_printf("%sContact: %s\r\n", contact, tmp);
939 g_free(tmp);
941 send_sip_request(sip->gc, "SUBSCRIBE", to, to, contact, "", NULL, simple_add_lcs_contacts);
943 g_free(to);
944 g_free(contact);
948 static void simple_buddy_resub(char *name, struct simple_buddy *buddy, struct simple_account_data *sip) {
949 time_t curtime = time(NULL);
950 purple_debug_info("simple", "buddy resub\n");
951 if(buddy->resubscribe < curtime) {
952 purple_debug(PURPLE_DEBUG_MISC, "simple", "simple_buddy_resub %s\n", name);
953 simple_subscribe(sip, buddy);
957 static gboolean resend_timeout(struct simple_account_data *sip) {
958 GSList *tmp = sip->transactions;
959 time_t currtime = time(NULL);
960 while(tmp) {
961 struct transaction *trans = tmp->data;
962 tmp = tmp->next;
963 purple_debug_info("simple", "have open transaction age: %lu\n", currtime- trans->time);
964 if((currtime - trans->time > 5) && trans->retries >= 1) {
965 /* TODO 408 */
966 } else {
967 if((currtime - trans->time > 2) && trans->retries == 0) {
968 trans->retries++;
969 sendout_sipmsg(sip, trans->msg);
973 return TRUE;
976 static gboolean subscribe_timeout(struct simple_account_data *sip) {
977 GSList *tmp;
978 time_t curtime = time(NULL);
979 /* register again if first registration expires */
980 if(sip->reregister < curtime) {
981 do_register(sip);
984 /* publish status again if our last update is about to expire. */
985 if (sip->republish != -1 &&
986 sip->republish < curtime &&
987 purple_account_get_bool(sip->account, "dopublish", TRUE))
989 purple_debug_info("simple", "subscribe_timeout: republishing status.\n");
990 send_open_publish(sip);
993 /* check for every subscription if we need to resubscribe */
994 g_hash_table_foreach(sip->buddies, (GHFunc)simple_buddy_resub, (gpointer)sip);
996 /* remove a timed out suscriber */
997 tmp = sip->watcher;
998 while(tmp) {
999 struct simple_watcher *watcher = tmp->data;
1000 if(watcher->expire < curtime) {
1001 watcher_remove(sip, watcher->name);
1002 tmp = sip->watcher;
1004 if(tmp) tmp = tmp->next;
1007 return TRUE;
1010 static void simple_send_message(struct simple_account_data *sip, const char *to, const char *msg, const char *type) {
1011 gchar *hdr;
1012 gchar *fullto;
1013 if(strncmp(to, "sip:", 4))
1014 fullto = g_strdup_printf("sip:%s", to);
1015 else
1016 fullto = g_strdup(to);
1018 if(type) {
1019 hdr = g_strdup_printf("Content-Type: %s\r\n", type);
1020 } else {
1021 hdr = g_strdup("Content-Type: text/plain\r\n");
1023 send_sip_request(sip->gc, "MESSAGE", fullto, fullto, hdr, msg, NULL, NULL);
1024 g_free(hdr);
1025 g_free(fullto);
1028 static int simple_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags) {
1029 struct simple_account_data *sip = gc->proto_data;
1030 char *to = g_strdup(who);
1031 char *text = purple_unescape_html(what);
1032 simple_send_message(sip, to, text, NULL);
1033 g_free(to);
1034 g_free(text);
1035 return 1;
1038 static void process_incoming_message(struct simple_account_data *sip, struct sipmsg *msg) {
1039 gchar *from;
1040 const gchar *contenttype;
1041 gboolean found = FALSE;
1043 from = parse_from(sipmsg_find_header(msg, "From"));
1045 if(!from) return;
1047 purple_debug(PURPLE_DEBUG_MISC, "simple", "got message from %s: %s\n", from, msg->body);
1049 contenttype = sipmsg_find_header(msg, "Content-Type");
1050 if(!contenttype || !strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
1051 serv_got_im(sip->gc, from, msg->body, 0, time(NULL));
1052 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1053 found = TRUE;
1055 else if(!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
1056 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
1057 xmlnode *state;
1058 gchar *statedata;
1060 if(!isc) {
1061 purple_debug_info("simple", "process_incoming_message: can not parse iscomposing\n");
1062 g_free(from);
1063 return;
1066 state = xmlnode_get_child(isc, "state");
1068 if(!state) {
1069 purple_debug_info("simple", "process_incoming_message: no state found\n");
1070 xmlnode_free(isc);
1071 g_free(from);
1072 return;
1075 statedata = xmlnode_get_data(state);
1076 if(statedata) {
1077 if(strstr(statedata, "active"))
1078 serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
1079 else
1080 serv_got_typing_stopped(sip->gc, from);
1082 g_free(statedata);
1084 xmlnode_free(isc);
1085 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1086 found = TRUE;
1088 if(!found) {
1089 purple_debug_info("simple", "got unknown mime-type\n");
1090 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
1092 g_free(from);
1096 gboolean process_register_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
1097 const gchar *tmp;
1098 purple_debug(PURPLE_DEBUG_MISC, "simple", "in process register response response: %d\n", msg->response);
1099 switch (msg->response) {
1100 case 200:
1101 if(sip->registerstatus < SIMPLE_REGISTER_COMPLETE) { /* registered */
1102 if(purple_account_get_bool(sip->account, "dopublish", TRUE)) {
1103 send_open_publish(sip);
1106 sip->registerstatus = SIMPLE_REGISTER_COMPLETE;
1107 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
1109 /* get buddies from blist */
1110 simple_get_buddies(sip->gc);
1112 subscribe_timeout(sip);
1113 tmp = sipmsg_find_header(msg, "Allow-Events");
1114 if(tmp && strstr(tmp, "vnd-microsoft-provisioning")){
1115 simple_subscribe_buddylist(sip);
1118 break;
1119 case 401:
1120 if(sip->registerstatus != SIMPLE_REGISTER_RETRY) {
1121 purple_debug_info("simple", "REGISTER retries %d\n", sip->registrar.retries);
1122 if(sip->registrar.retries > SIMPLE_REGISTER_RETRY_MAX) {
1123 if (!purple_account_get_remember_password(sip->gc->account))
1124 purple_account_set_password(sip->gc->account, NULL);
1125 purple_connection_error_reason(sip->gc,
1126 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
1127 _("Incorrect password"));
1128 return TRUE;
1130 tmp = sipmsg_find_header(msg, "WWW-Authenticate");
1131 fill_auth(sip, tmp, &sip->registrar);
1132 sip->registerstatus = SIMPLE_REGISTER_RETRY;
1133 do_register(sip);
1135 break;
1136 default:
1137 if (sip->registerstatus != SIMPLE_REGISTER_RETRY) {
1138 purple_debug_info("simple", "Unrecognized return code for REGISTER.\n");
1139 if (sip->registrar.retries > SIMPLE_REGISTER_RETRY_MAX) {
1140 purple_connection_error_reason(sip->gc,
1141 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
1142 _("Unknown server response"));
1143 return TRUE;
1145 sip->registerstatus = SIMPLE_REGISTER_RETRY;
1146 do_register(sip);
1148 break;
1150 return TRUE;
1153 static gboolean dialog_match(struct sip_dialog *dialog, struct sipmsg *msg)
1155 const gchar *fromhdr;
1156 const gchar *tohdr;
1157 const gchar *callid;
1158 gchar *ourtag, *theirtag;
1159 gboolean match = FALSE;
1161 fromhdr = sipmsg_find_header(msg, "From");
1162 tohdr = sipmsg_find_header(msg, "To");
1163 callid = sipmsg_find_header(msg, "Call-ID");
1165 if (!fromhdr || !tohdr || !callid)
1166 return FALSE;
1168 ourtag = find_tag(tohdr);
1169 theirtag = find_tag(fromhdr);
1171 if (ourtag && theirtag &&
1172 !strcmp(dialog->callid, callid) &&
1173 !strcmp(dialog->ourtag, ourtag) &&
1174 !strcmp(dialog->theirtag, theirtag))
1175 match = TRUE;
1177 g_free(ourtag);
1178 g_free(theirtag);
1180 return match;
1183 static void process_incoming_notify(struct simple_account_data *sip, struct sipmsg *msg) {
1184 gchar *from;
1185 const gchar *fromhdr;
1186 gchar *basicstatus_data;
1187 xmlnode *pidf;
1188 xmlnode *basicstatus = NULL, *tuple, *status;
1189 gboolean isonline = FALSE;
1190 struct simple_buddy *b = NULL;
1191 const gchar *sshdr = NULL;
1193 fromhdr = sipmsg_find_header(msg, "From");
1194 from = parse_from(fromhdr);
1195 if(!from) return;
1197 b = g_hash_table_lookup(sip->buddies, from);
1198 if (!b)
1200 g_free(from);
1201 purple_debug_info("simple", "Could not find the buddy.\n");
1202 return;
1205 if (b->dialog && !dialog_match(b->dialog, msg))
1207 /* We only accept notifies from people that
1208 * we already have a dialog with.
1210 purple_debug_info("simple","No corresponding dialog for notify--discard\n");
1211 g_free(from);
1212 return;
1215 pidf = xmlnode_from_str(msg->body, msg->bodylen);
1217 if(!pidf) {
1218 purple_debug_info("simple", "process_incoming_notify: no parseable pidf\n");
1219 sshdr = sipmsg_find_header(msg, "Subscription-State");
1220 if (sshdr)
1222 int i = 0;
1223 gchar **ssparts = g_strsplit(sshdr, ":", 0);
1224 while (ssparts[i])
1226 g_strchug(ssparts[i]);
1227 if (purple_str_has_prefix(ssparts[i], "terminated"))
1229 purple_debug_info("simple", "Subscription expired!");
1230 if (b->dialog)
1232 g_free(b->dialog->ourtag);
1233 g_free(b->dialog->theirtag);
1234 g_free(b->dialog->callid);
1235 g_free(b->dialog);
1236 b->dialog = NULL;
1239 purple_prpl_got_user_status(sip->account, from, "offline", NULL);
1240 break;
1242 i++;
1244 g_strfreev(ssparts);
1246 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1247 g_free(from);
1248 return;
1251 if ((tuple = xmlnode_get_child(pidf, "tuple")))
1252 if ((status = xmlnode_get_child(tuple, "status")))
1253 basicstatus = xmlnode_get_child(status, "basic");
1255 if(!basicstatus) {
1256 purple_debug_info("simple", "process_incoming_notify: no basic found\n");
1257 xmlnode_free(pidf);
1258 g_free(from);
1259 return;
1262 basicstatus_data = xmlnode_get_data(basicstatus);
1264 if(!basicstatus_data) {
1265 purple_debug_info("simple", "process_incoming_notify: no basic data found\n");
1266 xmlnode_free(pidf);
1267 g_free(from);
1268 return;
1271 if(strstr(basicstatus_data, "open"))
1272 isonline = TRUE;
1275 if(isonline)
1276 purple_prpl_got_user_status(sip->account, from, "available", NULL);
1277 else
1278 purple_prpl_got_user_status(sip->account, from, "offline", NULL);
1280 xmlnode_free(pidf);
1281 g_free(from);
1282 g_free(basicstatus_data);
1284 send_sip_response(sip->gc, msg, 200, "OK", NULL);
1287 static unsigned int simple_typing(PurpleConnection *gc, const char *name, PurpleTypingState state) {
1288 struct simple_account_data *sip = gc->proto_data;
1290 gchar *xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1291 "<isComposing xmlns=\"urn:ietf:params:xml:ns:im-iscomposing\"\n"
1292 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1293 "xsi:schemaLocation=\"urn:ietf:params:xml:ns:im-composing iscomposing.xsd\">\n"
1294 "<state>%s</state>\n"
1295 "<contenttype>text/plain</contenttype>\n"
1296 "<refresh>60</refresh>\n"
1297 "</isComposing>";
1298 gchar *recv = g_strdup(name);
1299 if(state == PURPLE_TYPING) {
1300 gchar *msg = g_strdup_printf(xml, "active");
1301 simple_send_message(sip, recv, msg, "application/im-iscomposing+xml");
1302 g_free(msg);
1303 } else /* TODO: Only if (state == PURPLE_TYPED) ? */ {
1304 gchar *msg = g_strdup_printf(xml, "idle");
1305 simple_send_message(sip, recv, msg, "application/im-iscomposing+xml");
1306 g_free(msg);
1308 g_free(recv);
1310 * TODO: Is this right? It will cause the core to call
1311 * serv_send_typing(gc, who, PURPLE_TYPING) once every second
1312 * until the user stops typing. If that's not desired,
1313 * then return 0 instead.
1315 return 1;
1318 static gchar *find_tag(const gchar *hdr) {
1319 const gchar *tmp = strstr(hdr, ";tag="), *tmp2;
1321 if(!tmp) return NULL;
1322 tmp += 5;
1323 if((tmp2 = strchr(tmp, ';'))) {
1324 return g_strndup(tmp, tmp2 - tmp);
1326 return g_strdup(tmp);
1329 static gchar* gen_xpidf(struct simple_account_data *sip) {
1330 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1331 "<presence>\n"
1332 "<presentity uri=\"sip:%s@%s;method=SUBSCRIBE\"/>\n"
1333 "<display name=\"sip:%s@%s\"/>\n"
1334 "<atom id=\"1234\">\n"
1335 "<address uri=\"sip:%s@%s\">\n"
1336 "<status status=\"%s\"/>\n"
1337 "</address>\n"
1338 "</atom>\n"
1339 "</presence>\n",
1340 sip->username,
1341 sip->servername,
1342 sip->username,
1343 sip->servername,
1344 sip->username,
1345 sip->servername,
1346 sip->status);
1347 return doc;
1350 static gchar* gen_pidf(struct simple_account_data *sip, gboolean open) {
1351 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1352 "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\n"
1353 "xmlns:im=\"urn:ietf:params:xml:ns:pidf:im\"\n"
1354 "entity=\"sip:%s@%s\">\n"
1355 "<tuple id=\"bs35r9f\">\n"
1356 "<status>\n"
1357 "<basic>%s</basic>\n"
1358 "</status>\n"
1359 "<note>%s</note>\n"
1360 "</tuple>\n"
1361 "</presence>",
1362 sip->username,
1363 sip->servername,
1364 (open == TRUE) ? "open" : "closed",
1365 (open == TRUE) ? sip->status : "");
1366 return doc;
1369 static void send_notify(struct simple_account_data *sip, struct simple_watcher *watcher) {
1370 gchar *doc = watcher->needsxpidf ? gen_xpidf(sip) : gen_pidf(sip, TRUE);
1371 gchar *hdr = watcher->needsxpidf ? "Event: presence\r\nContent-Type: application/xpidf+xml\r\n" : "Event: presence\r\nContent-Type: application/pidf+xml\r\n";
1372 send_sip_request(sip->gc, "NOTIFY", watcher->name, watcher->name, hdr, doc, &watcher->dialog, NULL);
1373 g_free(doc);
1376 static gboolean process_publish_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
1378 const gchar *etag = NULL;
1380 if(msg->response != 200 && msg->response != 408) {
1381 /* never send again */
1382 sip->republish = -1;
1385 etag = sipmsg_find_header(msg, "SIP-Etag");
1386 if (etag) {
1387 /* we must store the etag somewhere. */
1388 g_free(sip->publish_etag);
1389 sip->publish_etag = g_strdup(etag);
1392 return TRUE;
1395 static void send_open_publish(struct simple_account_data *sip) {
1396 gchar *add_headers = NULL;
1397 gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
1398 gchar *doc = gen_pidf(sip, TRUE);
1400 add_headers = g_strdup_printf("%s%s%s%s%d\r\n%s",
1401 sip->publish_etag ? "SIP-If-Match: " : "",
1402 sip->publish_etag ? sip->publish_etag : "",
1403 sip->publish_etag ? "\r\n" : "",
1404 "Expires: ", PUBLISH_EXPIRATION,
1405 "Event: presence\r\n"
1406 "Content-Type: application/pidf+xml\r\n");
1408 send_sip_request(sip->gc, "PUBLISH", uri, uri,
1409 add_headers, doc, NULL, process_publish_response);
1410 sip->republish = time(NULL) + PUBLISH_EXPIRATION - 50;
1411 g_free(uri);
1412 g_free(doc);
1413 g_free(add_headers);
1416 static void send_closed_publish(struct simple_account_data *sip) {
1417 gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
1418 gchar *add_headers, *doc;
1420 add_headers = g_strdup_printf("%s%s%s%s",
1421 sip->publish_etag ? "SIP-If-Match: " : "",
1422 sip->publish_etag ? sip->publish_etag : "",
1423 sip->publish_etag ? "\r\n" : "",
1424 "Expires: 600\r\n"
1425 "Event: presence\r\n"
1426 "Content-Type: application/pidf+xml\r\n");
1428 doc = gen_pidf(sip, FALSE);
1429 send_sip_request(sip->gc, "PUBLISH", uri, uri, add_headers,
1430 doc, NULL, process_publish_response);
1431 /*sip->republish = time(NULL) + 500;*/
1432 g_free(uri);
1433 g_free(doc);
1434 g_free(add_headers);
1437 static void process_incoming_subscribe(struct simple_account_data *sip, struct sipmsg *msg) {
1438 const char *from_hdr = sipmsg_find_header(msg, "From");
1439 gchar *from = parse_from(from_hdr);
1440 gchar *theirtag = find_tag(from_hdr);
1441 gchar *ourtag = find_tag(sipmsg_find_header(msg, "To"));
1442 gboolean tagadded = FALSE;
1443 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1444 const gchar *expire = sipmsg_find_header(msg, "Expire");
1445 gchar *tmp;
1446 struct simple_watcher *watcher = watcher_find(sip, from);
1447 if(!ourtag) {
1448 tagadded = TRUE;
1449 ourtag = gentag();
1451 if(!watcher) { /* new subscription */
1452 const gchar *acceptheader = sipmsg_find_header(msg, "Accept");
1453 gboolean needsxpidf = FALSE;
1454 if(!purple_privacy_check(sip->account, from)) {
1455 send_sip_response(sip->gc, msg, 202, "Ok", NULL);
1456 goto privend;
1458 if(acceptheader) {
1459 const gchar *tmp = acceptheader;
1460 gboolean foundpidf = FALSE;
1461 gboolean foundxpidf = FALSE;
1462 while(tmp && tmp < acceptheader + strlen(acceptheader)) {
1463 gchar *tmp2 = strchr(tmp, ',');
1464 if(tmp2) *tmp2 = '\0';
1465 if(!g_ascii_strcasecmp("application/pidf+xml", tmp))
1466 foundpidf = TRUE;
1467 if(!g_ascii_strcasecmp("application/xpidf+xml", tmp))
1468 foundxpidf = TRUE;
1469 if(tmp2) {
1470 *tmp2 = ',';
1471 tmp = tmp2 + 1;
1472 while(*tmp == ' ') tmp++;
1473 } else
1474 tmp = 0;
1476 if(!foundpidf && foundxpidf) needsxpidf = TRUE;
1478 watcher = watcher_create(sip, from, callid, ourtag, theirtag, needsxpidf);
1480 if(tagadded) {
1481 gchar *to = g_strdup_printf("%s;tag=%s", sipmsg_find_header(msg, "To"), ourtag);
1482 sipmsg_remove_header(msg, "To");
1483 sipmsg_add_header(msg, "To", to);
1484 g_free(to);
1486 if(expire)
1487 watcher->expire = time(NULL) + strtol(expire, NULL, 10);
1488 else
1489 watcher->expire = time(NULL) + 600;
1490 sipmsg_remove_header(msg, "Contact");
1491 tmp = get_contact(sip);
1492 sipmsg_add_header(msg, "Contact", tmp);
1493 g_free(tmp);
1494 purple_debug_info("simple", "got subscribe: name %s ourtag %s theirtag %s callid %s\n", watcher->name, watcher->dialog.ourtag, watcher->dialog.theirtag, watcher->dialog.callid);
1495 send_sip_response(sip->gc, msg, 200, "Ok", NULL);
1496 send_notify(sip, watcher);
1497 privend:
1498 g_free(from);
1499 g_free(theirtag);
1500 g_free(ourtag);
1503 static void process_input_message(struct simple_account_data *sip, struct sipmsg *msg) {
1504 gboolean found = FALSE;
1505 if(msg->response == 0) { /* request */
1506 if(!strcmp(msg->method, "MESSAGE")) {
1507 process_incoming_message(sip, msg);
1508 found = TRUE;
1509 } else if(!strcmp(msg->method, "NOTIFY")) {
1510 process_incoming_notify(sip, msg);
1511 found = TRUE;
1512 } else if(!strcmp(msg->method, "SUBSCRIBE")) {
1513 process_incoming_subscribe(sip, msg);
1514 found = TRUE;
1515 } else {
1516 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
1518 } else { /* response */
1519 struct transaction *trans = transactions_find(sip, msg);
1520 if(trans) {
1521 if(msg->response == 407) {
1522 gchar *resend, *auth;
1523 const gchar *ptmp;
1525 if(sip->proxy.retries > 3) return;
1526 sip->proxy.retries++;
1527 /* do proxy authentication */
1529 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
1531 fill_auth(sip, ptmp, &sip->proxy);
1532 auth = auth_header(sip, &sip->proxy, trans->msg->method, trans->msg->target);
1533 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
1534 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
1535 g_free(auth);
1536 resend = sipmsg_to_string(trans->msg);
1537 /* resend request */
1538 sendout_pkt(sip->gc, resend);
1539 g_free(resend);
1540 } else {
1541 if(msg->response == 100) {
1542 /* ignore provisional response */
1543 purple_debug_info("simple", "got trying response\n");
1544 } else {
1545 sip->proxy.retries = 0;
1546 if(!strcmp(trans->msg->method, "REGISTER")) {
1548 /* This is encountered when a REGISTER request was ...
1550 if(msg->response == 401) {
1551 /* denied until further authentication was provided. */
1552 sip->registrar.retries++;
1554 else if (msg->response != 200) {
1555 /* denied for some other reason! */
1556 sip->registrar.retries++;
1558 else {
1559 /* accepted! */
1560 sip->registrar.retries = 0;
1562 } else {
1563 if(msg->response == 401) {
1564 /* This is encountered when a generic (MESSAGE, NOTIFY, etc)
1565 * was denied until further authorization is provided.
1567 gchar *resend, *auth;
1568 const gchar *ptmp;
1570 if(sip->registrar.retries > SIMPLE_REGISTER_RETRY_MAX) return;
1571 sip->registrar.retries++;
1573 ptmp = sipmsg_find_header(msg, "WWW-Authenticate");
1575 fill_auth(sip, ptmp, &sip->registrar);
1576 auth = auth_header(sip, &sip->registrar, trans->msg->method, trans->msg->target);
1577 sipmsg_remove_header(trans->msg, "Authorization");
1578 sipmsg_add_header(trans->msg, "Authorization", auth);
1579 g_free(auth);
1580 resend = sipmsg_to_string(trans->msg);
1581 /* resend request */
1582 sendout_pkt(sip->gc, resend);
1583 g_free(resend);
1584 } else {
1585 /* Reset any count of retries that may have
1586 * accumulated in the above branch.
1588 sip->registrar.retries = 0;
1591 if(trans->callback) {
1592 /* call the callback to process response*/
1593 (trans->callback)(sip, msg, trans);
1595 transactions_remove(sip, trans);
1598 found = TRUE;
1599 } else {
1600 purple_debug(PURPLE_DEBUG_MISC, "simple", "received response to unknown transaction");
1603 if(!found) {
1604 purple_debug(PURPLE_DEBUG_MISC, "simple", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
1608 static void process_input(struct simple_account_data *sip, struct sip_connection *conn)
1610 char *cur;
1611 char *dummy;
1612 struct sipmsg *msg;
1613 int restlen;
1614 cur = conn->inbuf;
1616 /* according to the RFC remove CRLF at the beginning */
1617 while(*cur == '\r' || *cur == '\n') {
1618 cur++;
1620 if(cur != conn->inbuf) {
1621 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
1622 conn->inbufused = strlen(conn->inbuf);
1625 /* Received a full Header? */
1626 if((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
1627 time_t currtime = time(NULL);
1628 cur += 2;
1629 cur[0] = '\0';
1630 purple_debug_info("simple", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
1631 msg = sipmsg_parse_header(conn->inbuf);
1633 if(!msg) {
1634 /* Should we re-use this error message (from lower in the function)? */
1635 purple_debug_misc("simple", "received a incomplete sip msg: %s\n", conn->inbuf);
1636 return;
1639 cur[0] = '\r';
1640 cur += 2;
1641 restlen = conn->inbufused - (cur - conn->inbuf);
1642 if(restlen >= msg->bodylen) {
1643 dummy = g_malloc(msg->bodylen + 1);
1644 memcpy(dummy, cur, msg->bodylen);
1645 dummy[msg->bodylen] = '\0';
1646 msg->body = dummy;
1647 cur += msg->bodylen;
1648 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
1649 conn->inbufused = strlen(conn->inbuf);
1650 } else {
1651 sipmsg_free(msg);
1652 return;
1654 purple_debug(PURPLE_DEBUG_MISC, "simple", "in process response response: %d\n", msg->response);
1655 process_input_message(sip, msg);
1656 } else {
1657 purple_debug(PURPLE_DEBUG_MISC, "simple", "received a incomplete sip msg: %s\n", conn->inbuf);
1661 static void simple_udp_process(gpointer data, gint source, PurpleInputCondition con) {
1662 PurpleConnection *gc = data;
1663 struct simple_account_data *sip = gc->proto_data;
1664 struct sipmsg *msg;
1665 int len;
1666 time_t currtime = time(NULL);
1668 static char buffer[65536];
1669 if((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
1670 buffer[len] = '\0';
1671 purple_debug_info("simple", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
1672 msg = sipmsg_parse_msg(buffer);
1673 if(msg) process_input_message(sip, msg);
1677 static void simple_input_cb(gpointer data, gint source, PurpleInputCondition cond)
1679 PurpleConnection *gc = data;
1680 struct simple_account_data *sip = gc->proto_data;
1681 int len;
1682 struct sip_connection *conn = connection_find(sip, source);
1683 if(!conn) {
1684 purple_debug_error("simple", "Connection not found!\n");
1685 return;
1688 if(conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
1689 conn->inbuflen += SIMPLE_BUF_INC;
1690 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
1693 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
1695 if(len < 0 && errno == EAGAIN)
1696 return;
1697 else if(len <= 0) {
1698 purple_debug_info("simple", "simple_input_cb: read error\n");
1699 connection_remove(sip, source);
1700 if(sip->fd == source) sip->fd = -1;
1701 return;
1703 gc->last_received = time(NULL);
1704 conn->inbufused += len;
1705 conn->inbuf[conn->inbufused] = '\0';
1707 process_input(sip, conn);
1710 /* Callback for new connections on incoming TCP port */
1711 static void simple_newconn_cb(gpointer data, gint source, PurpleInputCondition cond) {
1712 PurpleConnection *gc = data;
1713 struct simple_account_data *sip = gc->proto_data;
1714 struct sip_connection *conn;
1715 int newfd, flags;
1717 newfd = accept(source, NULL, NULL);
1719 flags = fcntl(newfd, F_GETFL);
1720 fcntl(newfd, F_SETFL, flags | O_NONBLOCK);
1721 #ifndef _WIN32
1722 fcntl(newfd, F_SETFD, FD_CLOEXEC);
1723 #endif
1725 conn = connection_create(sip, newfd);
1727 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, simple_input_cb, gc);
1730 static void login_cb(gpointer data, gint source, const gchar *error_message) {
1731 PurpleConnection *gc = data;
1732 struct simple_account_data *sip;
1733 struct sip_connection *conn;
1735 if(source < 0) {
1736 gchar *tmp = g_strdup_printf(_("Unable to connect: %s"),
1737 error_message);
1738 purple_connection_error_reason(gc,
1739 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
1740 g_free(tmp);
1741 return;
1744 sip = gc->proto_data;
1745 sip->fd = source;
1747 conn = connection_create(sip, source);
1749 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
1751 do_register(sip);
1753 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, simple_input_cb, gc);
1756 static guint simple_ht_hash_nick(const char *nick) {
1757 char *lc = g_utf8_strdown(nick, -1);
1758 guint bucket = g_str_hash(lc);
1759 g_free(lc);
1761 return bucket;
1764 static gboolean simple_ht_equals_nick(const char *nick1, const char *nick2) {
1765 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
1768 static void simple_udp_host_resolved_listen_cb(int listenfd, gpointer data) {
1769 struct simple_account_data *sip = (struct simple_account_data*) data;
1771 sip->listen_data = NULL;
1773 if(listenfd == -1) {
1774 purple_connection_error_reason(sip->gc,
1775 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1776 _("Unable to create listen socket"));
1777 return;
1781 * TODO: Is it correct to set sip->fd to the listenfd? For the TCP
1782 * listener we set sip->listenfd, but maybe UDP is different?
1783 * Maybe we use the same fd for outgoing data or something?
1785 sip->fd = listenfd;
1787 sip->listenport = purple_network_get_port_from_fd(sip->fd);
1789 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, simple_udp_process, sip->gc);
1791 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
1792 sip->registertimeout = purple_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
1793 do_register(sip);
1796 static void simple_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message) {
1797 struct simple_account_data *sip = (struct simple_account_data*) data;
1798 int addr_size;
1800 sip->query_data = NULL;
1802 if (!hosts || !hosts->data) {
1803 purple_connection_error_reason(sip->gc,
1804 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1805 _("Unable to resolve hostname"));
1806 return;
1809 addr_size = GPOINTER_TO_INT(hosts->data);
1810 hosts = g_slist_remove(hosts, hosts->data);
1811 memcpy(&(sip->serveraddr), hosts->data, addr_size);
1812 g_free(hosts->data);
1813 hosts = g_slist_remove(hosts, hosts->data);
1814 while(hosts) {
1815 hosts = g_slist_remove(hosts, hosts->data);
1816 g_free(hosts->data);
1817 hosts = g_slist_remove(hosts, hosts->data);
1820 /* create socket for incoming connections */
1821 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
1822 simple_udp_host_resolved_listen_cb, sip);
1823 if (sip->listen_data == NULL) {
1824 purple_connection_error_reason(sip->gc,
1825 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1826 _("Unable to create listen socket"));
1827 return;
1831 static void
1832 simple_tcp_connect_listen_cb(int listenfd, gpointer data) {
1833 struct simple_account_data *sip = (struct simple_account_data*) data;
1835 sip->listen_data = NULL;
1837 sip->listenfd = listenfd;
1838 if(sip->listenfd == -1) {
1839 purple_connection_error_reason(sip->gc,
1840 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1841 _("Unable to create listen socket"));
1842 return;
1845 purple_debug_info("simple", "listenfd: %d\n", sip->listenfd);
1846 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
1847 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
1848 simple_newconn_cb, sip->gc);
1849 purple_debug_info("simple", "connecting to %s port %d\n",
1850 sip->realhostname, sip->realport);
1851 /* open tcp connection to the server */
1852 if (purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
1853 sip->realport, login_cb, sip->gc) == NULL) {
1854 purple_connection_error_reason(sip->gc,
1855 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1856 _("Unable to connect"));
1860 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data) {
1861 struct simple_account_data *sip;
1862 gchar *hostname;
1863 int port;
1865 sip = data;
1866 sip->srv_query_data = NULL;
1868 port = purple_account_get_int(sip->account, "port", 0);
1870 /* find the host to connect to */
1871 if(results) {
1872 hostname = g_strdup(resp->hostname);
1873 if(!port)
1874 port = resp->port;
1875 g_free(resp);
1876 } else {
1877 if(!purple_account_get_bool(sip->account, "useproxy", FALSE)) {
1878 hostname = g_strdup(sip->servername);
1879 } else {
1880 hostname = g_strdup(purple_account_get_string(sip->account, "proxy", sip->servername));
1884 sip->realhostname = hostname;
1885 sip->realport = port;
1886 if(!sip->realport) sip->realport = 5060;
1888 /* TCP case */
1889 if(!sip->udp) {
1890 /* create socket for incoming connections */
1891 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
1892 simple_tcp_connect_listen_cb, sip);
1893 if (sip->listen_data == NULL) {
1894 purple_connection_error_reason(sip->gc,
1895 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1896 _("Unable to create listen socket"));
1897 return;
1899 } else { /* UDP */
1900 purple_debug_info("simple", "using udp with server %s and port %d\n", hostname, port);
1902 sip->query_data = purple_dnsquery_a(hostname, port, simple_udp_host_resolved, sip);
1903 if (sip->query_data == NULL) {
1904 purple_connection_error_reason(sip->gc,
1905 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1906 _("Unable to resolve hostname"));
1911 static void simple_login(PurpleAccount *account)
1913 PurpleConnection *gc;
1914 struct simple_account_data *sip;
1915 gchar **userserver;
1916 const gchar *hosttoconnect;
1918 const char *username = purple_account_get_username(account);
1919 gc = purple_account_get_connection(account);
1921 if (strpbrk(username, " \t\v\r\n") != NULL) {
1922 purple_connection_error_reason(gc,
1923 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1924 _("SIP usernames may not contain whitespaces or @ symbols"));
1925 return;
1928 gc->proto_data = sip = g_new0(struct simple_account_data, 1);
1929 sip->gc = gc;
1930 sip->account = account;
1931 sip->registerexpire = 900;
1932 sip->udp = purple_account_get_bool(account, "udp", FALSE);
1933 /* TODO: is there a good default grow size? */
1934 if(!sip->udp)
1935 sip->txbuf = purple_circ_buffer_new(0);
1937 userserver = g_strsplit(username, "@", 2);
1938 if (userserver[1] == NULL || userserver[1][0] == '\0') {
1939 purple_connection_error_reason(gc,
1940 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1941 _("SIP connect server not specified"));
1942 return;
1945 purple_connection_set_display_name(gc, userserver[0]);
1946 sip->username = g_strdup(userserver[0]);
1947 sip->servername = g_strdup(userserver[1]);
1948 sip->password = g_strdup(purple_connection_get_password(gc));
1949 g_strfreev(userserver);
1951 sip->buddies = g_hash_table_new((GHashFunc)simple_ht_hash_nick, (GEqualFunc)simple_ht_equals_nick);
1953 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
1955 /* TODO: Set the status correctly. */
1956 sip->status = g_strdup("available");
1958 if(!purple_account_get_bool(account, "useproxy", FALSE)) {
1959 hosttoconnect = sip->servername;
1960 } else {
1961 hosttoconnect = purple_account_get_string(account, "proxy", sip->servername);
1964 sip->srv_query_data = purple_srv_resolve("sip",
1965 sip->udp ? "udp" : "tcp", hosttoconnect, srvresolved, sip);
1968 static void simple_close(PurpleConnection *gc)
1970 struct simple_account_data *sip = gc->proto_data;
1972 if (!sip)
1973 return;
1975 /* unregister */
1976 if (sip->registerstatus == SIMPLE_REGISTER_COMPLETE)
1978 g_hash_table_foreach(sip->buddies,
1979 (GHFunc)simple_unsubscribe,
1980 (gpointer)sip);
1982 if (purple_account_get_bool(sip->account, "dopublish", TRUE))
1983 send_closed_publish(sip);
1985 do_register_exp(sip, 0);
1987 connection_free_all(sip);
1989 if (sip->listenpa)
1990 purple_input_remove(sip->listenpa);
1991 if (sip->tx_handler)
1992 purple_input_remove(sip->tx_handler);
1993 if (sip->resendtimeout)
1994 purple_timeout_remove(sip->resendtimeout);
1995 if (sip->registertimeout)
1996 purple_timeout_remove(sip->registertimeout);
1997 if (sip->query_data != NULL)
1998 purple_dnsquery_destroy(sip->query_data);
2000 if (sip->srv_query_data != NULL)
2001 purple_srv_cancel(sip->srv_query_data);
2003 if (sip->listen_data != NULL)
2004 purple_network_listen_cancel(sip->listen_data);
2006 if (sip->fd >= 0)
2007 close(sip->fd);
2008 if (sip->listenfd >= 0)
2009 close(sip->listenfd);
2011 g_free(sip->servername);
2012 g_free(sip->username);
2013 g_free(sip->password);
2014 g_free(sip->registrar.nonce);
2015 g_free(sip->registrar.opaque);
2016 g_free(sip->registrar.target);
2017 g_free(sip->registrar.realm);
2018 g_free(sip->registrar.digest_session_key);
2019 g_free(sip->proxy.nonce);
2020 g_free(sip->proxy.opaque);
2021 g_free(sip->proxy.target);
2022 g_free(sip->proxy.realm);
2023 g_free(sip->proxy.digest_session_key);
2024 g_free(sip->publish_etag);
2025 if (sip->txbuf)
2026 purple_circ_buffer_destroy(sip->txbuf);
2027 g_free(sip->realhostname);
2029 g_free(sip);
2030 gc->proto_data = NULL;
2033 static PurplePluginProtocolInfo prpl_info =
2036 NULL, /* user_splits */
2037 NULL, /* protocol_options */
2038 NO_BUDDY_ICONS, /* icon_spec */
2039 simple_list_icon, /* list_icon */
2040 NULL, /* list_emblems */
2041 NULL, /* status_text */
2042 NULL, /* tooltip_text */
2043 simple_status_types, /* away_states */
2044 NULL, /* blist_node_menu */
2045 NULL, /* chat_info */
2046 NULL, /* chat_info_defaults */
2047 simple_login, /* login */
2048 simple_close, /* close */
2049 simple_im_send, /* send_im */
2050 NULL, /* set_info */
2051 simple_typing, /* send_typing */
2052 NULL, /* get_info */
2053 simple_set_status, /* set_status */
2054 NULL, /* set_idle */
2055 NULL, /* change_passwd */
2056 simple_add_buddy, /* add_buddy */
2057 NULL, /* add_buddies */
2058 simple_remove_buddy, /* remove_buddy */
2059 NULL, /* remove_buddies */
2060 NULL, /* add_permit */
2061 NULL, /* add_deny */
2062 NULL, /* rem_permit */
2063 NULL, /* rem_deny */
2064 NULL, /* set_permit_deny */
2065 NULL, /* join_chat */
2066 NULL, /* reject_chat */
2067 NULL, /* get_chat_name */
2068 NULL, /* chat_invite */
2069 NULL, /* chat_leave */
2070 NULL, /* chat_whisper */
2071 NULL, /* chat_send */
2072 simple_keep_alive, /* keepalive */
2073 NULL, /* register_user */
2074 NULL, /* get_cb_info */
2075 NULL, /* get_cb_away */
2076 NULL, /* alias_buddy */
2077 NULL, /* group_buddy */
2078 NULL, /* rename_group */
2079 NULL, /* buddy_free */
2080 NULL, /* convo_closed */
2081 NULL, /* normalize */
2082 NULL, /* set_buddy_icon */
2083 NULL, /* remove_group */
2084 NULL, /* get_cb_real_name */
2085 NULL, /* set_chat_topic */
2086 NULL, /* find_blist_chat */
2087 NULL, /* roomlist_get_list */
2088 NULL, /* roomlist_cancel */
2089 NULL, /* roomlist_expand_category */
2090 NULL, /* can_receive_file */
2091 NULL, /* send_file */
2092 NULL, /* new_xfer */
2093 NULL, /* offline_message */
2094 NULL, /* whiteboard_prpl_ops */
2095 simple_send_raw, /* send_raw */
2096 NULL, /* roomlist_room_serialize */
2097 NULL, /* unregister_user */
2098 NULL, /* send_attention */
2099 NULL, /* get_attention_types */
2100 sizeof(PurplePluginProtocolInfo), /* struct_size */
2101 NULL, /* get_account_text_table */
2102 NULL, /* initiate_media */
2103 NULL, /* get_media_caps */
2104 NULL /* get_moods */
2108 static PurplePluginInfo info =
2110 PURPLE_PLUGIN_MAGIC,
2111 PURPLE_MAJOR_VERSION,
2112 PURPLE_MINOR_VERSION,
2113 PURPLE_PLUGIN_PROTOCOL, /**< type */
2114 NULL, /**< ui_requirement */
2115 0, /**< flags */
2116 NULL, /**< dependencies */
2117 PURPLE_PRIORITY_DEFAULT, /**< priority */
2119 "prpl-simple", /**< id */
2120 "SIMPLE", /**< name */
2121 DISPLAY_VERSION, /**< version */
2122 N_("SIP/SIMPLE Protocol Plugin"), /** summary */
2123 N_("The SIP/SIMPLE Protocol Plugin"), /** description */
2124 "Thomas Butter <butter@uni-mannheim.de>", /**< author */
2125 PURPLE_WEBSITE, /**< homepage */
2127 NULL, /**< load */
2128 NULL, /**< unload */
2129 NULL, /**< destroy */
2131 NULL, /**< ui_info */
2132 &prpl_info, /**< extra_info */
2133 NULL,
2134 NULL,
2136 /* padding */
2137 NULL,
2138 NULL,
2139 NULL,
2140 NULL
2143 static void _init_plugin(PurplePlugin *plugin)
2145 PurpleAccountUserSplit *split;
2146 PurpleAccountOption *option;
2148 split = purple_account_user_split_new(_("Server"), "", '@');
2149 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
2151 option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "dopublish", TRUE);
2152 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2154 option = purple_account_option_int_new(_("Connect port"), "port", 0);
2155 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2157 option = purple_account_option_bool_new(_("Use UDP"), "udp", FALSE);
2158 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2159 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
2160 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2161 option = purple_account_option_string_new(_("Proxy"), "proxy", "");
2162 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2163 option = purple_account_option_string_new(_("Auth User"), "authuser", "");
2164 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2165 option = purple_account_option_string_new(_("Auth Domain"), "authdomain", "");
2166 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2169 PURPLE_INIT_PLUGIN(simple, _init_plugin, info);