aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/jabber/jid.c
blob: ed2b9ba13d44786c63153ed988dec5ed87757608 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* --------------------------------------------------------------------------
 *
 * License
 *
 * The contents of this file are subject to the Jabber Open Source License
 * Version 1.0 (the "JOSL").  You may not copy or use this file, in either
 * source code or executable form, except in compliance with the JOSL. You
 * may obtain a copy of the JOSL at http://www.jabber.org/ or at
 * http://www.opensource.org/.  
 *
 * Software distributed under the JOSL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the JOSL
 * for the specific language governing rights and limitations under the
 * JOSL.
 *
 * Copyrights
 * 
 * Portions created by or assigned to Jabber.com, Inc. are 
 * Copyright (c) 1999-2002 Jabber.com, Inc.  All Rights Reserved.  Contact
 * information for Jabber.com, Inc. is available at http://www.jabber.com/.
 *
 * Portions Copyright (c) 1998-1999 Jeremie Miller.
 * 
 * Acknowledgements
 * 
 * Special thanks to the Jabber Open Source Contributors for their
 * suggestions and support of Jabber.
 * 
 * Alternatively, the contents of this file may be used under the terms of the
 * GNU General Public License Version 2 or later (the "GPL"), in which case
 * the provisions of the GPL are applicable instead of those above.  If you
 * wish to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the JOSL,
 * indicate your decision by deleting the provisions above and replace them
 * with the notice and other provisions required by the GPL.  If you do not
 * delete the provisions above, a recipient may use your version of this file
 * under either the JOSL or the GPL. 
 * 
 * 
 * --------------------------------------------------------------------------*/

#include "jabber.h"
#include <glib.h>

