aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/nogaim.h
blob: 3d45d0a007dced573883588be723850c6b59d626 (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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2004 Wilmer van der Gaast and others                *
  \********************************************************************/

/*
 * nogaim
 *
 * Gaim without gaim - for BitlBee
 *
 * This file contains functions called by the Gaim IM-modules. It contains
 * some struct and type definitions from Gaim.
 *
 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
 *                          (and possibly other members of the Gaim team)
 * Copyright 2002-2004 Wilmer van der Gaast <lintux@lintux.cx>
 */

/*
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef _NOGAIM_H
#define _NOGAIM_H

#include "bitlbee.h"
#include "proxy.h"
#include "md5.h"
#include "sha.h"


#define BUF_LEN MSG_LEN
#define BUF_LONG ( BUF_LEN * 2 )
#define MSG_LEN 2048
#define BUF_LEN MSG_LEN

#define SELF_ALIAS_LEN 400
#define BUDDY_ALIAS_MAXLEN 388   /* because MSN names can be 387 characters */

#define PERMIT_ALL      1
#define PERMIT_NONE     2
#define PERMIT_SOME     3
#define DENY_SOME       4

#define WEBSITE "http://www.bitlee.org/"
#define IM_FLAG_AWAY 0x0020
#define OPT_CONN_HTML 0x00000001
#define OPT_LOGGED_IN 0x00010000
#define GAIM_AWAY_CUSTOM "Custom"

#define GAIM_LOGO	0
#define GAIM_ERROR	1
#define GAIM_WARNING	2
#define GAIM_INFO	3

/* ok. now the fun begins. first we create a connection structure */
struct gaim_connection {
	/* we need to do either oscar or TOC */
	/* we make this as an int in case if we want to add more protocols later */
	struct prpl *prpl;
	guint32 flags;
	
	/* all connections need an input watcher */
	int inpa;
	
	/* buddy list stuff. there is still a global groups for the buddy list, but
	 * we need to maintain our own set of buddies, and our own permit/deny lists */
	GSList *permit;
	GSList *deny;
	int permdeny;
	
	/* all connections need a list of chats, even if they don't have chat */
	GSList *buddy_chats;
	
	/* each connection then can have its own protocol-specific data */
	void *proto_data;
	
	struct aim_user *user;
	
	char username[64];
	char displayname[128];
	char password[32];
	guint keepalive;
	/* stuff needed for per-connection idle times */
	guint idle_timer;
	time_t login_time;
	time_t lastsent;
	int is_idle;
	
	char *away;
	int is_auto_away;
	
	int evil;
	gboolean wants_to_die; /* defaults to FALSE */
	
	/* BitlBee */
	irc_t *irc;
	int lstitems;  /* added for msnP8 */
	
	struct conversation *conversations;
};

/* struct buddy_chat went away and got merged with this. */
struct conversation {
	struct gaim_connection *gc;

	/* stuff used just for chat */
        GList *in_room;
        GList *ignored;
        int id;
        
        /* BitlBee */
        struct conversation *next;
        char *channel;
        char *title;
        char joined;
        void *data;
};

struct buddy {
	char name[80];
	char show[BUDDY_ALIAS_MAXLEN];
        int present;
	int evil;
	time_t signon;
	time_t idle;
        int uc;
	guint caps; /* woohoo! */
	void *proto_data; /* what a hack */
	struct gaim_connection *gc; /* the connection it belongs to */
};

struct aim_user {
	char username[64];
	char alias[SELF_ALIAS_LEN]; 
	char password[32];
	char user_info[2048];
	int options;
	struct prpl *prpl;
	/* prpls can use this to save information about the user,
	 * like which server to connect to, etc */
	char proto_opt[7][256];

	struct gaim_connection *gc;
	irc_t *irc;
};

struct prpl {
	int options;
	const char *name;

	/* for ICQ and Yahoo, who have off/on per-conversation options */
	/* char *checkbox; this should be per-connection */

	GList *(* away_states)(struct gaim_connection *gc);
	GList *(* actions)();
	void   (* do_action)(struct gaim_connection *, char *);
	/* user_opts returns a GList* of g_malloc'd struct proto_user_opts */
	GList *(* user_opts)();
	GList *(* chat_info)(struct gaim_connection *);

	/* all the server-related functions */

	/* a lot of these (like get_dir) are protocol-dependent and should be removed. ones like
	 * set_dir (which is also protocol-dependent) can stay though because there's a dialog
	 * (i.e. the prpl says you can set your dir info, the ui shows a dialog and needs to call
	 * set_dir in order to set it) */

	void (* login)		(struct aim_user *);
	void (* close)		(struct gaim_connection *);
	int  (* send_im)	(struct gaim_connection *, char *who, char *message, int len, int away);
	int  (* send_typing)	(struct gaim_connection *, char *who, int typing);
	void (* set_info)	(struct gaim_connection *, char *info);
	void (* get_info)	(struct gaim_connection *, char *who);
	void (* set_away)	(struct gaim_connection *, char *state, char *message);
	void (* get_away)       (struct gaim_connection *, char *who);
	void (* set_idle)	(struct gaim_connection *, int idletime);
	void (* add_buddy)	(struct gaim_connection *, char *name);
	void (* remove_buddy)	(struct gaim_connection *, char *name, char *group);
	void (* add_permit)	(struct gaim_connection *, char *name);
	void (* add_deny)	(struct gaim_connection *, char *name);
	void (* rem_permit)	(struct gaim_connection *, char *name);
	void (* rem_deny)	(struct gaim_connection *, char *name);
	void (* set_permit_deny)(struct gaim_connection *);
	void (* join_chat)	(struct gaim_connection *, GList *data);
	void (* chat_invite)	(struct gaim_connection *, int id, char *who, char *message);
	void (* chat_leave)	(struct gaim_connection *, int id);
	void (* chat_whisper)	(struct gaim_connection *, int id, char *who, char *message);
	int  (* chat_send)	(struct gaim_connection *, int id, char *message);
	int  (* chat_open)	(struct gaim_connection *, char *who);
	void (* keepalive)	(struct gaim_connection *);

	/* get "chat buddy" info and away message */
	void (* get_cb_info)	(struct gaim_connection *, int, char *who);
	void (* get_cb_away)	(struct gaim_connection *, int, char *who);

	/* save/store buddy's alias on server list/roster */
	void (* alias_buddy)	(struct gaim_connection *, char *who);

	/* change a buddy's group on a server list/roster */
	void (* group_buddy)	(struct gaim_connection *, char *who, char *old_group, char *new_group);

	void (* buddy_free)	(struct buddy *);

	char *(* get_status_string) (struct gaim_connection *gc, int stat);

	int (* cmp_buddynames) (const char *who1, const char *who2);
};

#define UC_UNAVAILABLE  1

/* JABBER */
#define UC_AWAY (0x02 | UC_UNAVAILABLE)
#define UC_CHAT  0x04
#define UC_XA   (0x08 | UC_UNAVAILABLE)
#define UC_DND  (0x10 | UC_UNAVAILABLE)

G_MODULE_EXPORT GSList *get_connections();
G_MODULE_EXPORT struct prpl *find_protocol(const char *name);
G_MODULE_EXPORT void register_protocol(struct prpl *);

/* nogaim.c */
int serv_send_im(irc_t *irc, user_t *u, char *msg, int flags);
int serv_send_chat(irc_t *irc, struct gaim_connection *gc, int id, char *msg );

G_MODULE_EXPORT signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf );
char *set_eval_charset( irc_t *irc, set_t *set, char *value );

