source: protocols/jabber/si.c @ cb1b973

Last change on this file since cb1b973 was 5ebff60, checked in by dequis <dx@…>, at 2015-02-20T22:50:54Z

Reindent everything to K&R style with tabs

Used uncrustify, with the configuration file in ./doc/uncrustify.cfg

Commit author set to "Indent <please@…>" so that it's easier to
skip while doing git blame.

  • Property mode set to 100644
File size: 16.7 KB
RevLine 
[2c2df7d]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - SI packets                                               *
5*                                                                           *
6*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
25#include "sha1.h"
26
[5ebff60]27void jabber_si_answer_request(file_transfer_t *ft);
28int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf);
[2c2df7d]29
[2ff2076]30/* file_transfer free() callback */
[5ebff60]31void jabber_si_free_transfer(file_transfer_t *ft)
[2c2df7d]32{
33        struct jabber_transfer *tf = ft->data;
34        struct jabber_data *jd = tf->ic->proto_data;
35
[5ebff60]36        if (tf->watch_in) {
37                b_event_remove(tf->watch_in);
[8256ad5]38                tf->watch_in = 0;
39        }
[2c2df7d]40
[5ebff60]41        jd->filetransfers = g_slist_remove(jd->filetransfers, tf);
[2c2df7d]42
[5ebff60]43        if (tf->fd != -1) {
44                closesocket(tf->fd);
[8a90001]45                tf->fd = -1;
[2c2df7d]46        }
47
[5ebff60]48        if (tf->disco_timeout) {
49                b_event_remove(tf->disco_timeout);
50        }
51
52        g_free(tf->ini_jid);
53        g_free(tf->tgt_jid);
54        g_free(tf->iq_id);
55        g_free(tf->sid);
56        g_free(tf);
[2c2df7d]57}
58
[2ff2076]59/* file_transfer canceled() callback */
[5ebff60]60void jabber_si_canceled(file_transfer_t *ft, char *reason)
[2c2df7d]61{
62        struct jabber_transfer *tf = ft->data;
63        struct xt_node *reply, *iqnode;
64
[5ebff60]65        if (tf->accepted) {
[2c2df7d]66                return;
[5ebff60]67        }
68
69        iqnode = jabber_make_packet("iq", "error", tf->ini_jid, NULL);
70        xt_add_attr(iqnode, "id", tf->iq_id);
71        reply = jabber_make_error_packet(iqnode, "forbidden", "cancel", "403");
72        xt_free_node(iqnode);
73
74        if (!jabber_write_packet(tf->ic, reply)) {
75                imcb_log(tf->ic, "WARNING: Error generating reply to file transfer request");
76        }
77        xt_free_node(reply);
[2c2df7d]78
79}
80
[5ebff60]81int jabber_si_check_features(struct jabber_transfer *tf, GSList *features)
82{
[b5cfc2b]83        int foundft = FALSE, foundbt = FALSE, foundsi = FALSE;
84
[5ebff60]85        while (features) {
86                if (!strcmp(features->data, XMLNS_FILETRANSFER)) {
[b5cfc2b]87                        foundft = TRUE;
[5ebff60]88                }
89                if (!strcmp(features->data, XMLNS_BYTESTREAMS)) {
[b5cfc2b]90                        foundbt = TRUE;
[5ebff60]91                }
92                if (!strcmp(features->data, XMLNS_SI)) {
[b5cfc2b]93                        foundsi = TRUE;
[5ebff60]94                }
[b5cfc2b]95
96                features = g_slist_next(features);
97        }
98
[5ebff60]99        if (!foundft) {
100                imcb_file_canceled(tf->ic, tf->ft, "Buddy's client doesn't feature file transfers");
101        } else if (!foundbt) {
102                imcb_file_canceled(tf->ic, tf->ft, "Buddy's client doesn't feature byte streams (required)");
103        } else if (!foundsi) {
104                imcb_file_canceled(tf->ic, tf->ft, "Buddy's client doesn't feature stream initiation (required)");
105        }
106
[b5cfc2b]107        return foundft && foundbt && foundsi;
108}
109
[5ebff60]110void jabber_si_transfer_start(struct jabber_transfer *tf)
111{
[b5cfc2b]112
[5ebff60]113        if (!jabber_si_check_features(tf, tf->bud->features)) {
[b5cfc2b]114                return;
[5ebff60]115        }
116
[b5cfc2b]117        /* send the request to our buddy */
[5ebff60]118        jabber_si_send_request(tf->ic, tf->bud->full_jid, tf);
[b5cfc2b]119
120        /* and start the receive logic */
[5ebff60]121        imcb_file_recv_start(tf->ic, tf->ft);
[b5cfc2b]122
123}
124
[5ebff60]125gboolean jabber_si_waitfor_disco(gpointer data, gint fd, b_input_condition cond)
[b5cfc2b]126{
127        struct jabber_transfer *tf = data;
128        struct jabber_data *jd = tf->ic->proto_data;
129
130        tf->disco_timeout_fired++;
131
[5ebff60]132        if (tf->bud->features && jd->have_streamhosts == 1) {
[b5cfc2b]133                tf->disco_timeout = 0;
[5ebff60]134                jabber_si_transfer_start(tf);
[b5cfc2b]135                return FALSE;
136        }
137
138        /* 8 seconds should be enough for server and buddy to respond */
[5ebff60]139        if (tf->disco_timeout_fired < 16) {
[b5cfc2b]140                return TRUE;
[5ebff60]141        }
142
143        if (!tf->bud->features && jd->have_streamhosts != 1) {
144                imcb_log(tf->ic, "Couldn't get buddy's features nor discover all services of the server");
145        } else if (!tf->bud->features) {
146                imcb_log(tf->ic, "Couldn't get buddy's features");
147        } else {
148                imcb_log(tf->ic, "Couldn't discover some of the server's services");
149        }
150
[b5cfc2b]151        tf->disco_timeout = 0;
[5ebff60]152        jabber_si_transfer_start(tf);
[b5cfc2b]153        return FALSE;
154}
155
[5ebff60]156void jabber_si_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *who)
[2ff2076]157{
158        struct jabber_transfer *tf;
159        struct jabber_data *jd = ic->proto_data;
[b5cfc2b]160        struct jabber_buddy *bud;
161        char *server = jd->server, *s;
162
[5ebff60]163        if ((s = strchr(who, '=')) && jabber_chat_by_jid(ic, s + 1)) {
164                bud = jabber_buddy_by_ext_jid(ic, who, 0);
165        } else {
166                bud = jabber_buddy_by_jid(ic, who, 0);
167        }
[2ff2076]168
[5ebff60]169        if (bud == NULL) {
170                imcb_file_canceled(ic, ft, "Couldn't find buddy (BUG?)");
[b5cfc2b]171                return;
172        }
[2ff2076]173
[5ebff60]174        imcb_log(ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who);
175
176        tf = g_new0(struct jabber_transfer, 1);
[2ff2076]177
178        tf->ic = ic;
179        tf->ft = ft;
[8a90001]180        tf->fd = -1;
[2ff2076]181        tf->ft->data = tf;
182        tf->ft->free = jabber_si_free_transfer;
[b5cfc2b]183        tf->bud = bud;
[2ff2076]184        ft->write = jabber_bs_send_write;
185
[5ebff60]186        jd->filetransfers = g_slist_prepend(jd->filetransfers, tf);
[2ff2076]187
[b5cfc2b]188        /* query buddy's features and server's streaming proxies if neccessary */
[dc0ba9c]189
[5ebff60]190        if (!tf->bud->features) {
191                jabber_iq_query_features(ic, bud->full_jid);
192        }
[dc0ba9c]193
[8a90001]194        /* If <auto> is not set don't check for proxies */
[5ebff60]195        if ((jd->have_streamhosts != 1) && (jd->streamhosts == NULL) &&
196            (strstr(set_getstr(&ic->acc->set, "proxy"), "<auto>") != NULL)) {
[b5cfc2b]197                jd->have_streamhosts = 0;
[5ebff60]198                jabber_iq_query_server(ic, server, XMLNS_DISCO_ITEMS);
199        } else if (jd->streamhosts != NULL) {
[8a90001]200                jd->have_streamhosts = 1;
[5ebff60]201        }
[2ff2076]202
[5ebff60]203        /* if we had to do a query, wait for the result.
[b5cfc2b]204         * Otherwise fire away. */
[5ebff60]205        if (!tf->bud->features || jd->have_streamhosts != 1) {
206                tf->disco_timeout = b_timeout_add(500, jabber_si_waitfor_disco, tf);
207        } else {
208                jabber_si_transfer_start(tf);
209        }
[2ff2076]210}
211
[2c2df7d]212/*
213 * First function that gets called when a file transfer request comes in.
214 * A lot to parse.
215 *
216 * We choose a stream type from the options given by the initiator.
217 * Then we wait for imcb to call the accept or cancel callbacks.
218 */
[5ebff60]219int jabber_si_handle_request(struct im_connection *ic, struct xt_node *node, struct xt_node *sinode)
[2c2df7d]220{
221        struct xt_node *c, *d, *reply;
[78d254f1]222        char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s;
[2c2df7d]223        struct jabber_buddy *bud;
224        int requestok = FALSE;
[aed152f]225        char *name, *cmp;
[2c2df7d]226        size_t size;
227        struct jabber_transfer *tf;
228        struct jabber_data *jd = ic->proto_data;
229        file_transfer_t *ft;
[5ebff60]230
[2c2df7d]231        /* All this means we expect something like this: ( I think )
232         * <iq from=... to=... id=...>
[5ebff60]233         *      <si id=id xmlns=si profile=ft>
234         *              <file xmlns=ft/>
235         *              <feature xmlns=feature>
236         *                      <x xmlns=xdata type=submit>
237         *                              <field var=stream-method>
[2c2df7d]238         *
239         */
[5ebff60]240        if (!(ini_jid          = xt_find_attr(node, "from")) ||
241            !(tgt_jid          = xt_find_attr(node, "to")) ||
242            !(iq_id            = xt_find_attr(node, "id")) ||
243            !(sid              = xt_find_attr(sinode, "id")) ||
244            !(cmp              = xt_find_attr(sinode, "profile")) ||
245            !(0               == strcmp(cmp, XMLNS_FILETRANSFER)) ||
246            !(d                = xt_find_node(sinode->children, "file")) ||
247            !(cmp = xt_find_attr(d, "xmlns")) ||
248            !(0               == strcmp(cmp, XMLNS_FILETRANSFER)) ||
249            !(name             = xt_find_attr(d, "name")) ||
250            !(size_s           = xt_find_attr(d, "size")) ||
251            !(1               == sscanf(size_s, "%zd", &size)) ||
252            !(d                = xt_find_node(sinode->children, "feature")) ||
253            !(cmp              = xt_find_attr(d, "xmlns")) ||
254            !(0               == strcmp(cmp, XMLNS_FEATURE)) ||
255            !(d                = xt_find_node(d->children, "x")) ||
256            !(cmp              = xt_find_attr(d, "xmlns")) ||
257            !(0               == strcmp(cmp, XMLNS_XDATA)) ||
258            !(cmp              = xt_find_attr(d, "type")) ||
259            !(0               == strcmp(cmp, "form")) ||
260            !(d                = xt_find_node(d->children, "field")) ||
261            !(cmp              = xt_find_attr(d, "var")) ||
262            !(0               == strcmp(cmp, "stream-method"))) {
263                imcb_log(ic, "WARNING: Received incomplete Stream Initiation request");
264        } else {
[2c2df7d]265                /* Check if we support one of the options */
266
267                c = d->children;
[5ebff60]268                while ((c = xt_find_node(c, "option"))) {
269                        if ((d = xt_find_node(c->children, "value")) &&
270                            (d->text != NULL) &&
271                            (strcmp(d->text, XMLNS_BYTESTREAMS) == 0)) {
[2c2df7d]272                                requestok = TRUE;
273                                break;
[5ebff60]274                        } else {
[1bb1e01]275                                c = c->next;
276                        }
[5ebff60]277                }
[2ff2076]278
[5ebff60]279                if (!requestok) {
280                        imcb_log(ic, "WARNING: Unsupported file transfer request from %s", ini_jid);
281                }
[2c2df7d]282        }
[5ebff60]283
284        if (requestok) {
[2c2df7d]285                /* Figure out who the transfer should come frome... */
286
[b8a491d]287                ext_jid = ini_jid;
[5ebff60]288                if ((s = strchr(ini_jid, '/'))) {
289                        if ((bud = jabber_buddy_by_jid(ic, ini_jid, GET_BUDDY_EXACT))) {
290                                bud->last_msg = time(NULL);
[2c2df7d]291                                ext_jid = bud->ext_jid ? : bud->bare_jid;
[5ebff60]292                        } else {
[2c2df7d]293                                *s = 0; /* We need to generate a bare JID now. */
[5ebff60]294                        }
[2c2df7d]295                }
296
[5ebff60]297                if (!(ft = imcb_file_send_start(ic, ext_jid, name, size))) {
298                        imcb_log(ic, "WARNING: Error handling transfer request from %s", ini_jid);
[2c2df7d]299                        requestok = FALSE;
300                }
301
[5ebff60]302                if (s) {
[92d3044]303                        *s = '/';
[5ebff60]304                }
[2ff2076]305        }
[5ebff60]306
307        if (!requestok) {
308                reply = jabber_make_error_packet(node, "item-not-found", "cancel", NULL);
309                if (!jabber_write_packet(ic, reply)) {
310                        imcb_log(ic, "WARNING: Error generating reply to file transfer request");
311                }
312                xt_free_node(reply);
[2c2df7d]313                return XT_HANDLED;
314        }
315
316        /* Request is fine. */
317
[5ebff60]318        tf = g_new0(struct jabber_transfer, 1);
[2c2df7d]319
[5ebff60]320        tf->ini_jid = g_strdup(ini_jid);
321        tf->tgt_jid = g_strdup(tgt_jid);
322        tf->iq_id = g_strdup(iq_id);
323        tf->sid = g_strdup(sid);
[2c2df7d]324        tf->ic = ic;
325        tf->ft = ft;
[8a90001]326        tf->fd = -1;
[2c2df7d]327        tf->ft->data = tf;
328        tf->ft->accept = jabber_si_answer_request;
329        tf->ft->free = jabber_si_free_transfer;
330        tf->ft->canceled = jabber_si_canceled;
331
[5ebff60]332        jd->filetransfers = g_slist_prepend(jd->filetransfers, tf);
[2c2df7d]333
334        return XT_HANDLED;
335}
336
337/*
[dce3903]338 * imc called the accept callback which probably means that the user accepted this file transfer.
[2c2df7d]339 * We send our response to the initiator.
340 * In the next step, the initiator will send us a request for the given stream type.
341 * (currently that can only be a SOCKS5 bytestream)
342 */
[5ebff60]343void jabber_si_answer_request(file_transfer_t *ft)
344{
[2c2df7d]345        struct jabber_transfer *tf = ft->data;
346        struct xt_node *node, *sinode, *reply;
347
348        /* generate response, start with the SI tag */
[5ebff60]349        sinode = xt_new_node("si", NULL, NULL);
350        xt_add_attr(sinode, "xmlns", XMLNS_SI);
351        xt_add_attr(sinode, "profile", XMLNS_FILETRANSFER);
352        xt_add_attr(sinode, "id", tf->sid);
[2c2df7d]353
354        /* now the file tag */
[5ebff60]355        node = xt_new_node("file", NULL, NULL);
356        xt_add_attr(node, "xmlns", XMLNS_FILETRANSFER);
[2c2df7d]357
[5ebff60]358        xt_add_child(sinode, node);
[2c2df7d]359
360        /* and finally the feature tag */
[5ebff60]361        node = xt_new_node("field", NULL, NULL);
362        xt_add_attr(node, "var", "stream-method");
363        xt_add_attr(node, "type", "list-single");
[2c2df7d]364
365        /* Currently all we can do. One could also implement in-band (IBB) */
[5ebff60]366        xt_add_child(node, xt_new_node("value", XMLNS_BYTESTREAMS, NULL));
[2c2df7d]367
[5ebff60]368        node = xt_new_node("x", NULL, node);
369        xt_add_attr(node, "xmlns", XMLNS_XDATA);
370        xt_add_attr(node, "type", "submit");
[2c2df7d]371
[5ebff60]372        node = xt_new_node("feature", NULL, node);
373        xt_add_attr(node, "xmlns", XMLNS_FEATURE);
[2c2df7d]374
[5ebff60]375        xt_add_child(sinode, node);
[2c2df7d]376
[5ebff60]377        reply = jabber_make_packet("iq", "result", tf->ini_jid, sinode);
378        xt_add_attr(reply, "id", tf->iq_id);
379
380        if (!jabber_write_packet(tf->ic, reply)) {
381                imcb_log(tf->ic, "WARNING: Error generating reply to file transfer request");
382        } else {
[2c2df7d]383                tf->accepted = TRUE;
[5ebff60]384        }
385        xt_free_node(reply);
[2c2df7d]386}
[2ff2076]387
[5ebff60]388static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[2ff2076]389{
390        struct xt_node *c, *d;
[bd599b9]391        char *ini_jid = NULL, *tgt_jid, *iq_id, *cmp;
[2ff2076]392        GSList *tflist;
[5ebff60]393        struct jabber_transfer *tf = NULL;
[2ff2076]394        struct jabber_data *jd = ic->proto_data;
395
[5ebff60]396        if (!(tgt_jid = xt_find_attr(node, "from")) ||
397            !(ini_jid = xt_find_attr(node, "to"))) {
398                imcb_log(ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid);
[2ff2076]399                return XT_HANDLED;
400        }
[5ebff60]401
[2ff2076]402        /* All this means we expect something like this: ( I think )
[dce3903]403         * <iq from=... to=... id=...>
[5ebff60]404         *      <si xmlns=si>
405         *      [       <file xmlns=ft/>    ] <-- not neccessary
406         *              <feature xmlns=feature>
407         *                      <x xmlns=xdata type=submit>
408         *                              <field var=stream-method>
409         *                                      <value>
[2ff2076]410         */
[5ebff60]411        if (!(tgt_jid = xt_find_attr(node, "from")) ||
412            !(ini_jid = xt_find_attr(node, "to")) ||
413            !(iq_id   = xt_find_attr(node, "id")) ||
414            !(c = xt_find_node(node->children, "si")) ||
415            !(cmp = xt_find_attr(c, "xmlns")) ||
416            !(strcmp(cmp, XMLNS_SI) == 0) ||
417            !(d = xt_find_node(c->children, "feature")) ||
418            !(cmp = xt_find_attr(d, "xmlns")) ||
419            !(strcmp(cmp, XMLNS_FEATURE) == 0) ||
420            !(d = xt_find_node(d->children, "x")) ||
421            !(cmp = xt_find_attr(d, "xmlns")) ||
422            !(strcmp(cmp, XMLNS_XDATA) == 0) ||
423            !(cmp = xt_find_attr(d, "type")) ||
424            !(strcmp(cmp, "submit") == 0) ||
425            !(d = xt_find_node(d->children, "field")) ||
426            !(cmp = xt_find_attr(d, "var")) ||
427            !(strcmp(cmp, "stream-method") == 0) ||
428            !(d = xt_find_node(d->children, "value"))) {
429                imcb_log(ic, "WARNING: Received incomplete Stream Initiation response");
[2ff2076]430                return XT_HANDLED;
431        }
432
[5ebff60]433        if (!(strcmp(d->text, XMLNS_BYTESTREAMS) == 0)) {
[2ff2076]434                /* since we should only have advertised what we can do and the peer should
435                 * only have chosen what we offered, this should never happen */
[5ebff60]436                imcb_log(ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text);
437
[2ff2076]438                return XT_HANDLED;
439        }
[5ebff60]440
[2ff2076]441        /* Let's see if we can find out what this bytestream should be for... */
442
[5ebff60]443        for (tflist = jd->filetransfers; tflist; tflist = g_slist_next(tflist)) {
[2ff2076]444                struct jabber_transfer *tft = tflist->data;
[5ebff60]445                if ((strcmp(tft->iq_id, iq_id) == 0)) {
446                        tf = tft;
[2ff2076]447                        break;
448                }
449        }
450
[5ebff60]451        if (!tf) {
452                imcb_log(ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid);
[2ff2076]453                return XT_HANDLED;
454        }
455
[5ebff60]456        tf->ini_jid = g_strdup(ini_jid);
457        tf->tgt_jid = g_strdup(tgt_jid);
[2ff2076]458
[5ebff60]459        imcb_log(ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid);
[dce3903]460
[5ebff60]461        jabber_bs_send_start(tf);
[2ff2076]462
463        return XT_HANDLED;
464}
465
[5ebff60]466int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf)
[2ff2076]467{
468        struct xt_node *node, *sinode;
469        struct jabber_buddy *bud;
470
471        /* who knows how many bits the future holds :) */
[5ebff60]472        char filesizestr[ 1 + ( int ) (0.301029995663981198f * sizeof(size_t) * 8) ];
[2ff2076]473
[5ebff60]474        const char *methods[] =
475        {
[2ff2076]476                XMLNS_BYTESTREAMS,
477                //XMLNS_IBB,
[5ebff60]478                NULL
[2ff2076]479        };
480        const char **m;
481        char *s;
482
483        /* Maybe we should hash this? */
[5ebff60]484        tf->sid = g_strdup_printf("BitlBeeJabberSID%d", tf->ft->local_id);
485
486        if ((s = strchr(who, '=')) && jabber_chat_by_jid(ic, s + 1)) {
487                bud = jabber_buddy_by_ext_jid(ic, who, 0);
488        } else {
489                bud = jabber_buddy_by_jid(ic, who, 0);
490        }
[2ff2076]491
492        /* start with the SI tag */
[5ebff60]493        sinode = xt_new_node("si", NULL, NULL);
494        xt_add_attr(sinode, "xmlns", XMLNS_SI);
495        xt_add_attr(sinode, "profile", XMLNS_FILETRANSFER);
496        xt_add_attr(sinode, "id", tf->sid);
[2ff2076]497
[5ebff60]498/*      if( mimetype )
499                xt_add_attr( node, "mime-type", mimetype ); */
[2ff2076]500
501        /* now the file tag */
502/*      if( desc )
[5ebff60]503                node = xt_new_node( "desc", descr, NULL ); */
504        node = xt_new_node("range", NULL, NULL);
505
506        sprintf(filesizestr, "%zd", tf->ft->file_size);
507        node = xt_new_node("file", NULL, node);
508        xt_add_attr(node, "xmlns", XMLNS_FILETRANSFER);
509        xt_add_attr(node, "name", tf->ft->file_name);
510        xt_add_attr(node, "size", filesizestr);
[2ff2076]511/*      if (hash)
[5ebff60]512                xt_add_attr( node, "hash", hash );
513        if (date)
514                xt_add_attr( node, "date", date ); */
[2ff2076]515
[5ebff60]516        xt_add_child(sinode, node);
[2ff2076]517
518        /* and finally the feature tag */
[5ebff60]519        node = xt_new_node("field", NULL, NULL);
520        xt_add_attr(node, "var", "stream-method");
521        xt_add_attr(node, "type", "list-single");
[2ff2076]522
[5ebff60]523        for (m = methods; *m; m++) {
524                xt_add_child(node, xt_new_node("option", NULL, xt_new_node("value", (char *) *m, NULL)));
525        }
[2ff2076]526
[5ebff60]527        node = xt_new_node("x", NULL, node);
528        xt_add_attr(node, "xmlns", XMLNS_XDATA);
529        xt_add_attr(node, "type", "form");
[2ff2076]530
[5ebff60]531        node = xt_new_node("feature", NULL, node);
532        xt_add_attr(node, "xmlns", XMLNS_FEATURE);
[2ff2076]533
[5ebff60]534        xt_add_child(sinode, node);
[2ff2076]535
536        /* and we are there... */
[5ebff60]537        node = jabber_make_packet("iq", "set", bud ? bud->full_jid : who, sinode);
538        jabber_cache_add(ic, node, jabber_si_handle_response);
539        tf->iq_id = g_strdup(xt_find_attr(node, "id"));
540
541        return jabber_write_packet(ic, node);
[2ff2076]542}
Note: See TracBrowser for help on using the repository browser.