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"
31 #include "conversation.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
) {
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
));
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
);
96 purple_debug_info("simple", "notifying %s\n", ((struct simple_watcher
*)tmp
->data
)->name
);
97 send_notify(sip
, tmp
->data
);
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
))
110 sip
= account
->gc
->proto_data
;
115 if (primitive
== PURPLE_STATUS_AVAILABLE
)
116 sip
->status
= g_strdup("available");
118 sip
->status
= g_strdup("busy");
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
;
129 if(ret
->fd
== fd
) return ret
;
135 static struct simple_watcher
*watcher_find(struct simple_account_data
*sip
,
137 struct simple_watcher
*watcher
;
138 GSList
*entry
= sip
->watcher
;
140 watcher
= entry
->data
;
141 if(!strcmp(name
, watcher
->name
)) return watcher
;
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
);
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
);
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);
173 sip
->openconns
= g_slist_append(sip
->openconns
, 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
);
185 static void connection_free_all(struct simple_account_data
*sip
) {
186 struct sip_connection
*ret
= NULL
;
187 GSList
*entry
= sip
->openconns
;
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
);
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
);
211 purple_debug_info("simple", "buddy %s already in internal list\n", name
);
215 static void simple_get_buddies(PurpleConnection
*gc
) {
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
);
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
);
241 static GList
*simple_status_types(PurpleAccount
*acc
) {
242 PurpleStatusType
*type
;
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
),
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
);
258 static gchar
*auth_header(struct simple_account_data
*sip
,
259 struct sip_auth
*auth
, const gchar
*method
, const gchar
*target
) {
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
);
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
);
292 tmp
= g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth
->realm
, auth
->target
);
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
);
307 static char *parse_attribute(const char *attrname
, const char *source
) {
308 const char *tmp
, *tmp2
;
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] == ' ')
318 if(!strncmp(source
, attrname
, len
)) {
320 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
322 retval
= g_strndup(tmp
, tmp2
- tmp
);
324 retval
= g_strdup(tmp
);
330 static void fill_auth(struct simple_account_data
*sip
, const gchar
*hdr
, struct sip_auth
*auth
) {
332 const char *authuser
;
336 authuser
= purple_account_get_string(sip
->account
, "authuser", sip
->username
);
338 if(!authuser
|| strlen(authuser
) < 1) {
339 authuser
= sip
->username
;
343 purple_debug_error("simple", "fill_auth: hdr==NULL\n");
347 if(!g_ascii_strncasecmp(hdr
, "NTLM", 4)) {
348 purple_debug_info("simple", "found NTLM\n");
350 parts
= g_strsplit(hdr
+5, "\",", 0);
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);
358 if((tmp
= parse_attribute("targetname=\"",
362 else if((tmp
= parse_attribute("realm=\"",
366 else if((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
373 if(!strstr(hdr
, "gssapi-data")) {
380 } else if(!g_ascii_strncasecmp(hdr
, "DIGEST", 6)) {
382 purple_debug_info("simple", "found DIGEST\n");
385 parts
= g_strsplit(hdr
+7, ",", 0);
387 if((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
390 else if((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
396 purple_debug(PURPLE_DEBUG_MISC
, "simple", "nonce: %s realm: %s\n",
397 auth
->nonce
? auth
->nonce
: "(null)",
398 auth
->realm
? auth
->realm
: "(null)");
401 auth
->digest_session_key
= purple_cipher_http_digest_calculate_session_key(
402 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
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
;
419 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
422 purple_input_remove(sip
->tx_handler
);
427 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
429 if(written
< 0 && errno
== EAGAIN
)
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"),
435 purple_connection_error_reason(gc
,
436 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
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
;
452 gchar
*tmp
= g_strdup_printf(_("Unable to connect: %s"),
454 purple_connection_error_reason(gc
,
455 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
460 sip
= gc
->proto_data
;
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
);
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");
510 if(sip
->tx_handler
) {
514 ret
= write(sip
->fd
, buf
, writelen
);
516 if (ret
< 0 && errno
== EAGAIN
)
518 else if(ret
<= 0) { /* XXX: When does this happen legitimately? */
523 if (ret
< writelen
) {
525 sip
->tx_handler
= purple_input_add(sip
->fd
,
526 PURPLE_INPUT_WRITE
, simple_canwrite_cb
,
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
,
540 static int simple_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
542 sendout_pkt(gc
, buf
);
546 static void sendout_sipmsg(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
547 GSList
*tmp
= msg
->headers
;
550 GString
*outstr
= g_string_new("");
551 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
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
;
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");
576 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
577 sipmsg_add_header(msg
, "Content-Length", len
);
580 sipmsg_add_header(msg
, "Content-Length", "0");
581 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
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
);
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");
615 while(transactions
) {
616 trans
= transactions
->data
;
617 if(!strcmp(trans
->cseq
, cseq
)) {
620 transactions
= transactions
->next
;
623 purple_debug(PURPLE_DEBUG_MISC
, "simple", "Received message contains no CSeq header.\n");
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();
635 const char *addh
= "";
636 gchar
*branch
= genbranch();
640 if(!strcmp(method
, "REGISTER")) {
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
);
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
);
658 purple_debug(PURPLE_DEBUG_MISC
, "simple", "header %s", auth
);
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"
669 "Max-Forwards: 10\r\n"
671 "User-Agent: Purple/" VERSION
"\r\n"
674 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
677 sip
->udp
? "UDP" : "TCP",
678 purple_network_get_my_ip(-1),
683 dialog
? dialog
->ourtag
: tag
,
685 dialog
? ";tag=" : "",
686 dialog
? dialog
->theirtag
: "",
700 /* add to ongoing transactions */
702 transactions_add_buf(sip
, buf
, tc
);
704 sendout_pkt(gc
, 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),
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
);
727 sip
->registerstatus
= SIMPLE_REGISTER_SENT
;
729 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
730 process_register_response
);
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
) {
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 <...> */
752 tmp
= strchr(tmp2
, '>');
754 from
= g_strndup(tmp2
, tmp
- tmp2
);
756 purple_debug_info("simple", "found < without > in From\n");
760 tmp
= strchr(tmp2
, ';');
762 from
= g_strndup(tmp2
, tmp
- tmp2
);
764 from
= g_strdup(tmp2
);
767 purple_debug_info("simple", "got %s\n", 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
) {
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
)) &&
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",
801 purple_debug_info("simple", "theirtag: %s\n",
803 purple_debug_info("simple", "callid: %s\n",
811 purple_debug_info("simple", "cannot create dialog!\n");
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
);
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(
830 "Accept: application/pidf+xml, application/xpidf+xml\r\n"
831 "Event: presence\r\n",
834 if(strncmp(buddy
->name
, "sip:", 4))
835 to
= g_strdup_printf("sip:%s", buddy
->name
);
837 to
= g_strdup(buddy
->name
);
839 tmp
= get_contact(sip
);
840 contact
= g_strdup_printf("%sContact: %s\r\n", tmp2
, tmp
);
844 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, contact
,"",buddy
->dialog
,
845 (expiration
> 0) ? process_subscribe_response
: NULL
);
850 /* resubscribe before subscription expires */
851 /* add some jitter */
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
) {
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
) {
872 xmlnode
*item
, *group
, *isc
;
873 const char *name_group
;
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
);
893 g
= purple_group_new(name_group
);
897 g
= purple_find_group("Buddies");
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
;
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
);
915 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
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
);
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";
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
);
941 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, contact
, "", NULL
, simple_add_lcs_contacts
);
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
);
961 struct transaction
*trans
= tmp
->data
;
963 purple_debug_info("simple", "have open transaction age: %lu\n", currtime
- trans
->time
);
964 if((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
967 if((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
969 sendout_sipmsg(sip
, trans
->msg
);
976 static gboolean
subscribe_timeout(struct simple_account_data
*sip
) {
978 time_t curtime
= time(NULL
);
979 /* register again if first registration expires */
980 if(sip
->reregister
< curtime
) {
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 */
999 struct simple_watcher
*watcher
= tmp
->data
;
1000 if(watcher
->expire
< curtime
) {
1001 watcher_remove(sip
, watcher
->name
);
1004 if(tmp
) tmp
= tmp
->next
;
1010 static void simple_send_message(struct simple_account_data
*sip
, const char *to
, const char *msg
, const char *type
) {
1013 if(strncmp(to
, "sip:", 4))
1014 fullto
= g_strdup_printf("sip:%s", to
);
1016 fullto
= g_strdup(to
);
1019 hdr
= g_strdup_printf("Content-Type: %s\r\n", type
);
1021 hdr
= g_strdup("Content-Type: text/plain\r\n");
1023 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msg
, NULL
, NULL
);
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
);
1038 static void process_incoming_message(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
1040 const gchar
*contenttype
;
1041 gboolean found
= FALSE
;
1043 from
= parse_from(sipmsg_find_header(msg
, "From"));
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
);
1055 else if(!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
1056 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1061 purple_debug_info("simple", "process_incoming_message: can not parse iscomposing\n");
1066 state
= xmlnode_get_child(isc
, "state");
1069 purple_debug_info("simple", "process_incoming_message: no state found\n");
1075 statedata
= xmlnode_get_data(state
);
1077 if(strstr(statedata
, "active"))
1078 serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
1080 serv_got_typing_stopped(sip
->gc
, from
);
1085 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
1089 purple_debug_info("simple", "got unknown mime-type\n");
1090 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
1096 gboolean
process_register_response(struct simple_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
) {
1098 purple_debug(PURPLE_DEBUG_MISC
, "simple", "in process register response response: %d\n", msg
->response
);
1099 switch (msg
->response
) {
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
);
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"));
1130 tmp
= sipmsg_find_header(msg
, "WWW-Authenticate");
1131 fill_auth(sip
, tmp
, &sip
->registrar
);
1132 sip
->registerstatus
= SIMPLE_REGISTER_RETRY
;
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"));
1145 sip
->registerstatus
= SIMPLE_REGISTER_RETRY
;
1153 static gboolean
dialog_match(struct sip_dialog
*dialog
, struct sipmsg
*msg
)
1155 const gchar
*fromhdr
;
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
)
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
))
1183 static void process_incoming_notify(struct simple_account_data
*sip
, struct sipmsg
*msg
) {
1185 const gchar
*fromhdr
;
1186 gchar
*basicstatus_data
;
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
);
1197 b
= g_hash_table_lookup(sip
->buddies
, from
);
1201 purple_debug_info("simple", "Could not find the buddy.\n");
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");
1215 pidf
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1218 purple_debug_info("simple", "process_incoming_notify: no parseable pidf\n");
1219 sshdr
= sipmsg_find_header(msg
, "Subscription-State");
1223 gchar
**ssparts
= g_strsplit(sshdr
, ":", 0);
1226 g_strchug(ssparts
[i
]);
1227 if (purple_str_has_prefix(ssparts
[i
], "terminated"))
1229 purple_debug_info("simple", "Subscription expired!");
1232 g_free(b
->dialog
->ourtag
);
1233 g_free(b
->dialog
->theirtag
);
1234 g_free(b
->dialog
->callid
);
1239 purple_prpl_got_user_status(sip
->account
, from
, "offline", NULL
);
1244 g_strfreev(ssparts
);
1246 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
1251 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
1252 if ((status
= xmlnode_get_child(tuple
, "status")))
1253 basicstatus
= xmlnode_get_child(status
, "basic");
1256 purple_debug_info("simple", "process_incoming_notify: no basic found\n");
1262 basicstatus_data
= xmlnode_get_data(basicstatus
);
1264 if(!basicstatus_data
) {
1265 purple_debug_info("simple", "process_incoming_notify: no basic data found\n");
1271 if(strstr(basicstatus_data
, "open"))
1276 purple_prpl_got_user_status(sip
->account
, from
, "available", NULL
);
1278 purple_prpl_got_user_status(sip
->account
, from
, "offline", NULL
);
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"
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");
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");
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.
1318 static gchar
*find_tag(const gchar
*hdr
) {
1319 const gchar
*tmp
= strstr(hdr
, ";tag="), *tmp2
;
1321 if(!tmp
) return NULL
;
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"
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"
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"
1357 "<basic>%s</basic>\n"
1364 (open
== TRUE
) ? "open" : "closed",
1365 (open
== TRUE
) ? sip
->status
: "");
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
);
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");
1387 /* we must store the etag somewhere. */
1388 g_free(sip
->publish_etag
);
1389 sip
->publish_etag
= g_strdup(etag
);
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;
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" : "",
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;*/
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");
1446 struct simple_watcher
*watcher
= watcher_find(sip
, from
);
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
);
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
))
1467 if(!g_ascii_strcasecmp("application/xpidf+xml", tmp
))
1472 while(*tmp
== ' ') tmp
++;
1476 if(!foundpidf
&& foundxpidf
) needsxpidf
= TRUE
;
1478 watcher
= watcher_create(sip
, from
, callid
, ourtag
, theirtag
, needsxpidf
);
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
);
1487 watcher
->expire
= time(NULL
) + strtol(expire
, NULL
, 10);
1489 watcher
->expire
= time(NULL
) + 600;
1490 sipmsg_remove_header(msg
, "Contact");
1491 tmp
= get_contact(sip
);
1492 sipmsg_add_header(msg
, "Contact", 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
);
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
);
1509 } else if(!strcmp(msg
->method
, "NOTIFY")) {
1510 process_incoming_notify(sip
, msg
);
1512 } else if(!strcmp(msg
->method
, "SUBSCRIBE")) {
1513 process_incoming_subscribe(sip
, msg
);
1516 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
1518 } else { /* response */
1519 struct transaction
*trans
= transactions_find(sip
, msg
);
1521 if(msg
->response
== 407) {
1522 gchar
*resend
, *auth
;
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
);
1536 resend
= sipmsg_to_string(trans
->msg
);
1537 /* resend request */
1538 sendout_pkt(sip
->gc
, resend
);
1541 if(msg
->response
== 100) {
1542 /* ignore provisional response */
1543 purple_debug_info("simple", "got trying response\n");
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
++;
1560 sip
->registrar
.retries
= 0;
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
;
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
);
1580 resend
= sipmsg_to_string(trans
->msg
);
1581 /* resend request */
1582 sendout_pkt(sip
->gc
, resend
);
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
);
1600 purple_debug(PURPLE_DEBUG_MISC
, "simple", "received response to unknown transaction");
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
)
1616 /* according to the RFC remove CRLF at the beginning */
1617 while(*cur
== '\r' || *cur
== '\n') {
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
);
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
);
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
);
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';
1647 cur
+= msg
->bodylen
;
1648 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
1649 conn
->inbufused
= strlen(conn
->inbuf
);
1654 purple_debug(PURPLE_DEBUG_MISC
, "simple", "in process response response: %d\n", msg
->response
);
1655 process_input_message(sip
, msg
);
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
;
1666 time_t currtime
= time(NULL
);
1668 static char buffer
[65536];
1669 if((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 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
;
1682 struct sip_connection
*conn
= connection_find(sip
, source
);
1684 purple_debug_error("simple", "Connection not found!\n");
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
)
1698 purple_debug_info("simple", "simple_input_cb: read error\n");
1699 connection_remove(sip
, source
);
1700 if(sip
->fd
== source
) sip
->fd
= -1;
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
;
1717 newfd
= accept(source
, NULL
, NULL
);
1719 flags
= fcntl(newfd
, F_GETFL
);
1720 fcntl(newfd
, F_SETFL
, flags
| O_NONBLOCK
);
1722 fcntl(newfd
, F_SETFD
, FD_CLOEXEC
);
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
;
1736 gchar
*tmp
= g_strdup_printf(_("Unable to connect: %s"),
1738 purple_connection_error_reason(gc
,
1739 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
1744 sip
= gc
->proto_data
;
1747 conn
= connection_create(sip
, source
);
1749 sip
->registertimeout
= purple_timeout_add((rand()%100)+10*1000, (GSourceFunc
)subscribe_timeout
, 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
);
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"));
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?
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
);
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
;
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"));
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
);
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"));
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"));
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
;
1866 sip
->srv_query_data
= NULL
;
1868 port
= purple_account_get_int(sip
->account
, "port", 0);
1870 /* find the host to connect to */
1872 hostname
= g_strdup(resp
->hostname
);
1877 if(!purple_account_get_bool(sip
->account
, "useproxy", FALSE
)) {
1878 hostname
= g_strdup(sip
->servername
);
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;
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"));
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
;
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"));
1928 gc
->proto_data
= sip
= g_new0(struct simple_account_data
, 1);
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? */
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"));
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
;
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
;
1976 if (sip
->registerstatus
== SIMPLE_REGISTER_COMPLETE
)
1978 g_hash_table_foreach(sip
->buddies
,
1979 (GHFunc
)simple_unsubscribe
,
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
);
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
);
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
);
2026 purple_circ_buffer_destroy(sip
->txbuf
);
2027 g_free(sip
->realhostname
);
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 */
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 */
2128 NULL
, /**< unload */
2129 NULL
, /**< destroy */
2131 NULL
, /**< ui_info */
2132 &prpl_info
, /**< extra_info */
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
);