void nogaim_init();
int proto_away( struct gaim_connection *gc, char *away );
char *set_eval_away_devoice( irc_t *irc, set_t *set, char *value );

gboolean auto_reconnect( gpointer data );
void cancel_auto_reconnect( struct account *a );

/* multi.c */
G_MODULE_EXPORT struct gaim_connection *new_gaim_conn( struct aim_user *user );
G_MODULE_EXPORT void destroy_gaim_conn( struct gaim_connection *gc );
G_MODULE_EXPORT void set_login_progress( struct gaim_connection *gc, int step, char *msg );
G_MODULE_EXPORT void hide_login_progress( struct gaim_connection *gc, char *msg );
G_MODULE_EXPORT void hide_login_progress_error( struct gaim_connection *gc, char *msg );
G_MODULE_EXPORT void serv_got_crap( struct gaim_connection *gc, char *format, ... );
G_MODULE_EXPORT void account_online( struct gaim_connection *gc );
G_MODULE_EXPORT void account_offline( struct gaim_connection *gc );
G_MODULE_EXPORT void signoff( struct gaim_connection *gc );

/* dialogs.c */
G_MODULE_EXPORT void do_error_dialog( struct gaim_connection *gc, char *msg, char *title );
G_MODULE_EXPORT void do_ask_dialog( struct gaim_connection *gc, char *msg, void *data, void *doit, void *dont );

