aboutsummaryrefslogtreecommitdiffstats
path: root/lib/oauth.c
blob: 005d76c494685bde6016b5ccbc8407ea48f2793e (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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/***************************************************************************\
*                                                                           *
*  BitlBee - An IRC to IM gateway                                           *
*  Simple OAuth client (consumer) implementation.                           *
*                                                                           *
*  Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net>                   *
*                                                                           *
*  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 along  *
*  with this program; if not, write to the Free Software Foundation, Inc.,  *
*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
*                                                                           *
\***************************************************************************/

#include <glib.h>
#include <gmodule.h>
#include <stdlib.h>
#include <string.h>
#include "http_client.h"
#include "base64.h"
#include "misc.h"
#include "sha1.h"
#include "url.h"
#include "oauth.h"

#define HMAC_BLOCK_SIZE 64

static char *oauth_sign(const char *method, const char *url,
                        const char *params, struct oauth_info *oi)
{
	uint8_t hash[SHA1_HASH_SIZE];
	GString *payload = g_string_new("");
	char *key;
	char *s;

	key = g_strdup_printf("%s&%s", oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : "");

	g_string_append_printf(payload, "%s&", method);

	s = g_new0(char, strlen(url) * 3 + 1);
	strcpy(s, url);
	http_encode(s);
	g_string_append_printf(payload, "%s&", s);
	g_free(s);

	s = g_new0(char, strlen(params) * 3 + 1);
	strcpy(s, params);
	http_encode(s);
	g_string_append(payload, s);
	g_free(s);

	sha1_hmac(key, 0, payload->str, 0, hash);

	g_free(key);
	g_string_free(payload, TRUE);

	/* base64_encode + HTTP escape it (both consumers
	   need it that away) and we're done. */
	s = base64_encode(hash, SHA1_HASH_SIZE);
	s = g_realloc(s, strlen(s) * 3 + 1);
	http_encode(s);

	return s;
}

static char *oauth_nonce()
{
	unsigned char bytes[21];

	random_bytes(bytes, sizeof(bytes));
	return base64_encode(bytes, sizeof(bytes));
}

void oauth_params_add(GSList **params, const char *key, const char *value)
{
	char *item;

	if (!key || !value) {
		return;
	}

	item = g_strdup_printf("%s=%s", key, value);
	*params = g_slist_insert_sorted(*params, item, (GCompareFunc) strcmp);
}

void oauth_params_del(GSList **params, const char *key)
{
	int key_len = strlen(key);
	GSList *l;

	if (!params) {
		return;
	}

	for (l = *params; l; l = l->next) {
		char *data = l->data;

		if (strncmp(data, key, key_len) == 0 && data[key_len] == '=') {
			*params = g_slist_remove(*params, data);
			g_free(data);
		}
	}
}

void oauth_params_set(GSList **params, const char *key, const char *value)
{
	oauth_params_del(params, key);
	oauth_params_add(params, key, value);
}

const char *oauth_params_get(GSList **params, const char *key)
{
	int key_len = strlen(key);
	GSList *l;

	if (params == NULL) {
		return NULL;
	}

	for (l = *params; l; l = l->next) {
		if (strncmp((char *) l->data, key, key_len) == 0 &&
		    ((char *) l->data)[key_len] == '=') {
			return (const char *) l->data + key_len + 1;
		}
	}

	return NULL;
}

void oauth_params_parse(GSList **params, char *in)
{
	char *amp, *eq, *s;

	while (in && *in) {
		eq = strchr(in, '=');
		if (!eq) {
			break;
		}

		*eq = '\0';
		if ((amp = strchr(eq + 1, '&'))) {
			*amp = '\0';
		}

		s = g_strdup(eq + 1);
		http_decode(s);
		oauth_params_add(params, in, s);
		g_free(s);

		*eq = '=';
		if (amp == NULL) {
			break;
		}

		*amp = '&';
		in = amp + 1;
	}
}

void oauth_params_free(GSList **params)
{
	while (params && *params) {
		g_free((*params)->data);
		*params = g_slist_remove(*params, (*params)->data);
	}
}

char *oauth_params_string(GSList *params)
{
	GSList *l;
	GString *str = g_string_new("");

	for (l = params; l; l = l->next) {
		char *s, *eq;

		s = g_malloc(strlen(l->data) * 3 + 1);
		strcpy(s, l->data);
		if ((eq = strchr(s, '='))) {
			http_encode(eq + 1);
		}
		g_string_append(str, s);
		g_free(s);

		if (l->next) {
			g_string_append_c(str, '&');
		}
	}

	return g_string_free(str, FALSE);
}

void oauth_info_free(struct oauth_info *info)
{
	if (info) {
		g_free(info->auth_url);
		g_free(info->request_token);
		g_free(info->token);
		g_free(info->token_secret);
		oauth_params_free(&info->params);
		g_free(info);
	}
}

static void oauth_add_default_params(GSList **params, const struct oauth_service *sp)
{
	char *s;

	oauth_params_set(params, "oauth_consumer_key", sp->consumer_key);
	oauth_params_set(params, "oauth_signature_method", "HMAC-SHA1");

	s = g_strdup_printf("%d", (int) time(NULL));
	oauth_params_set(params, "oauth_timestamp", s);
	g_free(s);

	s = oauth_nonce();
	oauth_params_set(params, "oauth_nonce", s);
	g_free(s);

	oauth_params_set(params, "oauth_version", "1.0");
}

static void *oauth_post_request(const char *url, GSList **params_, http_input_function func, struct oauth_info *oi)
{
	GSList *params = NULL;
	char *s, *params_s, *post;
	void *req;
	url_t url_p;

	if (!url_set(&url_p, url)) {
		oauth_params_free(params_);
		return NULL;
	}

	if (params_) {
		params = *params_;
	}

	oauth_add_default_params(&params, oi->sp);

	params_s = oauth_params_string(params);
	oauth_params_free(&params);

	s = oauth_sign("POST", url, params_s, oi);
	post = g_strdup_printf("%s&oauth_signature=%s", params_s, s);
	g_free(params_s);
	g_free(s);

	s = g_strdup_printf("POST %s HTTP/1.0\r\n"
	                    "Host: %s\r\n"
	                    "Content-Type: application/x-www-form-urlencoded\r\n"
	                    "Content-Length: %zd\r\n"
	                    "\r\n"
	                    "%s", url_p.file, url_p.host, strlen(post), post);
	g_free(post);

	req = http_dorequest(url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
	                     s, func, oi);
	g_free(s);

	return req;
}

static void oauth_request_token_done(struct http_request *req);

struct oauth_info *oauth_request_token(const struct oauth_service *sp, oauth_cb func, void *data)
{
	struct oauth_info *st = g_new0(struct oauth_info, 1);
	GSList *params = NULL;

	st->func = func;
	st->data = data;
	st->sp = sp;

	oauth_params_add(&params, "oauth_callback", "oob");

	if (!oauth_post_request(sp->url_request_token, &params, oauth_request_token_done, st)) {
		oauth_info_free(st);
		return NULL;
	}

	return st;
}

static void oauth_request_token_done(struct http_request *req)
{
	struct oauth_info *st = req->data;

	st->http = req;

	if (req->status_code == 200) {
		GSList *params = NULL;

		st->auth_url = g_strdup_printf("%s?%s", st->sp->url_authorize, req->reply_body);
		oauth_params_parse(&params, req->reply_body);
		st->request_token = g_strdup(oauth_params_get(&params, "oauth_token"));
		st->token_secret = g_strdup(oauth_params_get(&params, "oauth_token_secret"));
		oauth_params_free(&params);
	}

	st->stage = OAUTH_REQUEST_TOKEN;
	st->func(st);
}

static void oauth_access_token_done(struct http_request *req);

gboolean oauth_access_token(const char *pin, struct oauth_info *st)
{
	GSList *params = NULL;

	oauth_params_add(&params, "oauth_token", st->request_token);
	oauth_params_add(&params, "oauth_verifier", pin);

	return oauth_post_request(st->sp->url_access_token, &params, oauth_access_token_done, st) != NULL;
}

static void oauth_access_token_done(struct http_request *req)
{
	struct oauth_info *st = req->data;

	st->http = req;

	if (req->status_code == 200) {
		oauth_params_parse(&st->params, req->reply_body);
		st->token = g_strdup(oauth_params_get(&st->params, "oauth_token"));
		g_free(st->token_secret);
		st->token_secret = g_strdup(oauth_params_get(&st->params, "oauth_token_secret"));
	}

	st->stage = OAUTH_ACCESS_TOKEN;
	if (st->func(st)) {
		/* Don't need these anymore, but keep the rest. */
		g_free(st->auth_url);
		st->auth_url = NULL;
		g_free(st->request_token);
		st->request_token = NULL;
		oauth_params_free(&st->params);
	}
}

char *oauth_http_header(struct oauth_info *oi, const char *method, const char *url, char *args)
{
	GSList *params = NULL, *l;
	char *sig = NULL, *params_s, *s;
	GString *ret = NULL;

	oauth_params_add(&params, "oauth_token", oi->token);
	oauth_add_default_params(&params, oi->sp);

	/* Start building the OAuth header. 'key="value", '... */
	ret = g_string_new("OAuth ");
	for (l = params; l; l = l->next) {
		char *kv = l->data;
		char *eq = strchr(kv, '=');
		char esc[strlen(kv) * 3 + 1];

		if (eq == NULL) {
			break; /* WTF */

		}
		strcpy(esc, eq + 1);
		http_encode(esc);

		g_string_append_len(ret, kv, eq - kv + 1);
		g_string_append_c(ret, '"');
		g_string_append(ret, esc);
		g_string_append(ret, "\", ");
	}

	/* Now, before generating the signature, add GET/POST arguments to params
	   since they should be included in the base signature string (but not in
	   the HTTP header). */
	if (args) {
		oauth_params_parse(&params, args);
	}
	if ((s = strchr(url, '?'))) {
		s = g_strdup(s + 1);
		oauth_params_parse(&params, s);
		g_free(s);
	}

	/* Append the signature and we're done! */
	params_s = oauth_params_string(params);
	sig = oauth_sign(method, url, params_s, oi);
	g_string_append_printf(ret, "oauth_signature=\"%s\"", sig);
	g_free(params_s);

	oauth_params_free(&params);
	g_free(sig);

	return ret ? g_string_free(ret, FALSE) : NULL;
}

char *oauth_to_string(struct oauth_info *oi)
{
	GSList *params = NULL;
	char *ret;

	oauth_params_add(&params, "oauth_token", oi->token);
	oauth_params_add(&params, "oauth_token_secret", oi->token_secret);
	ret = oauth_params_string(params);
	oauth_params_free(&params);

	return ret;
}

struct oauth_info *oauth_from_string(char *in, const struct oauth_service *sp)
{
	struct oauth_info *oi = g_new0(struct oauth_info, 1);
	GSList *params = NULL;

	oauth_params_parse(&params, in);
	oi->token = g_strdup(oauth_params_get(&params, "oauth_token"));
	oi->token_secret = g_strdup(oauth_params_get(&params, "oauth_token_secret"));
	oauth_params_free(&params);
	oi->sp = sp;

	return oi;
}
> global.conf->runmode = oldmode; } static void ipc_child_cmd_kill( irc_t *irc, char **cmd ) { if( !( irc->status & USTATUS_LOGGED_IN ) ) return; if( nick_cmp( NULL, cmd[1], irc->user->nick ) != 0 ) return; /* It's not for us. */ irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2] ); irc_abort( irc, 0, "Killed by operator: %s", cmd[2] ); } static void ipc_child_cmd_hello( irc_t *irc, char **cmd ) { if( !( irc->status & USTATUS_LOGGED_IN ) ) ipc_to_master_str( "HELLO\r\n" ); else ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname ); } static void ipc_child_cmd_takeover_yes( void *data ); static void ipc_child_cmd_takeover_no( void *data ); static void ipc_child_cmd_takeover( irc_t *irc, char **cmd ) { if( strcmp( cmd[1], "NO" ) == 0 ) { /* Master->New connection */ /* No takeover, finish the login. */ } else if( strcmp( cmd[1], "INIT" ) == 0 ) { /* Master->New connection */ if( !set_getbool( &irc->b->set, "allow_takeover" ) ) { ipc_child_cmd_takeover_no( irc ); return; } /* Offer to take over the old session, unless for some reason we're already logging into IM connections. */ if( irc->login_source_id != -1 ) query_add( irc, NULL, "You're already connected to this server. " "Would you like to take over this session?", ipc_child_cmd_takeover_yes, ipc_child_cmd_takeover_no, NULL, irc ); /* This one's going to connect to accounts, avoid that. */ b_event_remove( irc->login_source_id ); irc->login_source_id = -1; } else if( strcmp( cmd[1], "AUTH" ) == 0 ) { /* Master->Old connection */ if( irc->password && cmd[2] && cmd[3] && ipc_child_recv_fd != -1 && strcmp( irc->user->nick, cmd[2] ) == 0 && strcmp( irc->password, cmd[3] ) == 0 && set_getbool( &irc->b->set, "allow_takeover" ) ) { irc_switch_fd( irc, ipc_child_recv_fd ); irc_sync( irc ); irc_rootmsg( irc, "You've successfully taken over your old session" ); ipc_child_recv_fd = -1; ipc_to_master_str( "TAKEOVER DONE\r\n" ); } else { ipc_to_master_str( "TAKEOVER FAIL\r\n" ); } } else if( strcmp( cmd[1], "DONE" ) == 0 ) { /* Master->New connection (now taken over by old process) */ irc_free( irc ); } else if( strcmp( cmd[1], "FAIL" ) == 0 ) { /* Master->New connection */ irc_rootmsg( irc, "Could not take over old session" ); } } static void ipc_child_cmd_takeover_yes( void *data ) { irc_t *irc = data, *old = NULL; char *to_auth[] = { "TAKEOVER", "AUTH", irc->user->nick, irc->password, NULL }; /* Master->New connection */ ipc_to_master_str( "TAKEOVER AUTH %s :%s\r\n", irc->user->nick, irc->password ); if( global.conf->runmode == RUNMODE_DAEMON ) { GSList *l; for( l = irc_connection_list; l; l = l->next ) { old = l->data; if( irc != old && irc->user->nick && old->user->nick && irc->password && old->password && strcmp( irc->user->nick, old->user->nick ) == 0 && strcmp( irc->password, old->password ) == 0 ) break; } if( l == NULL ) { to_auth[1] = "FAIL"; ipc_child_cmd_takeover( irc, to_auth ); return; } } /* Drop credentials, we'll shut down soon and shouldn't overwrite any settings. */ irc_rootmsg( irc, "Trying to take over existing session" ); irc_desync( irc ); if( old ) { ipc_child_recv_fd = dup( irc->fd ); ipc_child_cmd_takeover( old, to_auth ); } /* TODO: irc_setpass() should do all of this. */ irc_setpass( irc, NULL ); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set( irc, "-R", 1 ); if( old ) irc_abort( irc, FALSE, NULL ); } static void ipc_child_cmd_takeover_no( void *data ) { ipc_to_master_str( "TAKEOVER NO\r\n" ); cmd_identify_finish( data, 0, 0 ); } static const command_t ipc_child_commands[] = { { "die", 0, ipc_child_cmd_die, 0 }, { "wallops", 1, ipc_child_cmd_wallops, 0 }, { "wall", 1, ipc_child_cmd_wall, 0 }, { "opermsg", 1, ipc_child_cmd_opermsg, 0 }, { "rehash", 0, ipc_child_cmd_rehash, 0 }, { "kill", 2, ipc_child_cmd_kill, 0 }, { "hello", 0, ipc_child_cmd_hello, 0 }, { "takeover", 1, ipc_child_cmd_takeover, 0 }, { NULL } }; gboolean ipc_child_identify( irc_t *irc ) { if( global.conf->runmode == RUNMODE_FORKDAEMON ) { #ifndef NO_FD_PASSING if( !ipc_send_fd( global.listen_socket, irc->fd ) ) ipc_child_disable(); ipc_to_master_str( "IDENTIFY %s :%s\r\n", irc->user->nick, irc->password ); #endif return TRUE; } else if( global.conf->runmode == RUNMODE_DAEMON ) { GSList *l; irc_t *old; char *to_init[] = { "TAKEOVER", "INIT", NULL }; for( l = irc_connection_list; l; l = l->next ) { old = l->data; if( irc != old && irc->user->nick && old->user->nick && irc->password && old->password && strcmp( irc->user->nick, old->user->nick ) == 0 && strcmp( irc->password, old->password ) == 0 ) break; } if( l == NULL || !set_getbool( &irc->b->set, "allow_takeover" ) || !set_getbool( &old->b->set, "allow_takeover" ) ) return FALSE; ipc_child_cmd_takeover( irc, to_init ); return TRUE; } else return FALSE; } static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both ) { if( child == NULL || g_slist_find( child_list, child ) == NULL ) return; if( both && child->to_child != NULL ) ipc_master_takeover_fail( child->to_child, FALSE ); if( child->to_fd > -1 ) { /* Send this error only to the new connection, which can be recognised by to_fd being set. */ if( write( child->ipc_fd, "TAKEOVER FAIL\r\n", 15 ) != 15 ) { ipc_master_free_one( child ); return; } close( child->to_fd ); child->to_fd = -1; } child->to_child = NULL; } static void ipc_command_exec( void *data, char **cmd, const command_t *commands ) { int i, j; if( !cmd[0] ) return; for( i = 0; commands[i].command; i ++ ) if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 ) { /* There is no typo in this line: */ for( j = 1; cmd[j]; j ++ ); j --; if( j < commands[i].required_parameters ) break; if( commands[i].flags & IPC_CMD_TO_CHILDREN ) ipc_to_children( cmd ); else commands[i].execute( data, cmd ); break; } } /* Return just one line. Returns NULL if something broke, an empty string on temporary "errors" (EAGAIN and friends). */ static char *ipc_readline( int fd, int *recv_fd ) { struct msghdr msg; struct iovec iov; char ccmsg[CMSG_SPACE(sizeof(recv_fd))]; struct cmsghdr *cmsg; char buf[513], *eol; int size; /* Because this is internal communication, it should be pretty safe to just peek at the message, find its length (by searching for the end-of-line) and then just read that message. With internal sockets and limites message length, messages should always be complete. Saves us quite a lot of code and buffering. */ size = recv( fd, buf, sizeof( buf ) - 1, MSG_PEEK ); if( size == 0 || ( size < 0 && !sockerr_again() ) ) return NULL; else if( size < 0 ) /* && sockerr_again() */ return( g_strdup( "" ) ); else buf[size] = 0; if( ( eol = strstr( buf, "\r\n" ) ) == NULL ) return NULL; else size = eol - buf + 2; iov.iov_base = buf; iov.iov_len = size; memset( &msg, 0, sizeof( msg ) ); msg.msg_iov = &iov; msg.msg_iovlen = 1; #ifndef NO_FD_PASSING msg.msg_control = ccmsg; msg.msg_controllen = sizeof( ccmsg ); #endif if( recvmsg( fd, &msg, 0 ) != size ) return NULL; #ifndef NO_FD_PASSING if( recv_fd ) for( cmsg = CMSG_FIRSTHDR( &msg ); cmsg; cmsg = CMSG_NXTHDR( &msg, cmsg ) ) if( cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS ) { /* Getting more than one shouldn't happen but if it does, make sure we don't leave them around. */ if( *recv_fd != -1 ) close( *recv_fd ); *recv_fd = *(int*) CMSG_DATA( cmsg ); /* fprintf( stderr, "pid %d received fd %d\n", (int) getpid(), *recv_fd ); */ } #endif /* fprintf( stderr, "pid %d received: %s", (int) getpid(), buf ); */ return g_strndup( buf, size - 2 ); } gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond ) { struct bitlbee_child *child = data; char *buf, **cmd; if( ( buf = ipc_readline( source, &child->to_fd ) ) ) { cmd = irc_parse_line( buf ); if( cmd ) { ipc_command_exec( child, cmd, ipc_master_commands ); g_free( cmd ); } g_free( buf ); } else { ipc_master_free_fd( source ); } return TRUE; } gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond ) { char *buf, **cmd; if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) ) { cmd = irc_parse_line( buf ); if( cmd ) { ipc_command_exec( data, cmd, ipc_child_commands ); g_free( cmd ); } g_free( buf ); } else { ipc_child_disable(); } return TRUE; } void ipc_to_master( char **cmd ) { if( global.conf->runmode == RUNMODE_FORKDAEMON ) { char *s = irc_build_line( cmd ); ipc_to_master_str( "%s", s ); g_free( s ); } else if( global.conf->runmode == RUNMODE_DAEMON ) { ipc_command_exec( NULL, cmd, ipc_master_commands ); } } void ipc_to_master_str( char *format, ... ) { char *msg_buf; va_list params; va_start( params, format ); msg_buf = g_strdup_vprintf( format, params ); va_end( params ); if( strlen( msg_buf ) > 512 ) { /* Don't send it, it's too long... */ } else if( global.conf->runmode == RUNMODE_FORKDAEMON ) { if( global.listen_socket >= 0 ) if( write( global.listen_socket, msg_buf, strlen( msg_buf ) ) <= 0 ) ipc_child_disable(); } else if( global.conf->runmode == RUNMODE_DAEMON ) { char **cmd, *s; if( ( s = strchr( msg_buf, '\r' ) ) ) *s = 0; cmd = irc_parse_line( msg_buf ); ipc_command_exec( NULL, cmd, ipc_master_commands ); g_free( cmd ); } g_free( msg_buf ); } void ipc_to_children( char **cmd ) { if( global.conf->runmode == RUNMODE_FORKDAEMON ) { char *msg_buf = irc_build_line( cmd ); ipc_to_children_str( "%s", msg_buf ); g_free( msg_buf ); } else if( global.conf->runmode == RUNMODE_DAEMON ) { GSList *l; for( l = irc_connection_list; l; l = l->next ) ipc_command_exec( l->data, cmd, ipc_child_commands ); } } void ipc_to_children_str( char *format, ... ) { char *msg_buf; va_list params; va_start( params, format ); msg_buf = g_strdup_vprintf( format, params ); va_end( params ); if( strlen( msg_buf ) > 512 ) { /* Don't send it, it's too long... */ } else if( global.conf->runmode == RUNMODE_FORKDAEMON ) { int msg_len = strlen( msg_buf ); GSList *l, *next; for( l = child_list; l; l = next ) { struct bitlbee_child *c = l->data; next = l->next; if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 ) ipc_master_free_one( c ); } } else if( global.conf->runmode == RUNMODE_DAEMON ) { char **cmd, *s; if( ( s = strchr( msg_buf, '\r' ) ) ) *s = 0; cmd = irc_parse_line( msg_buf ); ipc_to_children( cmd ); g_free( cmd ); } g_free( msg_buf ); } static gboolean ipc_send_fd( int fd, int send_fd ) { struct msghdr msg; struct iovec iov; char ccmsg[CMSG_SPACE(sizeof(fd))]; struct cmsghdr *cmsg; memset( &msg, 0, sizeof( msg ) ); iov.iov_base = "0x90\r\n"; /* Ja, noppes */ iov.iov_len = 6; msg.msg_iov = &iov; msg.msg_iovlen = 1; #ifndef NO_FD_PASSING msg.msg_control = ccmsg; msg.msg_controllen = sizeof( ccmsg ); cmsg = CMSG_FIRSTHDR( &msg ); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN( sizeof( send_fd ) ); *(int*)CMSG_DATA( cmsg ) = send_fd; msg.msg_controllen = cmsg->cmsg_len; #endif return sendmsg( fd, &msg, 0 ) == 6; } void ipc_master_free_one( struct bitlbee_child *c ) { GSList *l; b_event_remove( c->ipc_inpa ); closesocket( c->ipc_fd ); if( c->to_fd != -1 ) close( c->to_fd ); g_free( c->host ); g_free( c->nick ); g_free( c->realname ); g_free( c->password ); g_free( c ); child_list = g_slist_remove( child_list, c ); /* Also, if any child has a reference to this one, remove it. */ for( l = child_list; l; l = l->next ) { struct bitlbee_child *oc = l->data; if( oc->to_child == c ) ipc_master_takeover_fail( oc, FALSE ); } } void ipc_master_free_fd( int fd ) { GSList *l; struct bitlbee_child *c; for( l = child_list; l; l = l->next ) { c = l->data; if( c->ipc_fd == fd ) { ipc_master_free_one( c ); break; } } } void ipc_master_free_all() { while( child_list ) ipc_master_free_one( child_list->data ); } void ipc_child_disable() { b_event_remove( global.listen_watch_source_id ); close( global.listen_socket ); global.listen_socket = -1; } #ifndef _WIN32 char *ipc_master_save_state() { char *fn = g_strdup( "/tmp/bee-restart.XXXXXX" ); int fd = mkstemp( fn ); GSList *l; FILE *fp; int i; if( fd == -1 ) { log_message( LOGLVL_ERROR, "Could not create temporary file: %s", strerror( errno ) ); g_free( fn ); return NULL; } /* This is more convenient now. */ fp = fdopen( fd, "w" ); for( l = child_list, i = 0; l; l = l->next ) i ++; /* Number of client processes. */ fprintf( fp, "%d\n", i ); for( l = child_list; l; l = l->next ) fprintf( fp, "%d %d\n", (int) ((struct bitlbee_child*)l->data)->pid, ((struct bitlbee_child*)l->data)->ipc_fd ); if( fclose( fp ) == 0 ) { return fn; } else { unlink( fn ); g_free( fn ); return NULL; } } static gboolean new_ipc_client( gpointer data, gint serversock, b_input_condition cond ) { struct bitlbee_child *child = g_new0( struct bitlbee_child, 1 ); child->to_fd = -1; child->ipc_fd = accept( serversock, NULL, 0 ); if( child->ipc_fd == -1 ) { log_message( LOGLVL_WARNING, "Unable to accept connection on UNIX domain socket: %s", strerror(errno) ); return TRUE; } child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); child_list = g_slist_prepend( child_list, child ); return TRUE; } int ipc_master_listen_socket() { struct sockaddr_un un_addr; int serversock; if (!IPCSOCKET || !*IPCSOCKET) return 1; /* Clean up old socket files that were hanging around.. */ if (unlink(IPCSOCKET) == -1 && errno != ENOENT) { log_message( LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno) ); return 0; } un_addr.sun_family = AF_UNIX; strcpy(un_addr.sun_path, IPCSOCKET); serversock = socket(AF_UNIX, SOCK_STREAM, PF_UNIX); if (serversock == -1) { log_message( LOGLVL_WARNING, "Unable to create UNIX socket: %s", strerror(errno) ); return 0; } if (bind(serversock, (struct sockaddr *)&un_addr, sizeof(un_addr)) == -1) { log_message( LOGLVL_WARNING, "Unable to bind UNIX socket to %s: %s", IPCSOCKET, strerror(errno) ); return 0; } if (listen(serversock, 5) == -1) { log_message( LOGLVL_WARNING, "Unable to listen on UNIX socket: %s", strerror(errno) ); return 0; } b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL ); return 1; } #else int ipc_master_listen_socket() { /* FIXME: Open named pipe \\.\BITLBEE */ return 0; } #endif int ipc_master_load_state( char *statefile ) { struct bitlbee_child *child; FILE *fp; int i, n; if( statefile == NULL ) return 0; fp = fopen( statefile, "r" ); unlink( statefile ); /* Why do it later? :-) */ if( fp == NULL ) return 0; if( fscanf( fp, "%d", &n ) != 1 ) { log_message( LOGLVL_WARNING, "Could not import state information for child processes." ); fclose( fp ); return 0; } log_message( LOGLVL_INFO, "Importing information for %d child processes.", n ); for( i = 0; i < n; i ++ ) { child = g_new0( struct bitlbee_child, 1 ); if( fscanf( fp, "%d %d", (int *) &child->pid, &child->ipc_fd ) != 2 ) { log_message( LOGLVL_WARNING, "Unexpected end of file: Only processed %d clients.", i ); g_free( child ); fclose( fp ); return 0; } child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); child->to_fd = -1; child_list = g_slist_prepend( child_list, child ); } ipc_to_children_str( "HELLO\r\n" ); ipc_to_children_str( "OPERMSG :New %s master process started (version %s)\r\n", PACKAGE, BITLBEE_VERSION ); fclose( fp ); return 1; }