6 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
9 * Thanks to Google's Summer of Code Program and the helpful mentors
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
29 #include "accountopt.h"
30 #include "buddylist.h"
31 #include "conversation.h"
46 static PurpleProtocol
*my_protocol
= NULL
;
48 static gchar
*gentag(void) {
49 return g_strdup_printf("%04d%04d", g_random_int(), g_random_int());
52 static char *genbranch(void) {
53 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
54 g_random_int(), g_random_int(), g_random_int(),
55 g_random_int(), g_random_int());
58 static char *gencallid(void) {
59 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
60 g_random_int(), g_random_int(), g_random_int(),
61 g_random_int(), g_random_int(), g_random_int(),
62 g_random_int(), g_random_int());
65 static const char *simple_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
) {
70 simple_uri_handler_find_account(PurpleAccount
*account
,
71 G_GNUC_UNUSED gconstpointer data
)
73 const gchar
*protocol_id
;
75 protocol_id
= purple_account_get_protocol_id(account
);
77 if (purple_strequal(protocol_id
, "prpl-simple") &&
78 purple_account_is_connected(account
)) {
86 simple_uri_handler(const gchar
*scheme
, const gchar
*screenname
,
91 PurpleIMConversation
*im
;
93 g_return_val_if_fail(screenname
!= NULL
, FALSE
);
95 if (!purple_strequal(scheme
, "sip")) {
99 if (screenname
[0] == '\0') {
100 purple_debug_warning("simple",
101 "Invalid empty screenname in URI");
105 /* Find online SIMPLE account */
106 accounts
= purple_accounts_get_all();
107 account_node
= g_list_find_custom(
108 accounts
, NULL
, (GCompareFunc
)simple_uri_handler_find_account
);
110 if (account_node
== NULL
) {
114 im
= purple_im_conversation_new(account_node
->data
, screenname
);
115 purple_conversation_present(PURPLE_CONVERSATION(im
));
120 static void simple_keep_alive(PurpleConnection
*gc
) {
121 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
122 if(sip
->udp
) { /* in case of UDP send a packet only with a 0 byte to
123 remain in the NAT table */
124 gchar buf
[2] = {0, 0};
125 purple_debug_info("simple", "sending keep alive\n");
126 if (sendto(sip
->fd
, buf
, 1, 0,
127 (struct sockaddr
*)&sip
->serveraddr
,
128 sizeof(struct sockaddr_in
)) != 1)
130 purple_debug_error("simple", "failed sending keep alive\n");
136 static gboolean
process_register_response(struct simple_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
137 static void send_notify(struct simple_account_data
*sip
, struct simple_watcher
*);
139 static void send_open_publish(struct simple_account_data
*sip
);
140 static void send_closed_publish(struct simple_account_data
*sip
);
142 static void do_notifies(struct simple_account_data
*sip
) {
143 GSList
*tmp
= sip
->watcher
;
144 purple_debug_info("simple", "do_notifies()\n");
145 if((sip
->republish
!= -1) || sip
->republish
< time(NULL
)) {
146 if(purple_account_get_bool(sip
->account
, "dopublish", TRUE
)) {
147 send_open_publish(sip
);
152 purple_debug_info("simple", "notifying %s\n", ((struct simple_watcher
*)tmp
->data
)->name
);
153 send_notify(sip
, tmp
->data
);
158 static void simple_set_status(PurpleAccount
*account
, PurpleStatus
*status
) {
159 PurpleConnection
*gc
= purple_account_get_connection(account
);
160 PurpleStatusPrimitive primitive
= purple_status_type_get_primitive(purple_status_get_status_type(status
));
161 struct simple_account_data
*sip
= NULL
;
163 if (!purple_status_is_active(status
))
167 sip
= purple_connection_get_protocol_data(gc
);
172 if (primitive
== PURPLE_STATUS_AVAILABLE
)
173 sip
->status
= g_strdup("available");
175 sip
->status
= g_strdup("busy");
181 static struct sip_connection
*connection_find(struct simple_account_data
*sip
, int fd
) {
182 struct sip_connection
*ret
= NULL
;
183 GSList
*entry
= sip
->openconns
;
186 if(ret
->fd
== fd
) return ret
;
192 static struct simple_watcher
*watcher_find(struct simple_account_data
*sip
,
194 struct simple_watcher
*watcher
;
195 GSList
*entry
= sip
->watcher
;
197 watcher
= entry
->data
;
198 if(purple_strequal(name
, watcher
->name
)) return watcher
;
204 static struct simple_watcher
*watcher_create(struct simple_account_data
*sip
,
205 const gchar
*name
, const gchar
*callid
, const gchar
*ourtag
,
206 const gchar
*theirtag
, gboolean needsxpidf
) {
207 struct simple_watcher
*watcher
= g_new0(struct simple_watcher
, 1);
208 watcher
->name
= g_strdup(name
);
209 watcher
->dialog
.callid
= g_strdup(callid
);
210 watcher
->dialog
.ourtag
= g_strdup(ourtag
);
211 watcher
->dialog
.theirtag
= g_strdup(theirtag
);
212 watcher
->needsxpidf
= needsxpidf
;
213 sip
->watcher
= g_slist_append(sip
->watcher
, watcher
);
217 static void watcher_remove(struct simple_account_data
*sip
, const gchar
*name
) {
218 struct simple_watcher
*watcher
= watcher_find(sip
, name
);
219 sip
->watcher
= g_slist_remove(sip
->watcher
, watcher
);
220 g_free(watcher
->name
);
221 g_free(watcher
->dialog
.callid
);
222 g_free(watcher
->dialog
.ourtag
);
223 g_free(watcher
->dialog
.theirtag
);
227 static struct sip_connection
*connection_create(struct simple_account_data
*sip
, int fd
) {
228 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
230 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
234 static void connection_remove(struct simple_account_data
*sip
, int fd
) {
235 struct sip_connection
*conn
= connection_find(sip
, fd
);
236 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
237 if(conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
242 static void connection_free_all(struct simple_account_data
*sip
) {
243 struct sip_connection
*ret
= NULL
;
244 GSList
*entry
= sip
->openconns
;
247 connection_remove(sip
, ret
->fd
);
248 entry
= sip
->openconns
;
252 static void simple_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
, const char *message
)
254 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
255 struct simple_buddy
*b
;
256 const char *name
= purple_buddy_get_name(buddy
);
257 if(strncmp(name
, "sip:", 4)) {
258 gchar
*buf
= g_strdup_printf("sip:%s", name
);
259 purple_buddy_set_name(buddy
, buf
);
262 if(!g_hash_table_lookup(sip
->buddies
, name
)) {
263 b
= g_new0(struct simple_buddy
, 1);
264 purple_debug_info("simple", "simple_add_buddy %s\n", name
);
265 b
->name
= g_strdup(name
);
266 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
268 purple_debug_info("simple", "buddy %s already in internal list\n", name
);
272 static void simple_get_buddies(PurpleConnection
*gc
) {
274 PurpleAccount
*account
;
276 purple_debug_info("simple", "simple_get_buddies\n");
278 account
= purple_connection_get_account(gc
);
279 buddies
= purple_blist_find_buddies(account
, NULL
);
281 PurpleBuddy
*buddy
= buddies
->data
;
282 simple_add_buddy(gc
, buddy
, purple_buddy_get_group(buddy
), NULL
);
284 buddies
= g_slist_delete_link(buddies
, buddies
);
288 static void simple_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
290 const char *name
= purple_buddy_get_name(buddy
);
291 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
292 struct simple_buddy
*b
= g_hash_table_lookup(sip
->buddies
, name
);
293 g_hash_table_remove(sip
->buddies
, name
);
298 static GList
*simple_status_types(PurpleAccount
*acc
) {
299 PurpleStatusType
*type
;
302 type
= purple_status_type_new_with_attrs(
303 PURPLE_STATUS_AVAILABLE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
304 "message", _("Message"), purple_value_new(G_TYPE_STRING
),
306 types
= g_list_append(types
, type
);
308 type
= purple_status_type_new_full(
309 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
310 types
= g_list_append(types
, type
);
315 static gchar
*auth_header(struct simple_account_data
*sip
,
316 struct sip_auth
*auth
, const gchar
*method
, const gchar
*target
) {
320 const char *authuser
;
322 authuser
= purple_account_get_string(sip
->account
, "authuser", sip
->username
);
324 if(!authuser
|| strlen(authuser
) < 1) {
325 authuser
= sip
->username
;
328 if(auth
->type
== 1) { /* Digest */
329 sprintf(noncecount
, "%08d", auth
->nc
++);
330 response
= purple_http_digest_calculate_response(
331 "md5", method
, target
, NULL
, NULL
,
332 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
333 purple_debug(PURPLE_DEBUG_MISC
, "simple", "response %s\n", response
);
335 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
);
338 } else if(auth
->type
== 2) { /* NTLM */
340 const gchar
*authdomain
;
343 authdomain
= purple_account_get_string(sip
->account
,
346 if(auth
->nc
== 3 && auth
->nonce
) {
347 /* TODO: Don't hardcode "purple" as the hostname */
348 ret
= purple_ntlm_gen_type3(authuser
, sip
->password
, "purple", authdomain
, (const guint8
*)auth
->nonce
, &auth
->flags
);
349 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, ret
);
353 tmp
= g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth
->realm
, auth
->target
);
356 /* Used without support enabled */
357 g_return_val_if_reached(NULL
);
358 #endif /* HAVE_NETTLE */
361 sprintf(noncecount
, "%08d", auth
->nc
++);
362 response
= purple_http_digest_calculate_response(
363 "md5", method
, target
, NULL
, NULL
,
364 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
365 purple_debug(PURPLE_DEBUG_MISC
, "simple", "response %s\n", response
);
367 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
);
372 static char *parse_attribute(const char *attrname
, const char *source
) {
373 const char *tmp
, *tmp2
;
375 int len
= strlen(attrname
);
377 /* we know that source is NULL-terminated.
378 * Therefore this loop won't be infinite.
380 while (source
[0] == ' ')
383 if(!strncmp(source
, attrname
, len
)) {
385 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
387 retval
= g_strndup(tmp
, tmp2
- tmp
);
389 retval
= g_strdup(tmp
);
395 static void fill_auth(struct simple_account_data
*sip
, const gchar
*hdr
, struct sip_auth
*auth
) {
397 const char *authuser
;
401 authuser
= purple_account_get_string(sip
->account
, "authuser", sip
->username
);
403 if(!authuser
|| strlen(authuser
) < 1) {
404 authuser
= sip
->username
;
408 purple_debug_error("simple", "fill_auth: hdr==NULL\n");
412 if(!g_ascii_strncasecmp(hdr
, "NTLM", 4)) {
414 purple_debug_info("simple", "found NTLM\n");
416 parts
= g_strsplit(hdr
+5, "\",", 0);
419 purple_debug_info("simple", "parts[i] %s\n", parts
[i
]);
420 if((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
421 auth
->nonce
= g_memdup(purple_ntlm_parse_type2(tmp
, &auth
->flags
), 8);
424 if((tmp
= parse_attribute("targetname=\"",
428 else if((tmp
= parse_attribute("realm=\"",
432 else if((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
439 if(!strstr(hdr
, "gssapi-data")) {
447 purple_debug_error("simple", "NTLM auth unsupported without "
448 "libnettle support. Please rebuild with "
449 "libnettle support for this feature.\n");
450 #endif /* HAVE_NETTLE */
451 } else if(!g_ascii_strncasecmp(hdr
, "DIGEST", 6)) {
453 purple_debug_info("simple", "found DIGEST\n");
456 parts
= g_strsplit(hdr
+7, ",", 0);
458 if((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
461 else if((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
467 purple_debug(PURPLE_DEBUG_MISC
, "simple", "nonce: %s realm: %s\n",
468 auth
->nonce
? auth
->nonce
: "(null)",
469 auth
->realm
? auth
->realm
: "(null)");
472 auth
->digest_session_key
= purple_http_digest_calculate_session_key(
473 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
479 purple_debug_error("simple", "Unsupported or bad WWW-Authenticate header (%s).\n", hdr
);
484 static void simple_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
) {
485 PurpleConnection
*gc
= data
;
486 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
489 const gchar
*output
= NULL
;
491 max_write
= purple_circular_buffer_get_max_read(sip
->txbuf
);
494 purple_input_remove(sip
->tx_handler
);
499 output
= purple_circular_buffer_get_output(sip
->txbuf
);
501 written
= write(sip
->fd
, output
, max_write
);
503 if(written
< 0 && errno
== EAGAIN
)
505 else if (written
<= 0) {
506 /*TODO: do we really want to disconnect on a failure to write?*/
507 gchar
*tmp
= g_strdup_printf(_("Lost connection with server: %s"),
509 purple_connection_error(gc
,
510 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
515 purple_circular_buffer_mark_read(sip
->txbuf
, written
);
518 static void simple_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
520 static void send_later_cb(gpointer data
, gint source
, const gchar
*error_message
) {
521 PurpleConnection
*gc
= data
;
522 struct simple_account_data
*sip
;
523 struct sip_connection
*conn
;
526 gchar
*tmp
= g_strdup_printf(_("Unable to connect: %s"),
528 purple_connection_error(gc
,
529 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
534 sip
= purple_connection_get_protocol_data(gc
);
536 sip
->connecting
= FALSE
;
538 simple_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
540 /* If there is more to write now, we need to register a handler */
541 if(purple_circular_buffer_get_used(sip
->txbuf
) > 0)
542 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
,
543 simple_canwrite_cb
, gc
);
545 conn
= connection_create(sip
, source
);
546 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, simple_input_cb
, gc
);
550 static void sendlater(PurpleConnection
*gc
, const char *buf
) {
551 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
553 if(!sip
->connecting
) {
554 purple_debug_info("simple", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
555 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
556 purple_connection_error(gc
, PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, _("Unable to connect"));
558 sip
->connecting
= TRUE
;
561 if(purple_circular_buffer_get_max_read(sip
->txbuf
) > 0)
562 purple_circular_buffer_append(sip
->txbuf
, "\r\n", 2);
564 purple_circular_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
567 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
) {
568 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
569 time_t currtime
= time(NULL
);
570 int writelen
= strlen(buf
);
572 purple_debug(PURPLE_DEBUG_MISC
, "simple", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
574 if(sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
575 purple_debug_info("simple", "could not send packet\n");
584 if(sip
->tx_handler
) {
588 ret
= write(sip
->fd
, buf
, writelen
);
590 if (ret
< 0 && errno
== EAGAIN
)
592 else if(ret
<= 0) { /* XXX: When does this happen legitimately? */
597 if (ret
< writelen
) {
599 sip
->tx_handler
= purple_input_add(sip
->fd
,
600 PURPLE_INPUT_WRITE
, simple_canwrite_cb
,
603 /* XXX: is it OK to do this? You might get part of a request sent
604 with part of another. */
605 if(purple_circular_buffer_get_used(sip
->txbuf
) > 0)
606 purple_circular_buffer_append(sip
->txbuf
, "\r\n", 2);
608 purple_circular_buffer_append(sip
->txbuf
, buf
+ ret
,
614 static int simple_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
616 sendout_pkt(gc
, buf
);
620 static void sendout_sipmsg(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
621 GSList
*tmp
= msg
->headers
;
624 GString
*outstr
= g_string_new("");
625 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
627 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
628 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
629 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
630 tmp
= g_slist_next(tmp
);
632 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
633 sendout_pkt(sip
->gc
, outstr
->str
);
634 g_string_free(outstr
, TRUE
);
637 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
638 const char *text
, const char *body
) {
639 GSList
*tmp
= msg
->headers
;
642 GString
*outstr
= g_string_new("");
644 /* When sending the acknowlegements and errors, the content length from the original
645 message is still here, but there is no body; we need to make sure we're sending the
646 correct content length */
647 sipmsg_remove_header(msg
, "Content-Length");
650 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
651 sipmsg_add_header(msg
, "Content-Length", len
);
654 sipmsg_add_header(msg
, "Content-Length", "0");
655 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
657 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
658 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
660 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
661 tmp
= g_slist_next(tmp
);
663 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
664 sendout_pkt(gc
, outstr
->str
);
665 g_string_free(outstr
, TRUE
);
668 static void transactions_remove(struct simple_account_data
*sip
, struct transaction
*trans
) {
669 if(trans
->msg
) sipmsg_free(trans
->msg
);
670 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
674 static void transactions_add_buf(struct simple_account_data
*sip
, const gchar
*buf
, void *callback
) {
675 struct transaction
*trans
= g_new0(struct transaction
, 1);
676 trans
->time
= time(NULL
);
677 trans
->msg
= sipmsg_parse_msg(buf
);
678 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
679 trans
->callback
= callback
;
680 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
683 static struct transaction
*transactions_find(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
684 struct transaction
*trans
;
685 GSList
*transactions
= sip
->transactions
;
686 const gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
689 while(transactions
) {
690 trans
= transactions
->data
;
691 if(purple_strequal(trans
->cseq
, cseq
)) {
694 transactions
= transactions
->next
;
697 purple_debug(PURPLE_DEBUG_MISC
, "simple", "Received message contains no CSeq header.\n");
703 static void send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
704 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
705 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
) {
706 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
707 char *callid
= dialog
? g_strdup(dialog
->callid
) : gencallid();
709 const char *addh
= "";
710 gchar
*branch
= genbranch();
714 if(purple_strequal(method
, "REGISTER")) {
717 callid
= g_strdup(sip
->regcallid
);
719 else sip
->regcallid
= g_strdup(callid
);
722 if(addheaders
) addh
= addheaders
;
723 if(sip
->registrar
.type
&& purple_strequal(method
, "REGISTER")) {
724 buf
= auth_header(sip
, &sip
->registrar
, method
, url
);
725 auth
= g_strdup_printf("Authorization: %s\r\n", buf
);
727 purple_debug(PURPLE_DEBUG_MISC
, "simple", "header %s", auth
);
728 } else if(sip
->proxy
.type
&& !purple_strequal(method
, "REGISTER")) {
729 buf
= auth_header(sip
, &sip
->proxy
, method
, url
);
730 auth
= g_strdup_printf("Proxy-Authorization: %s\r\n", buf
);
732 purple_debug(PURPLE_DEBUG_MISC
, "simple", "header %s", auth
);
738 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
739 "Via: SIP/2.0/%s %s:%d;branch=%s\r\n"
740 /* Don't know what epid is, but LCS wants it */
741 "From: <sip:%s@%s>;tag=%s;epid=1234567890\r\n"
743 "Max-Forwards: 10\r\n"
745 "User-Agent: Purple/" VERSION
"\r\n"
748 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
751 sip
->udp
? "UDP" : "TCP",
752 purple_network_get_my_ip(-1),
757 dialog
? dialog
->ourtag
: tag
,
759 dialog
? ";tag=" : "",
760 dialog
? dialog
->theirtag
: "",
774 /* add to ongoing transactions */
776 transactions_add_buf(sip
, buf
, tc
);
778 sendout_pkt(gc
, buf
);
783 static char *get_contact(struct simple_account_data
*sip
) {
784 return g_strdup_printf("<sip:%s@%s:%d;transport=%s>;methods=\"MESSAGE, SUBSCRIBE, NOTIFY\"",
785 sip
->username
, purple_network_get_my_ip(-1),
787 sip
->udp
? "udp" : "tcp");
790 static void do_register_exp(struct simple_account_data
*sip
, int expire
) {
791 char *uri
, *to
, *contact
, *hdr
;
793 sip
->reregister
= time(NULL
) + expire
- 50;
795 uri
= g_strdup_printf("sip:%s", sip
->servername
);
796 to
= g_strdup_printf("sip:%s@%s", sip
->username
, sip
->servername
);
797 contact
= get_contact(sip
);
798 hdr
= g_strdup_printf("Contact: %s\r\nExpires: %d\r\n", contact
, expire
);
801 sip
->registerstatus
= SIMPLE_REGISTER_SENT
;
803 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
804 process_register_response
);
811 static void do_register(struct simple_account_data
*sip
) {
812 do_register_exp(sip
, sip
->registerexpire
);
815 static gchar
*parse_from(const gchar
*hdr
) {
817 const gchar
*tmp
, *tmp2
= hdr
;
819 if(!hdr
) return NULL
;
820 purple_debug_info("simple", "parsing address out of %s\n", hdr
);
821 tmp
= strchr(hdr
, '<');
823 /* i hate the different SIP UA behaviours... */
824 if(tmp
) { /* sip address in <...> */
826 tmp
= strchr(tmp2
, '>');
828 from
= g_strndup(tmp2
, tmp
- tmp2
);
830 purple_debug_info("simple", "found < without > in From\n");
834 tmp
= strchr(tmp2
, ';');
836 from
= g_strndup(tmp2
, tmp
- tmp2
);
838 from
= g_strdup(tmp2
);
841 purple_debug_info("simple", "got %s\n", from
);
844 static gchar
*find_tag(const gchar
*);
846 static gboolean
process_subscribe_response(struct simple_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
) {
848 struct simple_buddy
*b
= NULL
;
849 gchar
*theirtag
= NULL
, *ourtag
= NULL
;
850 const gchar
*callid
= NULL
;
852 purple_debug_info("simple", "process subscribe response\n");
854 if(msg
->response
== 200 || msg
->response
== 202) {
855 if ( (to
= parse_from(sipmsg_find_header(msg
, "To"))) &&
856 (b
= g_hash_table_lookup(sip
->buddies
, to
)) &&
859 purple_debug_info("simple", "creating dialog"
860 " information for a subscription.\n");
862 theirtag
= find_tag(sipmsg_find_header(msg
, "To"));
863 ourtag
= find_tag(sipmsg_find_header(msg
, "From"));
864 callid
= sipmsg_find_header(msg
, "Call-ID");
866 if (theirtag
&& ourtag
&& callid
)
868 b
->dialog
= g_new0(struct sip_dialog
, 1);
869 b
->dialog
->ourtag
= g_strdup(ourtag
);
870 b
->dialog
->theirtag
= g_strdup(theirtag
);
871 b
->dialog
->callid
= g_strdup(callid
);
873 purple_debug_info("simple", "ourtag: %s\n",
875 purple_debug_info("simple", "theirtag: %s\n",
877 purple_debug_info("simple", "callid: %s\n",
885 purple_debug_info("simple", "cannot create dialog!\n");
890 to
= parse_from(sipmsg_find_header(tc
->msg
, "To")); /* cant be NULL since it is our own msg */
892 /* we can not subscribe -> user is offline (TODO unknown status?) */
894 purple_protocol_got_user_status(sip
->account
, to
, "offline", NULL
);
899 static void simple_subscribe_exp(struct simple_account_data
*sip
, struct simple_buddy
*buddy
, int expiration
) {
900 gchar
*contact
, *to
, *tmp
, *tmp2
;
902 tmp2
= g_strdup_printf(
904 "Accept: application/pidf+xml, application/xpidf+xml\r\n"
905 "Event: presence\r\n",
908 if(strncmp(buddy
->name
, "sip:", 4))
909 to
= g_strdup_printf("sip:%s", buddy
->name
);
911 to
= g_strdup(buddy
->name
);
913 tmp
= get_contact(sip
);
914 contact
= g_strdup_printf("%sContact: %s\r\n", tmp2
, tmp
);
918 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, contact
,"",buddy
->dialog
,
919 (expiration
> 0) ? process_subscribe_response
: NULL
);
924 /* resubscribe before subscription expires */
925 /* add some jitter */
927 buddy
->resubscribe
= time(NULL
) + (expiration
- 60) + (g_random_int_range(0, 50));
928 else if (expiration
> 0)
929 buddy
->resubscribe
= time(NULL
) + ((int) (expiration
/ 2));
932 static void simple_subscribe(struct simple_account_data
*sip
, struct simple_buddy
*buddy
) {
933 simple_subscribe_exp(sip
, buddy
, SUBSCRIBE_EXPIRATION
);
936 static void simple_unsubscribe(char *name
, struct simple_buddy
*buddy
, struct simple_account_data
*sip
) {
939 purple_debug_info("simple", "Unsubscribing from %s\n", name
);
940 simple_subscribe_exp(sip
, buddy
, 0);
944 static gboolean
simple_add_lcs_contacts(struct simple_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
) {
946 PurpleXmlNode
*item
, *group
, *isc
;
947 const char *name_group
;
949 PurpleGroup
*g
= NULL
;
950 struct simple_buddy
*bs
;
951 int len
= msg
->bodylen
;
954 tmp
= sipmsg_find_header(msg
, "Event");
955 if(tmp
&& !strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)){
957 purple_debug_info("simple", "simple_add_lcs_contacts->%s-%d\n", msg
->body
, len
);
958 /*Convert the contact from XML to Purple Buddies*/
959 isc
= purple_xmlnode_from_str(msg
->body
, len
);
961 /* ToDo. Find for all groups */
962 if ((group
= purple_xmlnode_get_child(isc
, "group"))) {
963 name_group
= purple_xmlnode_get_attrib(group
, "name");
964 purple_debug_info("simple", "name_group->%s\n", name_group
);
965 g
= purple_blist_find_group(name_group
);
967 g
= purple_group_new(name_group
);
971 g
= purple_blist_get_default_group();
973 for(item
= purple_xmlnode_get_child(isc
, "contact"); item
; item
= purple_xmlnode_get_next_twin(item
))
977 uri
= purple_xmlnode_get_attrib(item
, "uri");
978 /*name = purple_xmlnode_get_attrib(item, "name");
979 groups = purple_xmlnode_get_attrib(item, "groups");*/
980 purple_debug_info("simple", "URI->%s\n", uri
);
982 buddy_name
= g_strdup_printf("sip:%s", uri
);
984 b
= purple_blist_find_buddy(sip
->account
, buddy_name
);
986 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
990 purple_blist_add_buddy(b
, NULL
, g
, NULL
);
991 purple_buddy_set_local_alias(b
, uri
);
992 bs
= g_new0(struct simple_buddy
, 1);
993 bs
->name
= g_strdup(purple_buddy_get_name(b
));
994 g_hash_table_insert(sip
->buddies
, bs
->name
, bs
);
996 purple_xmlnode_free(isc
);
1001 static void simple_subscribe_buddylist(struct simple_account_data
*sip
) {
1002 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";
1005 to
= g_strdup_printf("sip:%s@%s", sip
->username
, sip
->servername
);
1007 tmp
= get_contact(sip
);
1009 contact
= g_strdup_printf("%sContact: %s\r\n", contact
, tmp
);
1012 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, contact
, "", NULL
, simple_add_lcs_contacts
);
1019 static void simple_buddy_resub(char *name
, struct simple_buddy
*buddy
, struct simple_account_data
*sip
) {
1020 time_t curtime
= time(NULL
);
1021 purple_debug_info("simple", "buddy resub\n");
1022 if(buddy
->resubscribe
< curtime
) {
1023 purple_debug(PURPLE_DEBUG_MISC
, "simple", "simple_buddy_resub %s\n", name
);
1024 simple_subscribe(sip
, buddy
);
1028 static gboolean
resend_timeout(struct simple_account_data
*sip
) {
1029 GSList
*tmp
= sip
->transactions
;
1030 time_t currtime
= time(NULL
);
1032 struct transaction
*trans
= tmp
->data
;
1034 purple_debug_info("simple", "have open transaction age: %lu\n", currtime
- trans
->time
);
1035 if((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
1038 if((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
1040 sendout_sipmsg(sip
, trans
->msg
);
1047 static gboolean
subscribe_timeout(struct simple_account_data
*sip
) {
1049 time_t curtime
= time(NULL
);
1050 /* register again if first registration expires */
1051 if(sip
->reregister
< curtime
) {
1055 /* publish status again if our last update is about to expire. */
1056 if (sip
->republish
!= -1 &&
1057 sip
->republish
< curtime
&&
1058 purple_account_get_bool(sip
->account
, "dopublish", TRUE
))
1060 purple_debug_info("simple", "subscribe_timeout: republishing status.\n");
1061 send_open_publish(sip
);
1064 /* check for every subscription if we need to resubscribe */
1065 g_hash_table_foreach(sip
->buddies
, (GHFunc
)simple_buddy_resub
, (gpointer
)sip
);
1067 /* remove a timed out suscriber */
1070 struct simple_watcher
*watcher
= tmp
->data
;
1071 if(watcher
->expire
< curtime
) {
1072 watcher_remove(sip
, watcher
->name
);
1075 if(tmp
) tmp
= tmp
->next
;
1081 static void simple_send_message(struct simple_account_data
*sip
, const char *to
, const char *msg
, const char *type
) {
1084 if(strncmp(to
, "sip:", 4))
1085 fullto
= g_strdup_printf("sip:%s", to
);
1087 fullto
= g_strdup(to
);
1090 hdr
= g_strdup_printf("Content-Type: %s\r\n", type
);
1092 hdr
= g_strdup("Content-Type: text/plain\r\n");
1094 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msg
, NULL
, NULL
);
1099 static int simple_im_send(PurpleConnection
*gc
, PurpleMessage
*msg
) {
1100 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
1101 char *to
= g_strdup(purple_message_get_recipient(msg
));
1102 char *text
= purple_unescape_html(purple_message_get_contents(msg
));
1103 simple_send_message(sip
, to
, text
, NULL
);
1109 static void process_incoming_message(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
1111 const gchar
*contenttype
;
1112 gboolean found
= FALSE
;
1114 from
= parse_from(sipmsg_find_header(msg
, "From"));
1118 purple_debug(PURPLE_DEBUG_MISC
, "simple", "got message from %s: %s\n", from
, msg
->body
);
1120 contenttype
= sipmsg_find_header(msg
, "Content-Type");
1121 if(!contenttype
|| !strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
1122 purple_serv_got_im(sip
->gc
, from
, msg
->body
, 0, time(NULL
));
1123 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
1126 else if(!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
1127 PurpleXmlNode
*isc
= purple_xmlnode_from_str(msg
->body
, msg
->bodylen
);
1128 PurpleXmlNode
*state
;
1132 purple_debug_info("simple", "process_incoming_message: can not parse iscomposing\n");
1137 state
= purple_xmlnode_get_child(isc
, "state");
1140 purple_debug_info("simple", "process_incoming_message: no state found\n");
1141 purple_xmlnode_free(isc
);
1146 statedata
= purple_xmlnode_get_data(state
);
1148 if(strstr(statedata
, "active"))
1149 purple_serv_got_typing(sip
->gc
, from
, 0, PURPLE_IM_TYPING
);
1151 purple_serv_got_typing_stopped(sip
->gc
, from
);
1155 purple_xmlnode_free(isc
);
1156 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
1160 purple_debug_info("simple", "got unknown mime-type\n");
1161 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
1167 gboolean
process_register_response(struct simple_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
) {
1169 purple_debug(PURPLE_DEBUG_MISC
, "simple", "in process register response response: %d\n", msg
->response
);
1170 switch (msg
->response
) {
1172 if(sip
->registerstatus
< SIMPLE_REGISTER_COMPLETE
) { /* registered */
1173 if(purple_account_get_bool(sip
->account
, "dopublish", TRUE
)) {
1174 send_open_publish(sip
);
1177 sip
->registerstatus
= SIMPLE_REGISTER_COMPLETE
;
1178 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTION_CONNECTED
);
1180 /* get buddies from blist */
1181 simple_get_buddies(sip
->gc
);
1183 subscribe_timeout(sip
);
1184 tmp
= sipmsg_find_header(msg
, "Allow-Events");
1185 if(tmp
&& strstr(tmp
, "vnd-microsoft-provisioning")){
1186 simple_subscribe_buddylist(sip
);
1191 if(sip
->registerstatus
!= SIMPLE_REGISTER_RETRY
) {
1192 purple_debug_info("simple", "REGISTER retries %d\n", sip
->registrar
.retries
);
1193 if(sip
->registrar
.retries
> SIMPLE_REGISTER_RETRY_MAX
) {
1194 if (!purple_account_get_remember_password(purple_connection_get_account(sip
->gc
)))
1195 purple_account_set_password(purple_connection_get_account(sip
->gc
), NULL
, NULL
, NULL
);
1196 purple_connection_error(sip
->gc
,
1197 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED
,
1198 _("Incorrect password"));
1201 tmp
= sipmsg_find_header(msg
, "WWW-Authenticate");
1202 fill_auth(sip
, tmp
, &sip
->registrar
);
1203 sip
->registerstatus
= SIMPLE_REGISTER_RETRY
;
1208 if (sip
->registerstatus
!= SIMPLE_REGISTER_RETRY
) {
1209 purple_debug_info("simple", "Unrecognized return code for REGISTER.\n");
1210 if (sip
->registrar
.retries
> SIMPLE_REGISTER_RETRY_MAX
) {
1211 purple_connection_error(sip
->gc
,
1212 PURPLE_CONNECTION_ERROR_OTHER_ERROR
,
1213 _("Unknown server response"));
1216 sip
->registerstatus
= SIMPLE_REGISTER_RETRY
;
1224 static gboolean
dialog_match(struct sip_dialog
*dialog
, struct sipmsg
*msg
)
1226 const gchar
*fromhdr
;
1228 const gchar
*callid
;
1229 gchar
*ourtag
, *theirtag
;
1230 gboolean match
= FALSE
;
1232 fromhdr
= sipmsg_find_header(msg
, "From");
1233 tohdr
= sipmsg_find_header(msg
, "To");
1234 callid
= sipmsg_find_header(msg
, "Call-ID");
1236 if (!fromhdr
|| !tohdr
|| !callid
)
1239 ourtag
= find_tag(tohdr
);
1240 theirtag
= find_tag(fromhdr
);
1242 if (ourtag
&& theirtag
&&
1243 purple_strequal(dialog
->callid
, callid
) &&
1244 purple_strequal(dialog
->ourtag
, ourtag
) &&
1245 purple_strequal(dialog
->theirtag
, theirtag
))
1254 static void process_incoming_notify(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
1256 const gchar
*fromhdr
;
1257 gchar
*basicstatus_data
;
1258 PurpleXmlNode
*pidf
;
1259 PurpleXmlNode
*basicstatus
= NULL
, *tuple
, *status
;
1260 gboolean isonline
= FALSE
;
1261 struct simple_buddy
*b
= NULL
;
1262 const gchar
*sshdr
= NULL
;
1264 fromhdr
= sipmsg_find_header(msg
, "From");
1265 from
= parse_from(fromhdr
);
1268 b
= g_hash_table_lookup(sip
->buddies
, from
);
1272 purple_debug_info("simple", "Could not find the buddy.\n");
1276 if (b
->dialog
&& !dialog_match(b
->dialog
, msg
))
1278 /* We only accept notifies from people that
1279 * we already have a dialog with.
1281 purple_debug_info("simple","No corresponding dialog for notify--discard\n");
1286 pidf
= purple_xmlnode_from_str(msg
->body
, msg
->bodylen
);
1289 purple_debug_info("simple", "process_incoming_notify: no parseable pidf\n");
1290 sshdr
= sipmsg_find_header(msg
, "Subscription-State");
1294 gchar
**ssparts
= g_strsplit(sshdr
, ":", 0);
1297 g_strchug(ssparts
[i
]);
1298 if (purple_str_has_prefix(ssparts
[i
], "terminated"))
1300 purple_debug_info("simple", "Subscription expired!");
1303 g_free(b
->dialog
->ourtag
);
1304 g_free(b
->dialog
->theirtag
);
1305 g_free(b
->dialog
->callid
);
1310 purple_protocol_got_user_status(sip
->account
, from
, "offline", NULL
);
1315 g_strfreev(ssparts
);
1317 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
1322 if ((tuple
= purple_xmlnode_get_child(pidf
, "tuple")))
1323 if ((status
= purple_xmlnode_get_child(tuple
, "status")))
1324 basicstatus
= purple_xmlnode_get_child(status
, "basic");
1327 purple_debug_info("simple", "process_incoming_notify: no basic found\n");
1328 purple_xmlnode_free(pidf
);
1333 basicstatus_data
= purple_xmlnode_get_data(basicstatus
);
1335 if(!basicstatus_data
) {
1336 purple_debug_info("simple", "process_incoming_notify: no basic data found\n");
1337 purple_xmlnode_free(pidf
);
1342 if(strstr(basicstatus_data
, "open"))
1347 purple_protocol_got_user_status(sip
->account
, from
, "available", NULL
);
1349 purple_protocol_got_user_status(sip
->account
, from
, "offline", NULL
);
1351 purple_xmlnode_free(pidf
);
1353 g_free(basicstatus_data
);
1355 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
1358 static unsigned int simple_typing(PurpleConnection
*gc
, const char *name
, PurpleIMTypingState state
) {
1359 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
1361 gchar
*xml
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1362 "<isComposing xmlns=\"urn:ietf:params:xml:ns:im-iscomposing\"\n"
1363 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1364 "xsi:schemaLocation=\"urn:ietf:params:xml:ns:im-composing iscomposing.xsd\">\n"
1365 "<state>%s</state>\n"
1366 "<contenttype>text/plain</contenttype>\n"
1367 "<refresh>60</refresh>\n"
1369 gchar
*recv
= g_strdup(name
);
1370 if(state
== PURPLE_IM_TYPING
) {
1371 gchar
*msg
= g_strdup_printf(xml
, "active");
1372 simple_send_message(sip
, recv
, msg
, "application/im-iscomposing+xml");
1374 } else /* TODO: Only if (state == PURPLE_IM_TYPED) ? */ {
1375 gchar
*msg
= g_strdup_printf(xml
, "idle");
1376 simple_send_message(sip
, recv
, msg
, "application/im-iscomposing+xml");
1381 * TODO: Is this right? It will cause the core to call
1382 * purple_serv_send_typing(gc, who, PURPLE_IM_TYPING) once every second
1383 * until the user stops typing. If that's not desired,
1384 * then return 0 instead.
1389 static gchar
*find_tag(const gchar
*hdr
) {
1390 const gchar
*tmp
= strstr(hdr
, ";tag="), *tmp2
;
1392 if(!tmp
) return NULL
;
1394 if((tmp2
= strchr(tmp
, ';'))) {
1395 return g_strndup(tmp
, tmp2
- tmp
);
1397 return g_strdup(tmp
);
1400 static gchar
* gen_xpidf(struct simple_account_data
*sip
) {
1401 gchar
*doc
= g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1403 "<presentity uri=\"sip:%s@%s;method=SUBSCRIBE\"/>\n"
1404 "<display name=\"sip:%s@%s\"/>\n"
1405 "<atom id=\"1234\">\n"
1406 "<address uri=\"sip:%s@%s\">\n"
1407 "<status status=\"%s\"/>\n"
1421 static gchar
* gen_pidf(struct simple_account_data
*sip
, gboolean open
) {
1422 gchar
*doc
= g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1423 "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\n"
1424 "xmlns:im=\"urn:ietf:params:xml:ns:pidf:im\"\n"
1425 "entity=\"sip:%s@%s\">\n"
1426 "<tuple id=\"bs35r9f\">\n"
1428 "<basic>%s</basic>\n"
1435 (open
== TRUE
) ? "open" : "closed",
1436 (open
== TRUE
) ? sip
->status
: "");
1440 static void send_notify(struct simple_account_data
*sip
, struct simple_watcher
*watcher
) {
1441 gchar
*doc
= watcher
->needsxpidf
? gen_xpidf(sip
) : gen_pidf(sip
, TRUE
);
1442 gchar
*hdr
= watcher
->needsxpidf
? "Event: presence\r\nContent-Type: application/xpidf+xml\r\n" : "Event: presence\r\nContent-Type: application/pidf+xml\r\n";
1443 send_sip_request(sip
->gc
, "NOTIFY", watcher
->name
, watcher
->name
, hdr
, doc
, &watcher
->dialog
, NULL
);
1447 static gboolean
process_publish_response(struct simple_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
) {
1449 const gchar
*etag
= NULL
;
1451 if(msg
->response
!= 200 && msg
->response
!= 408) {
1452 /* never send again */
1453 sip
->republish
= -1;
1456 etag
= sipmsg_find_header(msg
, "SIP-Etag");
1458 /* we must store the etag somewhere. */
1459 g_free(sip
->publish_etag
);
1460 sip
->publish_etag
= g_strdup(etag
);
1466 static void send_open_publish(struct simple_account_data
*sip
) {
1467 gchar
*add_headers
= NULL
;
1468 gchar
*uri
= g_strdup_printf("sip:%s@%s", sip
->username
, sip
->servername
);
1469 gchar
*doc
= gen_pidf(sip
, TRUE
);
1471 add_headers
= g_strdup_printf("%s%s%s%s%d\r\n%s",
1472 sip
->publish_etag
? "SIP-If-Match: " : "",
1473 sip
->publish_etag
? sip
->publish_etag
: "",
1474 sip
->publish_etag
? "\r\n" : "",
1475 "Expires: ", PUBLISH_EXPIRATION
,
1476 "Event: presence\r\n"
1477 "Content-Type: application/pidf+xml\r\n");
1479 send_sip_request(sip
->gc
, "PUBLISH", uri
, uri
,
1480 add_headers
, doc
, NULL
, process_publish_response
);
1481 sip
->republish
= time(NULL
) + PUBLISH_EXPIRATION
- 50;
1484 g_free(add_headers
);
1487 static void send_closed_publish(struct simple_account_data
*sip
) {
1488 gchar
*uri
= g_strdup_printf("sip:%s@%s", sip
->username
, sip
->servername
);
1489 gchar
*add_headers
, *doc
;
1491 add_headers
= g_strdup_printf("%s%s%s%s",
1492 sip
->publish_etag
? "SIP-If-Match: " : "",
1493 sip
->publish_etag
? sip
->publish_etag
: "",
1494 sip
->publish_etag
? "\r\n" : "",
1496 "Event: presence\r\n"
1497 "Content-Type: application/pidf+xml\r\n");
1499 doc
= gen_pidf(sip
, FALSE
);
1500 send_sip_request(sip
->gc
, "PUBLISH", uri
, uri
, add_headers
,
1501 doc
, NULL
, process_publish_response
);
1502 /*sip->republish = time(NULL) + 500;*/
1505 g_free(add_headers
);
1508 static void process_incoming_subscribe(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
1509 const char *from_hdr
= sipmsg_find_header(msg
, "From");
1510 gchar
*from
= parse_from(from_hdr
);
1511 gchar
*theirtag
= find_tag(from_hdr
);
1512 gchar
*ourtag
= find_tag(sipmsg_find_header(msg
, "To"));
1513 gboolean tagadded
= FALSE
;
1514 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
1515 const gchar
*expire
= sipmsg_find_header(msg
, "Expire");
1517 struct simple_watcher
*watcher
= watcher_find(sip
, from
);
1522 if(!watcher
) { /* new subscription */
1523 const gchar
*acceptheader
= sipmsg_find_header(msg
, "Accept");
1524 gboolean needsxpidf
= FALSE
;
1525 if(!purple_account_privacy_check(sip
->account
, from
)) {
1526 send_sip_response(sip
->gc
, msg
, 202, "Ok", NULL
);
1530 const gchar
*tmp
= acceptheader
;
1531 gboolean foundpidf
= FALSE
;
1532 gboolean foundxpidf
= FALSE
;
1533 while(tmp
&& tmp
< acceptheader
+ strlen(acceptheader
)) {
1534 gchar
*tmp2
= strchr(tmp
, ',');
1535 if(tmp2
) *tmp2
= '\0';
1536 if(!g_ascii_strcasecmp("application/pidf+xml", tmp
))
1538 if(!g_ascii_strcasecmp("application/xpidf+xml", tmp
))
1543 while(*tmp
== ' ') tmp
++;
1547 if(!foundpidf
&& foundxpidf
) needsxpidf
= TRUE
;
1549 watcher
= watcher_create(sip
, from
, callid
, ourtag
, theirtag
, needsxpidf
);
1552 gchar
*to
= g_strdup_printf("%s;tag=%s", sipmsg_find_header(msg
, "To"), ourtag
);
1553 sipmsg_remove_header(msg
, "To");
1554 sipmsg_add_header(msg
, "To", to
);
1558 watcher
->expire
= time(NULL
) + strtol(expire
, NULL
, 10);
1560 watcher
->expire
= time(NULL
) + 600;
1561 sipmsg_remove_header(msg
, "Contact");
1562 tmp
= get_contact(sip
);
1563 sipmsg_add_header(msg
, "Contact", tmp
);
1565 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
);
1566 send_sip_response(sip
->gc
, msg
, 200, "Ok", NULL
);
1567 send_notify(sip
, watcher
);
1574 static void process_input_message(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
1575 gboolean found
= FALSE
;
1576 if(msg
->response
== 0) { /* request */
1577 if(purple_strequal(msg
->method
, "MESSAGE")) {
1578 process_incoming_message(sip
, msg
);
1580 } else if(purple_strequal(msg
->method
, "NOTIFY")) {
1581 process_incoming_notify(sip
, msg
);
1583 } else if(purple_strequal(msg
->method
, "SUBSCRIBE")) {
1584 process_incoming_subscribe(sip
, msg
);
1587 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
1589 } else { /* response */
1590 struct transaction
*trans
= transactions_find(sip
, msg
);
1592 if(msg
->response
== 407) {
1593 gchar
*resend
, *auth
;
1596 if(sip
->proxy
.retries
> 3) return;
1597 sip
->proxy
.retries
++;
1598 /* do proxy authentication */
1600 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
1602 fill_auth(sip
, ptmp
, &sip
->proxy
);
1603 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
->method
, trans
->msg
->target
);
1604 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
1605 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
1607 resend
= sipmsg_to_string(trans
->msg
);
1608 /* resend request */
1609 sendout_pkt(sip
->gc
, resend
);
1612 if(msg
->response
== 100) {
1613 /* ignore provisional response */
1614 purple_debug_info("simple", "got trying response\n");
1616 sip
->proxy
.retries
= 0;
1617 if(purple_strequal(trans
->msg
->method
, "REGISTER")) {
1619 /* This is encountered when a REGISTER request was ...
1621 if(msg
->response
== 401) {
1622 /* denied until further authentication was provided. */
1623 sip
->registrar
.retries
++;
1625 else if (msg
->response
!= 200) {
1626 /* denied for some other reason! */
1627 sip
->registrar
.retries
++;
1631 sip
->registrar
.retries
= 0;
1634 if(msg
->response
== 401) {
1635 /* This is encountered when a generic (MESSAGE, NOTIFY, etc)
1636 * was denied until further authorization is provided.
1638 gchar
*resend
, *auth
;
1641 if(sip
->registrar
.retries
> SIMPLE_REGISTER_RETRY_MAX
) return;
1642 sip
->registrar
.retries
++;
1644 ptmp
= sipmsg_find_header(msg
, "WWW-Authenticate");
1646 fill_auth(sip
, ptmp
, &sip
->registrar
);
1647 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
->method
, trans
->msg
->target
);
1648 sipmsg_remove_header(trans
->msg
, "Authorization");
1649 sipmsg_add_header(trans
->msg
, "Authorization", auth
);
1651 resend
= sipmsg_to_string(trans
->msg
);
1652 /* resend request */
1653 sendout_pkt(sip
->gc
, resend
);
1656 /* Reset any count of retries that may have
1657 * accumulated in the above branch.
1659 sip
->registrar
.retries
= 0;
1662 if(trans
->callback
) {
1663 /* call the callback to process response*/
1664 (trans
->callback
)(sip
, msg
, trans
);
1666 transactions_remove(sip
, trans
);
1671 purple_debug(PURPLE_DEBUG_MISC
, "simple", "received response to unknown transaction");
1675 purple_debug(PURPLE_DEBUG_MISC
, "simple", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
1679 static void process_input(struct simple_account_data
*sip
, struct sip_connection
*conn
)
1687 /* according to the RFC remove CRLF at the beginning */
1688 while(*cur
== '\r' || *cur
== '\n') {
1691 if(cur
!= conn
->inbuf
) {
1692 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
1693 conn
->inbufused
= strlen(conn
->inbuf
);
1696 /* Received a full Header? */
1697 if((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
) {
1698 time_t currtime
= time(NULL
);
1701 purple_debug_info("simple", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
1702 msg
= sipmsg_parse_header(conn
->inbuf
);
1705 /* Should we re-use this error message (from lower in the function)? */
1706 purple_debug_misc("simple", "received a incomplete sip msg: %s\n", conn
->inbuf
);
1712 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
1713 if(restlen
>= msg
->bodylen
) {
1714 dummy
= g_new(char, msg
->bodylen
+ 1);
1715 memcpy(dummy
, cur
, msg
->bodylen
);
1716 dummy
[msg
->bodylen
] = '\0';
1718 cur
+= msg
->bodylen
;
1719 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
1720 conn
->inbufused
= strlen(conn
->inbuf
);
1725 purple_debug(PURPLE_DEBUG_MISC
, "simple", "in process response response: %d\n", msg
->response
);
1726 process_input_message(sip
, msg
);
1729 purple_debug(PURPLE_DEBUG_MISC
, "simple", "received a incomplete sip msg: %s\n", conn
->inbuf
);
1733 static void simple_udp_process(gpointer data
, gint source
, PurpleInputCondition con
) {
1734 PurpleConnection
*gc
= data
;
1735 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
1738 time_t currtime
= time(NULL
);
1740 static char buffer
[65536];
1741 if((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
1743 purple_debug_info("simple", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
1744 msg
= sipmsg_parse_msg(buffer
);
1746 process_input_message(sip
, msg
);
1752 static void simple_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
1754 PurpleConnection
*gc
= data
;
1755 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
1757 struct sip_connection
*conn
= connection_find(sip
, source
);
1759 purple_debug_error("simple", "Connection not found!\n");
1763 if(conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
1764 conn
->inbuflen
+= SIMPLE_BUF_INC
;
1765 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
1768 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
1770 if(len
< 0 && errno
== EAGAIN
)
1773 purple_debug_info("simple", "simple_input_cb: read error\n");
1774 connection_remove(sip
, source
);
1775 if(sip
->fd
== source
) sip
->fd
= -1;
1778 purple_connection_update_last_received(gc
);
1779 conn
->inbufused
+= len
;
1780 conn
->inbuf
[conn
->inbufused
] = '\0';
1782 process_input(sip
, conn
);
1785 /* Callback for new connections on incoming TCP port */
1786 static void simple_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
) {
1787 PurpleConnection
*gc
= data
;
1788 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
1789 struct sip_connection
*conn
;
1792 newfd
= accept(source
, NULL
, NULL
);
1793 g_return_if_fail(newfd
>= 0);
1795 _purple_network_set_common_socket_flags(newfd
);
1797 conn
= connection_create(sip
, newfd
);
1799 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, simple_input_cb
, gc
);
1802 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
) {
1803 PurpleConnection
*gc
= data
;
1804 struct simple_account_data
*sip
;
1805 struct sip_connection
*conn
;
1808 gchar
*tmp
= g_strdup_printf(_("Unable to connect: %s"),
1810 purple_connection_error(gc
,
1811 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
1816 sip
= purple_connection_get_protocol_data(gc
);
1819 conn
= connection_create(sip
, source
);
1821 sip
->registertimeout
= g_timeout_add(g_random_int_range(10000, 100000), (GSourceFunc
)subscribe_timeout
, sip
);
1825 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, simple_input_cb
, gc
);
1828 static guint
simple_ht_hash_nick(const char *nick
) {
1829 char *lc
= g_utf8_strdown(nick
, -1);
1830 guint bucket
= g_str_hash(lc
);
1836 static gboolean
simple_ht_equals_nick(const char *nick1
, const char *nick2
) {
1837 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
1840 static void simple_udp_host_resolved_listen_cb(int listenfd
, gpointer data
) {
1841 struct simple_account_data
*sip
= (struct simple_account_data
*) data
;
1843 sip
->listen_data
= NULL
;
1845 if(listenfd
== -1) {
1846 purple_connection_error(sip
->gc
,
1847 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1848 _("Unable to create listen socket"));
1853 * TODO: Is it correct to set sip->fd to the listenfd? For the TCP
1854 * listener we set sip->listenfd, but maybe UDP is different?
1855 * Maybe we use the same fd for outgoing data or something?
1859 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
1861 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, simple_udp_process
, sip
->gc
);
1863 sip
->resendtimeout
= g_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
1864 sip
->registertimeout
= g_timeout_add(g_random_int_range(10000, 100000), (GSourceFunc
)subscribe_timeout
, sip
);
1869 simple_udp_host_resolved(GObject
*sender
, GAsyncResult
*result
, gpointer data
) {
1870 GError
*error
= NULL
;
1871 GList
*addresses
= NULL
;
1872 GInetAddress
*inet_address
= NULL
;
1873 GSocketAddress
*socket_address
= NULL
;
1874 struct simple_account_data
*sip
= (struct simple_account_data
*) data
;
1876 addresses
= g_resolver_lookup_by_name_finish(G_RESOLVER(sender
),
1879 gchar
*msg
= g_strdup_printf(_("Unable to resolve hostname : %s"),
1882 purple_connection_error(sip
->gc
,
1883 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1887 g_error_free(error
);
1892 inet_address
= G_INET_ADDRESS(addresses
->data
);
1893 socket_address
= g_inet_socket_address_new(inet_address
, sip
->realport
);
1894 g_object_unref(G_OBJECT(inet_address
));
1896 g_socket_address_to_native(socket_address
,
1898 g_socket_address_get_native_size(socket_address
),
1901 g_object_unref(G_OBJECT(socket_address
));
1903 g_resolver_free_addresses(addresses
);
1905 /* create socket for incoming connections */
1906 sip
->listen_data
= purple_network_listen_range(5060, 5160, AF_UNSPEC
, SOCK_DGRAM
, TRUE
,
1907 simple_udp_host_resolved_listen_cb
, sip
);
1908 if (sip
->listen_data
== NULL
) {
1909 purple_connection_error(sip
->gc
,
1910 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1911 _("Unable to create listen socket"));
1917 simple_tcp_connect_listen_cb(int listenfd
, gpointer data
) {
1918 struct simple_account_data
*sip
= (struct simple_account_data
*) data
;
1920 sip
->listen_data
= NULL
;
1922 sip
->listenfd
= listenfd
;
1923 if(sip
->listenfd
== -1) {
1924 purple_connection_error(sip
->gc
,
1925 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1926 _("Unable to create listen socket"));
1930 purple_debug_info("simple", "listenfd: %d\n", sip
->listenfd
);
1931 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
1932 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
1933 simple_newconn_cb
, sip
->gc
);
1934 purple_debug_info("simple", "connecting to %s port %d\n",
1935 sip
->realhostname
, sip
->realport
);
1936 /* open tcp connection to the server */
1937 if (purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
1938 sip
->realport
, login_cb
, sip
->gc
) == NULL
) {
1939 purple_connection_error(sip
->gc
,
1940 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1941 _("Unable to connect"));
1946 srvresolved(GObject
*sender
, GAsyncResult
*result
, gpointer data
) {
1947 GError
*error
= NULL
;
1948 GList
*targets
= NULL
;
1949 struct simple_account_data
*sip
;
1955 targets
= g_resolver_lookup_service_finish(G_RESOLVER(sender
),
1958 purple_debug_info("simple",
1959 "srv lookup failed, continuing with configured settings : %s",
1962 g_error_free(error
);
1964 if(!purple_account_get_bool(sip
->account
, "useproxy", FALSE
)) {
1965 hostname
= g_strdup(sip
->servername
);
1967 hostname
= g_strdup(purple_account_get_string(sip
->account
, "proxy", sip
->servername
));
1969 port
= purple_account_get_int(sip
->account
, "port", 0);
1971 GSrvTarget
*target
= (GSrvTarget
*)targets
->data
;
1973 hostname
= g_strdup(g_srv_target_get_hostname(target
));
1974 port
= g_srv_target_get_port(target
);
1976 g_resolver_free_targets(targets
);
1979 sip
->realhostname
= hostname
;
1980 sip
->realport
= port
;
1983 sip
->realport
= 5060;
1987 /* create socket for incoming connections */
1988 sip
->listen_data
= purple_network_listen_range(5060, 5160, AF_UNSPEC
, SOCK_STREAM
, TRUE
,
1989 simple_tcp_connect_listen_cb
, sip
);
1990 if (sip
->listen_data
== NULL
) {
1991 purple_connection_error(sip
->gc
,
1992 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1993 _("Unable to create listen socket"));
1997 GResolver
*resolver
= g_resolver_get_default();
1999 purple_debug_info("simple", "using udp with server %s and port %d\n", hostname
, port
);
2001 g_resolver_lookup_by_name_async(resolver
,
2004 simple_udp_host_resolved
,
2006 g_object_unref(resolver
);
2010 static void simple_login(PurpleAccount
*account
)
2012 PurpleConnection
*gc
;
2013 struct simple_account_data
*sip
;
2015 const gchar
*hosttoconnect
;
2016 const char *username
= purple_account_get_username(account
);
2017 GResolver
*resolver
;
2019 gc
= purple_account_get_connection(account
);
2021 purple_connection_set_flags(gc
, PURPLE_CONNECTION_FLAG_NO_IMAGES
);
2023 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
2024 purple_connection_error(gc
,
2025 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
2026 _("SIP usernames may not contain whitespaces or @ symbols"));
2030 sip
= g_new0(struct simple_account_data
, 1);
2031 purple_connection_set_protocol_data(gc
, sip
);
2035 sip
->account
= account
;
2036 sip
->registerexpire
= 900;
2037 sip
->udp
= purple_account_get_bool(account
, "udp", FALSE
);
2038 /* TODO: is there a good default grow size? */
2040 sip
->txbuf
= purple_circular_buffer_new(0);
2042 userserver
= g_strsplit(username
, "@", 2);
2043 if (userserver
[1] == NULL
|| userserver
[1][0] == '\0') {
2044 purple_connection_error(gc
,
2045 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
2046 _("SIP connect server not specified"));
2050 purple_connection_set_display_name(gc
, userserver
[0]);
2051 sip
->username
= g_strdup(userserver
[0]);
2052 sip
->servername
= g_strdup(userserver
[1]);
2053 sip
->password
= g_strdup(purple_connection_get_password(gc
));
2054 g_strfreev(userserver
);
2056 sip
->buddies
= g_hash_table_new((GHashFunc
)simple_ht_hash_nick
, (GEqualFunc
)simple_ht_equals_nick
);
2058 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
2060 /* TODO: Set the status correctly. */
2061 sip
->status
= g_strdup("available");
2063 if(!purple_account_get_bool(account
, "useproxy", FALSE
)) {
2064 hosttoconnect
= sip
->servername
;
2066 hosttoconnect
= purple_account_get_string(account
, "proxy", sip
->servername
);
2069 resolver
= g_resolver_get_default();
2070 g_resolver_lookup_service_async(resolver
,
2072 sip
->udp
? "udp" : "tcp",
2077 g_object_unref(resolver
);
2080 static void simple_close(PurpleConnection
*gc
)
2082 struct simple_account_data
*sip
= purple_connection_get_protocol_data(gc
);
2088 if (sip
->registerstatus
== SIMPLE_REGISTER_COMPLETE
)
2090 g_hash_table_foreach(sip
->buddies
,
2091 (GHFunc
)simple_unsubscribe
,
2094 if (purple_account_get_bool(sip
->account
, "dopublish", TRUE
))
2095 send_closed_publish(sip
);
2097 do_register_exp(sip
, 0);
2099 connection_free_all(sip
);
2102 purple_input_remove(sip
->listenpa
);
2103 if (sip
->tx_handler
)
2104 purple_input_remove(sip
->tx_handler
);
2105 if (sip
->resendtimeout
)
2106 g_source_remove(sip
->resendtimeout
);
2107 if (sip
->registertimeout
)
2108 g_source_remove(sip
->registertimeout
);
2110 g_cancellable_cancel(sip
->cancellable
);
2111 g_object_unref(G_OBJECT(sip
->cancellable
));
2113 if (sip
->listen_data
!= NULL
)
2114 purple_network_listen_cancel(sip
->listen_data
);
2118 if (sip
->listenfd
>= 0)
2119 close(sip
->listenfd
);
2121 g_free(sip
->servername
);
2122 g_free(sip
->username
);
2123 g_free(sip
->password
);
2124 g_free(sip
->registrar
.nonce
);
2125 g_free(sip
->registrar
.opaque
);
2126 g_free(sip
->registrar
.target
);
2127 g_free(sip
->registrar
.realm
);
2128 g_free(sip
->registrar
.digest_session_key
);
2129 g_free(sip
->proxy
.nonce
);
2130 g_free(sip
->proxy
.opaque
);
2131 g_free(sip
->proxy
.target
);
2132 g_free(sip
->proxy
.realm
);
2133 g_free(sip
->proxy
.digest_session_key
);
2134 g_free(sip
->status
);
2135 g_hash_table_destroy(sip
->buddies
);
2136 g_free(sip
->regcallid
);
2137 while (sip
->transactions
)
2138 transactions_remove(sip
, sip
->transactions
->data
);
2139 g_free(sip
->publish_etag
);
2141 g_object_unref(G_OBJECT(sip
->txbuf
));
2142 g_free(sip
->realhostname
);
2145 purple_connection_set_protocol_data(gc
, NULL
);
2149 simple_protocol_init(PurpleProtocol
*protocol
)
2151 PurpleAccountUserSplit
*split
;
2152 PurpleAccountOption
*option
;
2154 protocol
->id
= "prpl-simple";
2155 protocol
->name
= "SIMPLE";
2157 split
= purple_account_user_split_new(_("Server"), "", '@');
2158 protocol
->user_splits
= g_list_append(protocol
->user_splits
, split
);
2160 option
= purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "dopublish", TRUE
);
2161 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
2163 option
= purple_account_option_int_new(_("Connect port"), "port", 0);
2164 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
2166 option
= purple_account_option_bool_new(_("Use UDP"), "udp", FALSE
);
2167 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
2168 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
2169 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
2170 option
= purple_account_option_string_new(_("Proxy"), "proxy", "");
2171 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
2172 option
= purple_account_option_string_new(_("Auth User"), "authuser", "");
2173 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
2174 option
= purple_account_option_string_new(_("Auth Domain"), "authdomain", "");
2175 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
2179 simple_protocol_class_init(PurpleProtocolClass
*klass
)
2181 klass
->login
= simple_login
;
2182 klass
->close
= simple_close
;
2183 klass
->status_types
= simple_status_types
;
2184 klass
->list_icon
= simple_list_icon
;
2188 simple_protocol_server_iface_init(PurpleProtocolServerInterface
*server_iface
)
2190 server_iface
->set_status
= simple_set_status
;
2191 server_iface
->add_buddy
= simple_add_buddy
;
2192 server_iface
->remove_buddy
= simple_remove_buddy
;
2193 server_iface
->keepalive
= simple_keep_alive
;
2194 server_iface
->send_raw
= simple_send_raw
;
2198 simple_protocol_im_iface_init(PurpleProtocolIMInterface
*im_iface
)
2200 im_iface
->send
= simple_im_send
;
2201 im_iface
->send_typing
= simple_typing
;
2204 PURPLE_DEFINE_TYPE_EXTENDED(
2205 SIMPLEProtocol
, simple_protocol
, PURPLE_TYPE_PROTOCOL
, 0,
2207 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER
,
2208 simple_protocol_server_iface_init
)
2210 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM
,
2211 simple_protocol_im_iface_init
)
2214 static PurplePluginInfo
*
2215 plugin_query(GError
**error
)
2217 const gchar
* const authors
[] = {
2218 "Thomas Butter <butter@uni-mannheim.de>",
2222 return purple_plugin_info_new(
2223 "id", "prpl-simple",
2224 "name", "SIMPLE Protocol",
2225 "version", DISPLAY_VERSION
,
2226 "category", N_("Protocol"),
2227 "summary", N_("SIP/SIMPLE Protocol Plugin"),
2228 "description", N_("The SIP/SIMPLE Protocol Plugin"),
2230 "website", PURPLE_WEBSITE
,
2231 "abi-version", PURPLE_ABI_VERSION
,
2232 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL
|
2233 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD
,
2239 plugin_load(PurplePlugin
*plugin
, GError
**error
)
2241 simple_protocol_register_type(plugin
);
2243 my_protocol
= purple_protocols_add(SIMPLE_TYPE_PROTOCOL
, error
);
2247 purple_signal_connect(purple_get_core(), "uri-handler", plugin
,
2248 PURPLE_CALLBACK(simple_uri_handler
), NULL
);
2254 plugin_unload(PurplePlugin
*plugin
, GError
**error
)
2256 purple_signal_disconnect(purple_get_core(), "uri-handler", plugin
,
2257 PURPLE_CALLBACK(simple_uri_handler
));
2259 if (!purple_protocols_remove(my_protocol
, error
))
2265 PURPLE_PLUGIN_INIT(simple
, plugin_query
, plugin_load
, plugin_unload
);