/* list.c */
G_MODULE_EXPORT int bud_list_cache_exists( struct gaim_connection *gc );
G_MODULE_EXPORT void do_import( struct gaim_connection *gc, void *null );
G_MODULE_EXPORT void add_buddy( struct gaim_connection *gc, char *group, char *handle, char *realname );
G_MODULE_EXPORT struct buddy *find_buddy( struct gaim_connection *gc, char *handle );
G_MODULE_EXPORT void do_export( struct gaim_connection *gc );
G_MODULE_EXPORT void signoff_blocked( struct gaim_connection *gc );

G_MODULE_EXPORT void serv_buddy_rename( struct gaim_connection *gc, char *handle, char *realname );

/* buddy_chat.c */
G_MODULE_EXPORT void add_chat_buddy( struct conversation *b, char *handle );
G_MODULE_EXPORT void remove_chat_buddy( struct conversation *b, char *handle, char *reason );

/* prpl.c */
G_MODULE_EXPORT void show_got_added( struct gaim_connection *gc, char *id, char *handle, const char *realname, const char *msg );

/* server.c */                    
G_MODULE_EXPORT void serv_got_update( struct gaim_connection *gc, char *handle, int loggedin, int evil, time_t signon, time_t idle, int type, guint caps );
G_MODULE_EXPORT void serv_got_im( struct gaim_connection *gc, char *handle, char *msg, guint32 flags, time_t mtime, gint len );
G_MODULE_EXPORT void serv_got_typing( struct gaim_connection *gc, char *handle, int timeout, int type );
G_MODULE_EXPORT void serv_got_chat_invite( struct gaim_connection *gc, char *handle, char *who, char *msg, GList *data );
G_MODULE_EXPORT struct conversation *serv_got_joined_chat( struct gaim_connection *gc, int id, char *handle );
G_MODULE_EXPORT void serv_got_chat_in( struct gaim_connection *gc, int id, char *who, int whisper, char *msg, time_t mtime );
G_MODULE_EXPORT void serv_got_chat_left( struct gaim_connection *gc, int id );

/* util.c */
G_MODULE_EXPORT void strip_linefeed( gchar *text );
G_MODULE_EXPORT char *add_cr( char *text );
G_MODULE_EXPORT char *tobase64( const char *text );
G_MODULE_EXPORT char *normalize( const char *s );
G_MODULE_EXPORT time_t get_time( int year, int month, int day, int hour, int min, int sec );
G_MODULE_EXPORT void strip_html( char *msg );
G_MODULE_EXPORT char *escape_html( const char *html );
G_MODULE_EXPORT void info_string_append(GString *str, char *newline, char *name, char *value);
G_MODULE_EXPORT char *ipv6_wrap( char *src );
G_MODULE_EXPORT char *ipv6_unwrap( char *src );

/* prefs.c */
G_MODULE_EXPORT void build_block_list();
G_MODULE_EXPORT void build_allow_list();

struct conversation *conv_findchannel( char *channel );