static jid jid_safe(jid id)
{
    char *str;

    if(strlen(id->server) == 0 || strlen(id->server) > 255)
        return NULL;

    /* lowercase the hostname, make sure it's valid characters */
    for(str = id->server; *str != '\0'; str++)
    {
        *str = tolower(*str);
        if(!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_')) return NULL;
    }

    /* cut off the user */
    if(id->user != NULL && strlen(id->user) > 64)
        id->user[64] = '\0';

    /* check for low and invalid ascii characters in the username */
    if(id->user != NULL)
        for(str = id->user; *str != '\0'; str++)
            if(*str <= 32 || *str == ':' || *str == '@' || *str == '<' || *str == '>' || *str == '\'' || *str == '"' || *str == '&') return NULL;

    return id;
}

jid jid_new(pool p, char *idstr)
{
    char *server, *resource, *type, *str;
    jid id;

    if(p == NULL || idstr == NULL || strlen(idstr) == 0)
        return NULL;

    /* user@server/resource */

    str = pstrdup(p, idstr);

    id = pmalloco(p,sizeof(struct jid_struct));
    id->p = p;

    resource = strstr(str,"/");
    if(resource != NULL)
    {
        *resource = '\0';
        ++resource;
        if(strlen(resource) > 0)
            id->resource = resource;
    }else{
        resource = str + strlen(str); /* point to end */
    }

    type = strstr(str,":");
    if(type != NULL && type < resource)
    {
        *type = '\0';
        ++type;
        str = type; /* ignore the type: prefix */
    }

    server = strstr(str,"@");
    if(server == NULL || server > resource)
    { /* if there's no @, it's just the server address */
        id->server = str;
    }else{
        *server = '\0';
        ++server;
        id->server = server;
        if(strlen(str) > 0)
            id->user = str;
    }

    return jid_safe(id);
}

char *jid_full(jid id)
{
    spool s;

    if(id == NULL)
        return NULL;

    /* use cached copy */
    if(id->full != NULL)
        return id->full;

    s = spool_new(id->p);

    if(id->user != NULL)
        spooler(s, id->user,"@",s);

    spool_add(s, id->server);

    if(id->resource != NULL)
        spooler(s, "/",id->resource,s);

    id->full = spool_print(s);
    return id->full;
}

/* local utils */
static int _jid_nullstrcmp(char *a, char *b)
{
    if(a == NULL && b == NULL) return 0;
    if(a == NULL || b == NULL) return -1;
    return strcmp(a,b);
}
static int _jid_nullstrcasecmp(char *a, char *b)
{
    if(a == NULL && b == NULL) return 0;
    if(a == NULL || b == NULL) return -1;
    return g_strcasecmp(a,b);
}

/* suggested by Anders Qvist <quest@valdez.netg.se> */
int jid_cmpx(jid a, jid b, int parts)
{
    if(a == NULL || b == NULL)
        return -1;

    if(parts & JID_RESOURCE && _jid_nullstrcmp(a->resource, b->resource) != 0) return -1;
    if(parts & JID_USER && _jid_nullstrcasecmp(a->user, b->user) != 0) return -1;
    if(parts & JID_SERVER && _jid_nullstrcmp(a->server, b->server) != 0) return -1;

    return 0;
}
> /* TODO: randomize */ char buf[2048]; msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); file->data = msn_file; file->free = msn_ftp_free; file->canceled = msn_ftp_canceled; file->write = msn_ftps_write; msn_file->md = ic->proto_data; msn_file->invite_cookie = cookie; msn_file->handle = g_strdup( who ); msn_file->dcc = file; msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); msn_file->fd = -1; msn_file->sbufpos = 3; g_snprintf( buf, sizeof( buf ), "Application-Name: File Transfer\r\n" "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" "Application-File: %s\r\n" "Application-FileSize: %zd\r\n", file->file_name, file->file_size); msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf ); imcb_file_recv_start( file ); } void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { char *itype = msn_findheader( body, "Application-GUID:", blen ); char *name, *size, *invitecookie, *reject = NULL; user_t *u; size_t isize; file_transfer_t *file; if( !itype || strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) != 0 ) { /* Don't know what that is - don't care */ char *iname = msn_findheader( body, "Application-Name:", blen ); imcb_log( sb->ic, "Received unknown MSN invitation %s (%s) from %s", itype ? : "with no GUID", iname ? iname : "no application name", handle ); g_free( iname ); reject = "REJECT_NOT_INSTALLED"; } else if ( !( name = msn_findheader( body, "Application-File:", blen )) || !( size = msn_findheader( body, "Application-FileSize:", blen )) || !( invitecookie = msn_findheader( body, "Invitation-Cookie:", blen)) || !( isize = atoll( size ) ) ) { imcb_log( sb->ic, "Received corrupted transfer request from %s" "(name=%s, size=%s, invitecookie=%s)", handle, name, size, invitecookie ); reject = "REJECT"; } else if ( !( u = user_findhandle( sb->ic, handle ) ) ) { imcb_log( sb->ic, "Error in parsing transfer request, User '%s'" "is not in contact list", handle ); reject = "REJECT"; } else if ( !( file = imcb_file_send_start( sb->ic, handle, name, isize ) ) ) { imcb_log( sb->ic, "Error initiating transfer for request from %s for %s", handle, name ); reject = "REJECT"; } else { msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); file->data = msn_file; file->accept = msn_ftpr_accept; file->free = msn_ftp_free; file->finished = msn_ftp_finished; file->canceled = msn_ftp_canceled; file->write_request = msn_ftpr_write_request; msn_file->md = sb->ic->proto_data; msn_file->invite_cookie = cookie; msn_file->handle = g_strdup( handle ); msn_file->dcc = file; msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); msn_file->fd = -1; } if( reject ) msn_ftp_cancel_invite( sb->ic, sb->who, cookie, reject ); g_free( name ); g_free( size ); g_free( invitecookie ); g_free( itype ); } msn_filetransfer_t* msn_find_filetransfer( struct msn_data *md, unsigned int cookie, char *handle ) { GSList *l; for( l = md->filetransfers; l; l = l->next ) { msn_filetransfer_t *file = ( (file_transfer_t*) l->data )->data; if( file->invite_cookie == cookie && strcmp( handle, file->handle ) == 0 ) { return file; } } return NULL; } gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond ) { file_transfer_t *file = data; msn_filetransfer_t *msn_file = file->data; struct sockaddr_storage clt_addr; socklen_t ssize = sizeof( clt_addr ); debug( "Connected to MSNFTP client" ); ASSERTSOCKOP( msn_file->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); closesocket( fd ); fd = msn_file->fd; sock_make_nonblocking( fd ); msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file ); return FALSE; } void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { file_transfer_t *file = msn_file->dcc; char buf[1024]; unsigned int acookie = time ( NULL ); char host[HOST_NAME_MAX+1]; char port[6]; char *errmsg; msn_file->auth_cookie = acookie; if( ( msn_file->fd = ft_listen( NULL, host, port, FALSE, &errmsg ) ) == -1 ) { msn_ftp_abort( file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); return; } msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file ); g_snprintf( buf, sizeof( buf ), "IP-Address: %s\r\n" "Port: %s\r\n" "AuthCookie: %d\r\n" "Launch-Application: FALSE\r\n" "Request-Data: IP-Address:\r\n\r\n", host, port, msn_file->auth_cookie ); msn_ftp_invitation_cmd( msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf ); } void msn_invitationr_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { file_transfer_t *file = msn_file->dcc; char *authcookie, *ip, *port; if( !( authcookie = msn_findheader( body, "AuthCookie:", blen ) ) || !( ip = msn_findheader( body, "IP-Address:", blen ) ) || !( port = msn_findheader( body, "Port:", blen ) ) ) { msn_ftp_abort( file, "Received invalid accept reply" ); } else if( ( msn_file->fd = proxy_connect( ip, atoi( port ), msn_ftp_connected, file ) ) < 0 ) { msn_ftp_abort( file, "Error connecting to MSN client" ); } else msn_file->auth_cookie = strtoul( authcookie, NULL, 10 ); g_free( authcookie ); g_free( ip ); g_free( port ); } void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); file_transfer_t *file = msn_file ? msn_file->dcc : NULL; if( !msn_file ) imcb_log( sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)" ); else if( file->sending ) msn_invitations_accept( msn_file, sb, handle, cookie, body, blen ); else msn_invitationr_accept( msn_file, sb, handle, cookie, body, blen ); } void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); if( !msn_file ) imcb_log( sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)" ); else msn_ftp_abort( msn_file->dcc, msn_findheader( body, "Cancel-Code:", blen ) ); } int msn_ftp_write( file_transfer_t *file, char *format, ... ) { msn_filetransfer_t *msn_file = file->data; va_list params; int st; char *s; va_start( params, format ); s = g_strdup_vprintf( format, params ); va_end( params ); st = write( msn_file->fd, s, strlen( s ) ); if( st != strlen( s ) ) return msn_ftp_abort( file, "Error sending data over MSNFTP connection: %s", strerror( errno ) ); g_free( s ); return 1; } gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ) { file_transfer_t *file = data; msn_filetransfer_t *msn_file = file->data; debug( "Connected to MSNFTP server, starting authentication" ); if( !msn_ftp_write( file, "VER MSNFTP\r\n" ) ) return FALSE; sock_make_nonblocking( msn_file->fd ); msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); return FALSE; } gboolean msn_ftp_handle_command( file_transfer_t *file, char* line ) { msn_filetransfer_t *msn_file = file->data; char **cmd = msn_linesplit( line ); int count = 0; if( cmd[0] ) while( cmd[++count] ); if( count < 1 ) return msn_ftp_abort( file, "Missing command in MSNFTP communication" ); if( strcmp( cmd[0], "VER" ) == 0 ) { if( strcmp( cmd[1], "MSNFTP" ) != 0 ) return msn_ftp_abort( file, "Unsupported filetransfer protocol: %s", cmd[1] ); if( file->sending ) msn_ftp_write( file, "VER MSNFTP\r\n" ); else msn_ftp_write( file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie ); } else if( strcmp( cmd[0], "FIL" ) == 0 ) { if( strtoul( cmd[1], NULL, 10 ) != file->file_size ) return msn_ftp_abort( file, "FIL reply contains a different file size than the size in the invitation" ); msn_ftp_write( file, "TFR\r\n" ); msn_file->status |= MSN_TRANSFER_RECEIVING; } else if( strcmp( cmd[0], "USR" ) == 0 ) { if( ( strcmp( cmd[1], msn_file->handle ) != 0 ) || ( strtoul( cmd[2], NULL, 10 ) != msn_file->auth_cookie ) ) msn_ftp_abort( file, "Authentication failed. " "Expected handle: %s (got %s), cookie: %u (got %s)", msn_file->handle, cmd[1], msn_file->auth_cookie, cmd[2] ); msn_ftp_write( file, "FIL %zu\r\n", file->file_size); } else if( strcmp( cmd[0], "TFR" ) == 0 ) { file->write_request( file ); } else if( strcmp( cmd[0], "BYE" ) == 0 ) { unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1; if( ( retcode==16777989 ) || ( retcode==16777987 ) ) imcb_file_finished( file ); else if( retcode==2147942405 ) imcb_file_canceled( file, "Failure: receiver is out of disk space" ); else if( retcode==2164261682 ) imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); else if( retcode==2164261683 ) imcb_file_canceled( file, "Failure: sender has cancelled the transfer" ); else if( retcode==2164261694 ) imcb_file_canceled( file, "Failure: connection is blocked" ); else { char buf[128]; sprintf( buf, "Failure: unknown BYE code: %d", retcode); imcb_file_canceled( file, buf ); } } else if( strcmp( cmd[0], "CCL" ) == 0 ) { imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); } else { msn_ftp_abort( file, "Received invalid command %s from msn client", cmd[0] ); } return TRUE; } gboolean msn_ftp_send( gpointer data, gint fd, b_input_condition cond ) { file_transfer_t *file = data; msn_filetransfer_t *msn_file = file->data; msn_file->w_event_id = 0; file->write_request( file ); return FALSE; } /* * This should only be called if we can write, so just do it. * Add a write watch so we can write more during the next cycle (if possible). * This got a bit complicated because (at least) amsn expects packets of size 2045. */ gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ) { msn_filetransfer_t *msn_file = file->data; int ret, overflow; /* what we can't send now */ overflow = msn_file->sbufpos + len - MSNFTP_PSIZE; /* append what we can do the send buffer */ memcpy( msn_file->sbuf + msn_file->sbufpos, buffer, MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ) ); msn_file->sbufpos += MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ); /* if we don't have enough for a full packet and there's more wait for it */ if( ( msn_file->sbufpos < MSNFTP_PSIZE ) && ( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) { if( !msn_file->w_event_id ) msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); return TRUE; } /* Accumulated enough data, lets send something out */ msn_file->sbuf[0] = 0; msn_file->sbuf[1] = ( msn_file->sbufpos - 3 ) & 0xff; msn_file->sbuf[2] = ( ( msn_file->sbufpos - 3 ) >> 8 ) & 0xff; ASSERTSOCKOP( ret = send( msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0 ), "Sending" ); msn_file->data_sent += ret - 3; /* TODO: this should really not be fatal */ if( ret < msn_file->sbufpos ) return msn_ftp_abort( file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos ); msn_file->sbufpos = 3; if( overflow > 0 ) { while( overflow > ( MSNFTP_PSIZE - 3 ) ) { if( !msn_ftps_write( file, buffer + len - overflow, MSNFTP_PSIZE - 3 ) ) return FALSE; overflow -= MSNFTP_PSIZE - 3; } return msn_ftps_write( file, buffer + len - overflow, overflow ); } if( msn_file->data_sent == file->file_size ) { if( msn_file->w_event_id ) { b_event_remove( msn_file->w_event_id ); msn_file->w_event_id = 0; } } else { /* we might already be listening if this is data from an overflow */ if( !msn_file->w_event_id ) msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); } return TRUE; } /* Binary part of the file transfer protocol */ gboolean msn_ftpr_read( file_transfer_t *file ) { msn_filetransfer_t *msn_file = file->data; int st; unsigned char buf[3]; if( msn_file->data_remaining ) { msn_file->r_event_id = 0; ASSERTSOCKOP( st = read( msn_file->fd, file->buffer, MIN( sizeof( file->buffer ), msn_file->data_remaining ) ), "Receiving" ); if( st == 0 ) return msn_ftp_abort( file, "Remote end closed connection"); msn_file->data_sent += st; msn_file->data_remaining -= st; file->write( file, file->buffer, st ); if( msn_file->data_sent >= file->file_size ) imcb_file_finished( file ); return FALSE; } else { ASSERTSOCKOP( st = read( msn_file->fd, buf, 1 ), "Receiving" ); if( st == 0 ) { return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); } else if( buf[0] == '\r' || buf[0] == '\n' ) { debug( "Discarding extraneous newline" ); } else if( buf[0] != 0 ) { msn_ftp_abort( file, "Remote end canceled the transfer"); /* don't really care about these last 2 (should be 0,0) */ read( msn_file->fd, buf, 2 ); return FALSE; } else { unsigned int size; ASSERTSOCKOP( st = read( msn_file->fd, buf, 2 ), "Receiving" ); if( st < 2 ) return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); size = buf[0] + ((unsigned int) buf[1] << 8); msn_file->data_remaining = size; } } return TRUE; } /* Text mode part of the file transfer protocol */ gboolean msn_ftp_txtproto( file_transfer_t *file ) { msn_filetransfer_t *msn_file = file->data; int i = msn_file->tbufpos, st; char *tbuf = msn_file->tbuf; ASSERTSOCKOP( st = read( msn_file->fd, tbuf + msn_file->tbufpos, sizeof( msn_file->tbuf ) - msn_file->tbufpos ), "Receiving" ); if( st == 0 ) return msn_ftp_abort( file, "read returned EOF while reading text from msn client" ); msn_file->tbufpos += st; do { for( ;i < msn_file->tbufpos; i++ ) { if( tbuf[i] == '\n' || tbuf[i] == '\r' ) { tbuf[i] = '\0'; if( i > 0 ) msn_ftp_handle_command( file, tbuf ); else while( tbuf[i] == '\n' || tbuf[i] == '\r' ) i++; memmove( tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1 ); msn_file->tbufpos -= i + 1; i = 0; break; } } } while ( i < msn_file->tbufpos ); if( msn_file->tbufpos == sizeof( msn_file->tbuf ) ) return msn_ftp_abort( file, "Line exceeded %d bytes in text protocol", sizeof( msn_file->tbuf ) ); return TRUE; } gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ) { file_transfer_t *file = data; msn_filetransfer_t *msn_file = file->data; if( msn_file->status & MSN_TRANSFER_RECEIVING ) return msn_ftpr_read( file ); else return msn_ftp_txtproto( file ); } void msn_ftp_free( file_transfer_t *file ) { msn_filetransfer_t *msn_file = file->data; if( msn_file->r_event_id ) b_event_remove( msn_file->r_event_id ); if( msn_file->w_event_id ) b_event_remove( msn_file->w_event_id ); if( msn_file->fd != -1 ) closesocket( msn_file->fd ); msn_file->md->filetransfers = g_slist_remove( msn_file->md->filetransfers, msn_file->dcc ); g_free( msn_file->handle ); g_free( msn_file ); } void msn_ftpr_accept( file_transfer_t *file ) { msn_filetransfer_t *msn_file = file->data; msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT", "Launch-Application: FALSE\r\n" "Request-Data: IP-Address:\r\n"); } void msn_ftp_finished( file_transfer_t *file ) { msn_ftp_write( file, "BYE 16777989\r\n" ); } void msn_ftp_canceled( file_transfer_t *file, char *reason ) { msn_filetransfer_t *msn_file = file->data; msn_ftp_cancel_invite( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, file->status & FT_STATUS_TRANSFERRING ? "FTTIMEOUT" : "FAIL" ); imcb_log( msn_file->md->ic, "File transfer aborted: %s", reason ); } gboolean msn_ftpr_write_request( file_transfer_t *file ) { msn_filetransfer_t *msn_file = file->data; if( msn_file->r_event_id != 0 ) { msn_ftp_abort( file, "BUG in MSN file transfer:" "write_request called when" "already watching for input" ); return FALSE; } msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); return TRUE; }