1 /***************************************************************************\
2 *                                                                           *
3 *  BitlBee - An IRC to IM gateway                                           *
4 *  libpurple module - File transfer stuff                                   *
5 *                                                                           *
6 *  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
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 /* Do file transfers via disk for now, since libpurple was really designed
25    for straight-to/from disk fts and is only just learning how to pass the
26    file contents the the UI instead (2.6.0 and higher it seems, and with
27    varying levels of success). */
28 
29 #include "bitlbee.h"
30 #include "bpurple.h"
31 
32 #include <stdarg.h>
33 
34 #include <glib.h>
35 #include <purple.h>
36 
37 struct prpl_xfer_data {
38 	PurpleXfer *xfer;
39 	file_transfer_t *ft;
40 	struct im_connection *ic;
41 	int fd;
42 	char *fn, *handle;
43 	gboolean ui_wants_data;
44 	int timeout;
45 };
46 
47 static file_transfer_t *next_ft;
48 
49 struct im_connection *purple_ic_by_pa(PurpleAccount *pa);
50 static gboolean prplcb_xfer_new_send_cb(gpointer data, gint fd, b_input_condition cond);
51 static gboolean prpl_xfer_write_request(struct file_transfer *ft);
52 
53 
54 /* Receiving files (IM->UI): */
prpl_xfer_accept(struct file_transfer * ft)55 static void prpl_xfer_accept(struct file_transfer *ft)
56 {
57 	struct prpl_xfer_data *px = ft->data;
58 
59 	purple_xfer_request_accepted(px->xfer, NULL);
60 	prpl_xfer_write_request(ft);
61 }
62 
prpl_xfer_canceled(struct file_transfer * ft,char * reason)63 static void prpl_xfer_canceled(struct file_transfer *ft, char *reason)
64 {
65 	struct prpl_xfer_data *px = ft->data;
66 
67 	if (px->xfer) {
68 		if (!purple_xfer_is_completed(px->xfer) && !purple_xfer_is_canceled(px->xfer)) {
69 			purple_xfer_cancel_local(px->xfer);
70 		}
71 		px->xfer->ui_data = NULL;
72 		purple_xfer_unref(px->xfer);
73 		px->xfer = NULL;
74 	}
75 }
76 
prpl_xfer_free(struct file_transfer * ft)77 static void prpl_xfer_free(struct file_transfer *ft)
78 {
79 	struct prpl_xfer_data *px = ft->data;
80 	struct purple_data *pd = px->ic->proto_data;
81 
82 	pd->filetransfers = g_slist_remove(pd->filetransfers, px);
83 
84 	if (px->xfer) {
85 		px->xfer->ui_data = NULL;
86 		purple_xfer_unref(px->xfer);
87 	}
88 
89 	if (px->timeout) {
90 		b_event_remove(px->timeout);
91 	}
92 
93 	g_free(px->fn);
94 	g_free(px->handle);
95 	if (px->fd >= 0) {
96 		close(px->fd);
97 	}
98 	g_free(px);
99 }
100 
prplcb_xfer_new(PurpleXfer * xfer)101 static void prplcb_xfer_new(PurpleXfer *xfer)
102 {
103 	purple_xfer_ref(xfer);
104 
105 	if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) {
106 		struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
107 		struct purple_data *pd;
108 
109 		xfer->ui_data = px;
110 		px->xfer = xfer;
111 		px->fn = mktemp(g_strdup("/tmp/bitlbee-purple-ft.XXXXXX"));
112 		px->fd = -1;
113 		px->ic = purple_ic_by_pa(xfer->account);
114 
115 		pd = px->ic->proto_data;
116 		pd->filetransfers = g_slist_prepend(pd->filetransfers, px);
117 
118 		purple_xfer_set_local_filename(xfer, px->fn);
119 
120 		/* Sadly the xfer struct is still empty ATM so come back after
121 		   the caller is done. */
122 		b_timeout_add(0, prplcb_xfer_new_send_cb, xfer);
123 	} else {
124 		struct file_transfer *ft = next_ft;
125 		struct prpl_xfer_data *px = ft->data;
126 
127 		xfer->ui_data = px;
128 		px->xfer = xfer;
129 
130 		next_ft = NULL;
131 	}
132 }
133 
prplcb_xfer_new_send_cb(gpointer data,gint fd,b_input_condition cond)134 static gboolean prplcb_xfer_new_send_cb(gpointer data, gint fd, b_input_condition cond)
135 {
136 	PurpleXfer *xfer = data;
137 	struct im_connection *ic = purple_ic_by_pa(xfer->account);
138 	struct prpl_xfer_data *px = xfer->ui_data;
139 	PurpleBuddy *buddy;
140 	const char *who;
141 
142 	buddy = purple_find_buddy(xfer->account, xfer->who);
143 	who = buddy ? purple_buddy_get_name(buddy) : xfer->who;
144 
145 	/* TODO(wilmer): After spreading some more const goodness in BitlBee,
146 	   remove the evil cast below. */
147 	px->ft = imcb_file_send_start(ic, (char *) who, xfer->filename, xfer->size);
148 
149 	if (!px->ft) {
150 		return FALSE;
151 	}
152 	px->ft->data = px;
153 
154 	px->ft->accept = prpl_xfer_accept;
155 	px->ft->canceled = prpl_xfer_canceled;
156 	px->ft->free = prpl_xfer_free;
157 	px->ft->write_request = prpl_xfer_write_request;
158 
159 	return FALSE;
160 }
161 
try_write_to_ui(gpointer data,gint fd,b_input_condition cond)162 gboolean try_write_to_ui(gpointer data, gint fd, b_input_condition cond)
163 {
164 	struct file_transfer *ft = data;
165 	struct prpl_xfer_data *px = ft->data;
166 	struct stat fs;
167 	off_t tx_bytes;
168 
169 	/* If we don't have the file opened yet, there's no data so wait. */
170 	if (px->fd < 0 || !px->ui_wants_data) {
171 		return FALSE;
172 	}
173 
174 	tx_bytes = lseek(px->fd, 0, SEEK_CUR);
175 	fstat(px->fd, &fs);
176 
177 	if (fs.st_size > tx_bytes) {
178 		char buf[1024];
179 		size_t n = MIN(fs.st_size - tx_bytes, sizeof(buf));
180 
181 		if (read(px->fd, buf, n) == n && ft->write(ft, buf, n)) {
182 			px->ui_wants_data = FALSE;
183 		} else {
184 			purple_xfer_cancel_local(px->xfer);
185 			imcb_file_canceled(px->ic, ft, "Read error");
186 		}
187 	}
188 
189 	if (lseek(px->fd, 0, SEEK_CUR) == px->xfer->size) {
190 		/*purple_xfer_end( px->xfer );*/
191 		imcb_file_finished(px->ic, ft);
192 	}
193 
194 	return FALSE;
195 }
196 
197 /* UI calls this when its buffer is empty and wants more data to send to the user. */
prpl_xfer_write_request(struct file_transfer * ft)198 static gboolean prpl_xfer_write_request(struct file_transfer *ft)
199 {
200 	struct prpl_xfer_data *px = ft->data;
201 
202 	px->ui_wants_data = TRUE;
203 	try_write_to_ui(ft, 0, 0);
204 
205 	return FALSE;
206 }
207 
208 
prplcb_xfer_destroy(PurpleXfer * xfer)209 static void prplcb_xfer_destroy(PurpleXfer *xfer)
210 {
211 	struct prpl_xfer_data *px = xfer->ui_data;
212 
213 	if (px) {
214 		px->xfer = NULL;
215 	}
216 }
217 
prplcb_xfer_progress(PurpleXfer * xfer,double percent)218 static void prplcb_xfer_progress(PurpleXfer *xfer, double percent)
219 {
220 	struct prpl_xfer_data *px = xfer->ui_data;
221 
222 	if (px == NULL) {
223 		return;
224 	}
225 
226 	if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
227 		if (*px->fn) {
228 			char *slash;
229 
230 			unlink(px->fn);
231 			if ((slash = strrchr(px->fn, '/'))) {
232 				*slash = '\0';
233 				rmdir(px->fn);
234 			}
235 			*px->fn = '\0';
236 		}
237 
238 		return;
239 	}
240 
241 	if (px->fd == -1 && percent > 0) {
242 		/* Weeeeeeeee, we're getting data! That means the file exists
243 		   by now so open it and start sending to the UI. */
244 		px->fd = open(px->fn, O_RDONLY);
245 
246 		/* Unlink it now, because we don't need it after this. */
247 		unlink(px->fn);
248 	}
249 
250 	if (percent < 1) {
251 		try_write_to_ui(px->ft, 0, 0);
252 	} else {
253 		/* Another nice problem: If we have the whole file, it only
254 		   gets closed when we return. Problem: There may still be
255 		   stuff buffered and not written, we'll only see it after
256 		   the caller close()s the file. So poll the file after that. */
257 		b_timeout_add(0, try_write_to_ui, px->ft);
258 	}
259 }
260 
prplcb_xfer_cancel_remote(PurpleXfer * xfer)261 static void prplcb_xfer_cancel_remote(PurpleXfer *xfer)
262 {
263 	struct prpl_xfer_data *px = xfer->ui_data;
264 
265 	if (px && px->ft) {
266 		imcb_file_canceled(px->ic, px->ft, "Canceled by remote end");
267 	} else if (px) {
268 		/* px->ft == NULL for sends, because of the two stages. :-/ */
269 		imcb_error(px->ic, "File transfer cancelled by remote end");
270 	}
271 }
272 
273 
274 /* Sending files (UI->IM): */
275 static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len);
276 static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond);
277 
purple_transfer_request(struct im_connection * ic,file_transfer_t * ft,char * handle)278 void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle)
279 {
280 	struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
281 	struct purple_data *pd;
282 	char *dir, *basename;
283 
284 	ft->data = px;
285 	px->ft = ft;
286 	px->ft->free = prpl_xfer_free;
287 
288 	dir = g_strdup("/tmp/bitlbee-purple-ft.XXXXXX");
289 	if (!mkdtemp(dir)) {
290 		imcb_error(ic, "Could not create temporary file for file transfer");
291 		g_free(px);
292 		g_free(dir);
293 		return;
294 	}
295 
296 	if ((basename = strrchr(ft->file_name, '/'))) {
297 		basename++;
298 	} else {
299 		basename = ft->file_name;
300 	}
301 	px->fn = g_strdup_printf("%s/%s", dir, basename);
302 	px->fd = open(px->fn, O_WRONLY | O_CREAT, 0600);
303 	g_free(dir);
304 
305 	if (px->fd < 0) {
306 		imcb_error(ic, "Could not create temporary file for file transfer");
307 		g_free(px);
308 		g_free(px->fn);
309 		return;
310 	}
311 
312 	px->ic = ic;
313 	px->handle = g_strdup(handle);
314 
315 	pd = px->ic->proto_data;
316 	pd->filetransfers = g_slist_prepend(pd->filetransfers, px);
317 
318 	imcb_log(ic,
319 	         "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait...");
320 
321 	px->timeout = b_timeout_add(0, purple_transfer_request_cb, ft);
322 }
323 
purple_transfer_forward(struct file_transfer * ft)324 static void purple_transfer_forward(struct file_transfer *ft)
325 {
326 	struct prpl_xfer_data *px = ft->data;
327 	struct purple_data *pd = px->ic->proto_data;
328 
329 	/* xfer_new() will pick up this variable. It's a hack but we're not
330 	   multi-threaded anyway. */
331 	next_ft = ft;
332 	serv_send_file(purple_account_get_connection(pd->account),
333                    px->handle, px->fn);
334 }
335 
purple_transfer_request_cb(gpointer data,gint fd,b_input_condition cond)336 static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond)
337 {
338 	file_transfer_t *ft = data;
339 	struct prpl_xfer_data *px = ft->data;
340 
341 	px->timeout = 0;
342 
343 	if (ft->write == NULL) {
344 		ft->write = prpl_xfer_write;
345 		imcb_file_recv_start(px->ic, ft);
346 	}
347 
348 	ft->write_request(ft);
349 
350 	return FALSE;
351 }
352 
prpl_xfer_write(struct file_transfer * ft,char * buffer,unsigned int len)353 static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len)
354 {
355 	struct prpl_xfer_data *px = ft->data;
356 
357 	if (write(px->fd, buffer, len) != len) {
358 		imcb_file_canceled(px->ic, ft, "Error while writing temporary file");
359 		return FALSE;
360 	}
361 
362 	if (lseek(px->fd, 0, SEEK_CUR) >= ft->file_size) {
363 		close(px->fd);
364 		px->fd = -1;
365 
366 		purple_transfer_forward(ft);
367 		imcb_file_finished(px->ic, ft);
368 		px->ft = NULL;
369 	} else {
370 		px->timeout = b_timeout_add(0, purple_transfer_request_cb, ft);
371 	}
372 
373 	return TRUE;
374 }
375 
purple_transfer_cancel_all(struct im_connection * ic)376 void purple_transfer_cancel_all(struct im_connection *ic)
377 {
378 	struct purple_data *pd = ic->proto_data;
379 
380 	while (pd->filetransfers) {
381 		struct prpl_xfer_data *px = pd->filetransfers->data;
382 
383 		if (px->ft) {
384 			imcb_file_canceled(ic, px->ft, "Logging out");
385 		}
386 
387 		pd->filetransfers = g_slist_remove(pd->filetransfers, px);
388 	}
389 }
390 
391 
392 
393 PurpleXferUiOps bee_xfer_uiops =
394 {
395 	prplcb_xfer_new,           /* new_xfer */
396 	prplcb_xfer_destroy,       /* destroy */
397 	NULL,                      /* add_xfer */
398 	prplcb_xfer_progress,      /* update_progress */
399 	NULL,                      /* cancel_local */
400 	prplcb_xfer_cancel_remote, /* cancel_remote */
401 	NULL,                      /* ui_write */
402 	NULL,                      /* ui_read */
403 	NULL,                      /* data_not_sent */
404 };
405