#endif
">->cblen + len + 1); memcpy(req->cbuf + req->cblen, buffer, len); req->cblen += len; req->cbuf[req->cblen] = '\0'; } /* Turns out writing a proper chunked-encoding state machine is not that simple. :-( I've tested this one feeding it byte by byte so I hope it's solid now. */ chunk = req->cbuf; eos = req->cbuf + req->cblen; while (TRUE) { int clen = 0; /* Might be a \r\n from the last chunk. */ s = chunk; while (g_ascii_isspace(*s)) { s++; } /* Chunk length. Might be incomplete. */ if (s < eos && sscanf(s, "%x", &clen) != 1) { return CR_ERROR; } while (g_ascii_isxdigit(*s)) { s++; } /* If we read anything here, it *must* be \r\n. */ if (strncmp(s, "\r\n", MIN(2, eos - s)) != 0) { return CR_ERROR; } s += 2; if (s >= eos) { break; } /* 0-length chunk means end of response. */ if (clen == 0) { return CR_EOF; } /* Wait for the whole chunk to arrive. */ if (s + clen > eos) { break; } if (http_process_data(req, s, clen) != CR_OK) { return CR_ABORT; } chunk = s + clen; } if (chunk != req->cbuf) { req->cblen = eos - chunk; s = g_memdup(chunk, req->cblen + 1); g_free(req->cbuf); req->cbuf = s; } return CR_OK; } static http_ret_t http_process_data(struct http_request *req, const char *buffer, int len) { if (len <= 0) { return CR_OK; } if (!req->reply_body) { req->reply_headers = g_realloc(req->reply_headers, req->bytes_read + len + 1); memcpy(req->reply_headers + req->bytes_read, buffer, len); req->bytes_read += len; req->reply_headers[req->bytes_read] = '\0'; if (strstr(req->reply_headers, "\r\n\r\n") || strstr(req->reply_headers, "\n\n")) { /* We've now received all headers. Look for something interesting. */ if (!http_handle_headers(req)) { return CR_ABORT; } /* Start parsing the body as chunked if required. */ if (req->flags & HTTPC_CHUNKED) { return http_process_chunked_data(req, NULL, 0); } } } else { int pos = req->reply_body - req->sbuf; req->sbuf = g_realloc(req->sbuf, req->sblen + len + 1); memcpy(req->sbuf + req->sblen, buffer, len); req->bytes_read += len; req->sblen += len; req->sbuf[req->sblen] = '\0'; req->reply_body = req->sbuf + pos; req->body_size = req->sblen - pos; } if ((req->flags & HTTPC_STREAMING) && req->reply_body && req->func != NULL) { req->func(req); } return CR_OK; } /* Splits headers and body. Checks result code, in case of 300s it'll handle redirects. If this returns FALSE, don't call any callbacks! */ static gboolean http_handle_headers(struct http_request *req) { char *end1, *end2, *s; int evil_server = 0; /* Zero termination is very convenient. */ req->reply_headers[req->bytes_read] = '\0'; /* Find the separation between headers and body, and keep stupid webservers in mind. */ end1 = strstr(req->reply_headers, "\r\n\r\n"); end2 = strstr(req->reply_headers, "\n\n"); if (end2 && end2 < end1) { end1 = end2 + 1; evil_server = 1; } else if (end1) { end1 += 2; } else { req->status_string = g_strdup("Malformed HTTP reply"); return TRUE; } *end1 = '\0'; if (getenv("BITLBEE_DEBUG")) { printf("HTTP response headers:\n%s\n", req->reply_headers); } if (evil_server) { req->reply_body = end1 + 1; } else { req->reply_body = end1 + 2; } /* Separately allocated space for headers and body. */ req->sblen = req->body_size = req->reply_headers + req->bytes_read - req->reply_body; req->sbuf = req->reply_body = g_memdup(req->reply_body, req->body_size + 1); req->reply_headers = g_realloc(req->reply_headers, end1 - req->reply_headers + 1); if ((end1 = strchr(req->reply_headers, ' ')) != NULL) { if (sscanf(end1 + 1, "%hd", &req->status_code) != 1) { req->status_string = g_strdup("Can't parse status code"); req->status_code = -1; } else { char *eol; if (evil_server) { eol = strchr(end1, '\n'); } else { eol = strchr(end1, '\r'); } req->status_string = g_strndup(end1 + 1, eol - end1 - 1); /* Just to be sure... */ if ((eol = strchr(req->status_string, '\r'))) { *eol = 0; } if ((eol = strchr(req->status_string, '\n'))) { *eol = 0; } } } else { req->status_string = g_strdup("Can't locate status code"); req->status_code = -1; } if (((req->status_code >= 301 && req->status_code <= 303) || req->status_code == 307) && req->redir_ttl-- > 0) { char *loc, *new_request, *new_host; int error = 0, new_port, new_proto; /* We might fill it again, so let's not leak any memory. */ g_free(req->status_string); req->status_string = NULL; loc = strstr(req->reply_headers, "\nLocation: "); if (loc == NULL) { /* We can't handle this redirect... */ req->status_string = g_strdup("Can't locate Location: header"); return TRUE; } loc += 11; while (*loc == ' ') { loc++; } /* TODO/FIXME: Possibly have to handle relative redirections, and rewrite Host: headers. Not necessary for now, it's enough for passport authentication like this. */ if (*loc == '/') { /* Just a different pathname... */ /* Since we don't cache the servername, and since we don't need this yet anyway, I won't implement it. */ req->status_string = g_strdup("Can't handle relative redirects"); return TRUE; } else { /* A whole URL */ url_t *url; char *s, *version, *headers; const char *new_method; s = strstr(loc, "\r\n"); if (s == NULL) { return TRUE; } url = g_new0(url_t, 1); *s = 0; if (!url_set(url, loc)) { req->status_string = g_strdup("Malformed redirect URL"); g_free(url); return TRUE; } /* Find all headers and, if necessary, the POST request contents. Skip the old Host: header though. This crappy code here means anything using this http_client MUST put the Host: header at the top. */ if (!((s = strstr(req->request, "\r\nHost: ")) && (s = strstr(s + strlen("\r\nHost: "), "\r\n")))) { req->status_string = g_strdup("Error while rebuilding request string"); g_free(url); return TRUE; } headers = s; /* More or less HTTP/1.0 compliant, from my reading of RFC 2616. Always perform a GET request unless we received a 301. 303 was meant for this but it's HTTP/1.1-only and we're specifically speaking HTTP/1.0. ... Well except someone at identi.ca's didn't bother reading any RFCs and just return HTTP/1.1-specific status codes to HTTP/1.0 requests. Fuckers. So here we are, handle 301..303,307. */ if (strncmp(req->request, "GET", 3) == 0) { /* GETs never become POSTs. */ new_method = "GET"; } else if (req->status_code == 302 || req->status_code == 303) { /* 302 de-facto becomes GET, 303 as specified by RFC 2616#10.3.3 */ new_method = "GET"; } else { /* 301 de-facto should stay POST, 307 specifally RFC 2616#10.3.8 */ new_method = "POST"; } if ((version = strstr(req->request, " HTTP/")) && (s = strstr(version, "\r\n"))) { version++; version = g_strndup(version, s - version); } else { version = g_strdup("HTTP/1.0"); } /* Okay, this isn't fun! We have to rebuild the request... :-( */ new_request = g_strdup_printf("%s %s %s\r\nHost: %s%s", new_method, url->file, version, url->host, headers); new_host = g_strdup(url->host); new_port = url->port; new_proto = url->proto; /* If we went from POST to GET, truncate the request content. */ if (new_request[0] != req->request[0] && new_request[0] == 'G' && (s = strstr(new_request, "\r\n\r\n"))) { s[4] = '\0'; } g_free(url); g_free(version); } if (req->ssl) { ssl_disconnect(req->ssl); } else { closesocket(req->fd); } req->fd = -1; req->ssl = NULL; if (getenv("BITLBEE_DEBUG")) { printf("New headers for redirected HTTP request:\n%s\n", new_request); } if (new_proto == PROTO_HTTPS) { req->ssl = ssl_connect(new_host, new_port, TRUE, http_ssl_connected, req); if (req->ssl == NULL) { error = 1; } } else { req->fd = proxy_connect(new_host, new_port, http_connected, req); if (req->fd < 0) { error = 1; } } g_free(new_host); if (error) { req->status_string = g_strdup("Connection problem during redirect"); g_free(new_request); return TRUE; } g_free(req->request); g_free(req->reply_headers); g_free(req->sbuf); req->request = new_request; req->request_length = strlen(new_request); req->bytes_read = req->bytes_written = req->inpa = 0; req->reply_headers = req->reply_body = NULL; req->sbuf = req->cbuf = NULL; req->sblen = req->cblen = 0; return FALSE; } if ((s = get_rfc822_header(req->reply_headers, "Content-Length", 0)) && sscanf(s, "%d", &req->content_length) != 1) { req->content_length = -1; } g_free(s); if ((s = get_rfc822_header(req->reply_headers, "Transfer-Encoding", 0))) { if (strcasestr(s, "chunked")) { req->flags |= HTTPC_CHUNKED; req->cbuf = req->sbuf; req->cblen = req->sblen; req->reply_body = req->sbuf = g_strdup(""); req->body_size = req->sblen = 0; } g_free(s); } return TRUE; } void http_flush_bytes(struct http_request *req, size_t len) { if (len <= 0 || len > req->body_size || !(req->flags & HTTPC_STREAMING)) { return; } req->reply_body += len; req->body_size -= len; if (req->reply_body - req->sbuf >= 512) { char *new = g_memdup(req->reply_body, req->body_size + 1); g_free(req->sbuf); req->reply_body = req->sbuf = new; req->sblen = req->body_size; } } void http_close(struct http_request *req) { if (!req) { return; } if (req->inpa > 0) { b_event_remove(req->inpa); } if (req->ssl) { ssl_disconnect(req->ssl); } else { proxy_disconnect(req->fd); } http_free(req); } static void http_free(struct http_request *req) { g_free(req->request); g_free(req->reply_headers); g_free(req->status_string); g_free(req->sbuf); g_free(req->cbuf); g_free(req); }