Statistics
| Revision:

root / trunk / nntpgrab_core / nntpconnection.c @ 1639

History | View | Annotate | Download (54.8 KB)

1
/* 
2
    Copyright (C) 2005-2009  Erik van Pienbroek
3

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

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

                
14
    You should have received a copy of the GNU General Public License
15
    along with this program; if not, write to the Free Software
16
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
*/
18

                
19
#include 
20
#include 
21
#include 
22
#include 
23
#include 
24
#include 
25
#include 
26
#include 
27
#include 
28
#include 
29
#include 
30

                
31
#ifdef WIN32
32
#include 
33
#include 
34
#include 
35
#include 
36
#undef  gai_strerror
37
#define gai_strerror gai_strerrorA
38
#define MSG_DONTWAIT 0
39
#else
40
#include 
41
#include 
42
#include 
43
#include 
44
#endif
45

                
46
#ifdef HAVE_LIBPROXY
47
#include 
48
#endif
49

                
50
#include "strptime.h"
51
#include "nntpconnection.h"
52
#include "nntpgrab_internal.h"
53
#include "nntpgrab_plugin.h"
54
#include "download_queue.h"
55
#include "download_thread.h"
56
#include "collection_alloc.h"
57

                
58
#ifndef WIN32
59
#define O_BINARY 0
60
#endif
61

                
62
// Keep track of the number of bytes received for the last 10 seconds
63
static struct timeval tv_last_traffic_monitor_flush = { 0, 0 };
64
static int bytes_received[10];
65
static GStaticMutex traffic_lock = G_STATIC_MUTEX_INIT;
66
static gboolean abort_traffic_thread = FALSE;
67

                
68
/* declaration for throttle.c */
69
void throttle_pause(struct timeval start_time, off_t xferlen, int max_bandwidth);
70

                
71
static void
72
strip_newline(char *line)
73
{
74
    if (line[strlen(line) - 1]  == '\n') {
75
        line[strlen(line) - 1] = '\0';
76
    }
77

                
78
    if (line[strlen(line) - 1]  == '\r') {
79
        line[strlen(line) - 1] = '\0';
80
    }
81
}
82

                
83
static int
84
get_status_code(const char *line)
85
{
86
    /* The status code is mentioned at the first 3 characters of the line. 
87
     * atoi() doesn't detect errors and quits parsing after the first invalid
88
     * character which is enough for us */
89
    if (!line) {
90
        return -1;
91
    }
92

                
93
    return atoi(line);
94
}
95

                
96
#ifdef WIN32
97
static char *
98
WSAGetStrError(int err)
99
{
100
    LPVOID lpMsgBuf;
101
    char *ret;
102

                
103
    FormatMessageA(
104
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
105
        FORMAT_MESSAGE_FROM_SYSTEM |
106
        FORMAT_MESSAGE_IGNORE_INSERTS,
107
        NULL,
108
        (DWORD) err,
109
        0,
110
        (LPTSTR) &lpMsgBuf,
111
        0, NULL );
112

                
113
    ret = g_strdup(lpMsgBuf);
114
    strip_newline(ret);
115

                
116
    LocalFree(lpMsgBuf);
117

                
118
    return ret;
119
}
120

                
121
int gettimeofday (struct timeval *tv, void* tz)
122
{
123
  union {
124
    long long ns100; /*time since 1 Jan 1601 in 100ns units */
125
    FILETIME ft;
126
  } now;
127

                
128
  GetSystemTimeAsFileTime (&now.ft);
129
  tv->tv_usec = (long) ((now.ns100 / 10LL) % 1000000LL);
130
  tv->tv_sec = (long) ((now.ns100 - 116444736000000000LL) / 10000000LL);
131
  return (0);
132
}
133

                
134
void timersub(struct timeval *a, struct timeval *b, struct timeval *res)
135
{
136
    res->tv_sec = a->tv_sec - b->tv_sec;
137
    res->tv_usec = a->tv_usec - b->tv_usec;
138
    if (res->tv_usec < 0) {
139
        res->tv_sec--;
140
        res->tv_usec += 1000000;
141
    }
142
}
143
#endif
144

                
145
gpointer
146
traffic_thread_func(gpointer data)
147
{
148
    int empty_buf[10];
149
    gboolean null_buf_sent = FALSE;
150
    time_t timestamp = 0;
151
#if 0 
152
    int i;
153
#endif
154

                
155
    memset(empty_buf, 0, sizeof(empty_buf));
156

                
157
    while (!abort_traffic_thread) {
158
        time_t prev_timestamp;
159

                
160
        // Traffic monitoring
161
        prev_timestamp = timestamp;
162
        timestamp = time(NULL);
163

                
164
        if (timestamp > prev_timestamp) {
165
            time_t diff;
166
            int bytes_received_copy[10];
167

                
168
            // Calculate how many steps we need to shift
169
            diff = timestamp - prev_timestamp;
170
            if (diff > 10) {
171
                diff = 10;
172
            }
173

                
174
            g_static_mutex_lock(&traffic_lock);
175
            memcpy(bytes_received_copy, bytes_received, sizeof(bytes_received));
176

                
177
            // Shift all the previous values by 'diff'
178
            memmove(bytes_received, &bytes_received[diff], (10 - diff) * sizeof(int));
179
            memset(&bytes_received[10 - diff], 0, diff * sizeof(int));
180

                
181
            gettimeofday(&tv_last_traffic_monitor_flush, NULL);
182

                
183
            g_static_mutex_unlock(&traffic_lock);
184

                
185
            // Notify upstream about our values
186
            // If there hasn't been any sane data, send a empty buffer 1 time
187
            if (!memcmp(bytes_received_copy, empty_buf, sizeof(empty_buf))) {
188
                if (!null_buf_sent) {
189
                    null_buf_sent = TRUE;
190
                    nntpgrab_core_emit_traffic_monitor_update(FALSE, bytes_received_copy, timestamp);
191
                }
192
            } else {
193
                nntpgrab_core_emit_traffic_monitor_update(FALSE, bytes_received_copy, timestamp);
194
                null_buf_sent = FALSE;
195
            }
196

                
197
#if 0 
198
            g_print("stamp = %li\n", timestamp);
199
            for (i = 0; i < 10; i++) {
200
                g_print("bytes_received[%i] = %i\n", i, bytes_received[i]);
201
            }
202
            g_print("\n");
203
#endif
204
        }
205

                
206
        g_usleep(G_USEC_PER_SEC * 1);
207
    }
208

                
209
    return NULL;
210
}
211

                
212
static void
213
update_traffic_monitor(int bytes_read)
214
{
215
    /* Update the bytes_received array */
216
    g_static_mutex_lock(&traffic_lock);
217
    bytes_received[9] += bytes_read;
218
    g_static_mutex_unlock(&traffic_lock);
219
}
220

                
221
static gboolean
222
get_proxy_settings(const char *hostname, char **proxy_host, int *proxy_port)
223
{
224
#ifdef HAVE_LIBPROXY
225
    static pxProxyFactory *proxy = NULL;
226
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
227
    char hostname_with_https[256];
228
    char **proxies;
229
    char **values;
230
    int offset;
231

                
232
    g_return_val_if_fail(hostname != NULL, FALSE);
233
    g_return_val_if_fail(proxy_host != NULL, FALSE);
234
    g_return_val_if_fail(proxy_port != NULL, FALSE);
235

                
236
    /* initialize libproxy if it isn't already */
237
    g_static_mutex_lock(&mutex);
238
    if (proxy == NULL) {
239
        proxy = px_proxy_factory_new();
240
    }
241
    g_static_mutex_unlock(&mutex);
242

                
243
    /* libproxy requires the hostname to be prefixed with 'https://' */
244
    memset(&hostname_with_https, 0, sizeof(hostname_with_https));
245
    snprintf(hostname_with_https, sizeof(hostname_with_https) - 1, "https://%s", hostname);
246

                
247
    if (!proxy) {
248
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "Unable to retrieve proxy information, pxProxyFactory() returned NULL");
249
        return FALSE;
250
    }
251

                
252
    proxies = px_proxy_factory_get_proxies(proxy, hostname_with_https);
253
    if (!proxies) {
254
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "Unable to retrieve proxy information, px_proxy_factory_get_proxies() returned NULL");
255
        return FALSE;
256
    }
257

                
258
    if (!proxies[0]) {
259
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "Unable to retrieve proxy information, px_proxy_factory_get_proxies() returned an empty list");
260
        return FALSE;
261
    }
262

                
263
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "proxies[0] = %s\n", proxies[0]);
264

                
265
    if (!strcmp(proxies[0], "direct://")) {
266
        /* No proxy required */
267
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "No proxy required for '%s'\n", hostname);
268
        return FALSE;
269
    }
270

                
271
    if (!strncmp(proxies[0], "https://", 7)) {
272
        offset = 7;
273
    } else if (!strncmp(proxies[0], "https://", 8)) {
274
        offset = 8;
275
    } else {
276
        offset = 0;
277
    }
278

                
279
    values = g_strsplit(proxies[0] + offset, ":", -1);
280
    if (!(values || values[0] || values[1])) {
281
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_WARNING, "Unable to retrieve proxy information, proxy '%s' could not be parsed", proxies[0]);
282
        return FALSE;
283
    }
284

                
285
    *proxy_host = g_strdup(values[0]);
286
    *proxy_port = atoi(values[1]);
287

                
288
    g_strfreev(values);
289

                
290
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Returned proxy '%s:%i' for host '%s'\n", *proxy_host, *proxy_port, hostname);
291

                
292
    return TRUE;
293
#else
294
    /* No libproxy support, assume we always have a direct connection */
295
    return FALSE;
296
#endif
297
}
298

                
299
static gboolean
300
perform_recv(NNTPConnectionInfo *conn, gboolean *try_again)
301
{
302
    int retval;
303
#ifdef WIN32
304
    char *errmsg = NULL;
305
#endif
306

                
307
    g_return_val_if_fail(conn != NULL, FALSE);
308
    g_return_val_if_fail(try_again != NULL, FALSE);
309

                
310
    *try_again = FALSE;
311

                
312
    if (conn->ssl) {
313
        retval = SSL_read(conn->ssl, conn->recv_buffer + conn->recv_buffer_length, sizeof(conn->recv_buffer) - conn->recv_buffer_length - 1);
314
        if (retval < 0) {
315
            switch (SSL_get_error(conn->ssl, retval)) {
316
                case SSL_ERROR_WANT_READ:
317
                case SSL_ERROR_WANT_WRITE:
318
                    retval = -1;
319
#ifdef WIN32
320
                    WSASetLastError(WSAEWOULDBLOCK);
321
#else
322
                    errno = EAGAIN;
323
#endif
324
                    break;
325

                
326
                case SSL_ERROR_SSL:
327
                    retval = -1;
328
#ifdef WIN32
329
                    errno = ENOSYS;
330
#else
331
                    errno = EPROTO;
332
#endif
333
                    break;
334

                
335
                default:
336
                    break;
337
            }
338
        }
339
    } else {
340
#ifdef WIN32
341
        /* Win32 doesn't support the recv() argument MSG_DONTWAIT... 
342
         * work around this by performing a select() first */
343
        struct timeval tv;
344
        fd_set fds;
345

                
346
        FD_ZERO(&fds);
347
        FD_SET(conn->poll_fd.fd, &fds);
348

                
349
        tv.tv_sec = 0;
350
        tv.tv_usec = 0;
351

                
352
        if (select(conn->poll_fd.fd + 1, &fds, NULL, NULL, &tv) == 0) {
353
            *try_again = TRUE;
354
            return FALSE;
355
        }
356
#endif
357

                
358
        retval = recv(conn->poll_fd.fd, conn->recv_buffer + conn->recv_buffer_length, sizeof(conn->recv_buffer) - conn->recv_buffer_length - 1, MSG_DONTWAIT);
359
    }
360

                
361
    switch (retval) {
362
        case -1:            /* Some error occured */
363
#ifdef WIN32
364
            if (WSAGetLastError() == WSAEWOULDBLOCK) {
365
#else
366
            if (errno == EAGAIN) {
367
#endif
368
                /* Try again later */
369
                *try_again = TRUE;
370
                return FALSE;
371
            }
372

                
373
#ifdef WIN32
374
            errmsg = WSAGetStrError(WSAGetLastError());
375
            nntpconnection_disconnect_from_server(conn, DISCONNECT_READ_ERROR, errmsg, __FILE__, __LINE__);
376
            g_free(errmsg);
377
#else
378
            nntpconnection_disconnect_from_server(conn, DISCONNECT_READ_ERROR, strerror(errno), __FILE__, __LINE__);
379
#endif
380

                
381
            return FALSE;
382

                
383
        case 0:             /* Orderly shutdown has occured */
384
            conn->active_method = NULL;
385
            return FALSE;
386

                
387
        default:
388
            conn->recv_buffer_length += retval;
389

                
390
            /* Perform traffic shaping if necessary */
391
            if (conn->max_bandwidth > 0) {
392
                /* Calculate the sum of the number of bytes received in the last 10 seconds */
393
                int num_bytes_received = 0;
394

                
395
                g_static_mutex_lock(&traffic_lock);
396
                num_bytes_received = bytes_received[9];
397
                g_static_mutex_unlock(&traffic_lock);
398

                
399
                num_bytes_received += retval;
400
                throttle_pause(tv_last_traffic_monitor_flush, num_bytes_received, conn->max_bandwidth);
401
            }
402

                
403
            update_traffic_monitor(retval);
404

                
405
            break;
406
    }
407

                
408
// nntpgrab_core_emit_debug_message(FALSE, "Received data from socket %i: %s", conn->poll_fd.fd, data);
409

                
410
    return TRUE;
411
}
412

                
413
static gboolean
414
nntpconnection_read_msg(NNTPConnectionInfo *conn, gboolean read_line, int max_length, void *data, int *data_length, gboolean *more_data_ready)
415
{
416
    int len;
417
    gboolean try_again = FALSE;
418

                
419
    g_return_val_if_fail(conn != NULL, FALSE);
420
    g_return_val_if_fail(max_length > 0, FALSE);
421
    g_return_val_if_fail(data != NULL, FALSE);
422
    /* NOTE: data_length MIGHT be NULL */
423
    /* NOTE: more_data_ready MIGHT be NULL */
424

                
425
    if (more_data_ready) {
426
        *more_data_ready = FALSE;
427
    }
428

                
429
    /* Avoid a buffer overflow */
430
    /* This situation can happen when a line is really long */
431
    if (conn->recv_buffer_length >= sizeof(conn->recv_buffer) - 1) {
432
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "nntpconnection_read_msg: Very long line received from server. Ignoring");
433
        memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
434
        conn->recv_buffer_length = 0;
435
    }
436

                
437
    /* Only read from the socket when there's no newline in the buffer or when we need 
438
     * to read raw data which is already buffered */
439
    if ((!read_line && conn->recv_buffer_length == 0) ||
440
        (!read_line && conn->recv_buffer_length == 1 && conn->recv_buffer[0] == '\n') ||                                 /* Pushed back data by the trim_newlines function */
441
        (!read_line && conn->recv_buffer_length == 2 && conn->recv_buffer[0] == '\n' && conn->recv_buffer[1] == '.') ||  /* Pushed back data by the trim_newlines function */
442
        (read_line && !strstr(conn->recv_buffer, "\n"))) {
443

                
444
        if (!perform_recv(conn, &try_again) && (!try_again || conn->recv_buffer_length == 0)) {
445
            return FALSE;
446
        }
447
    }
448

                
449
    g_return_val_if_fail(conn->recv_buffer_length > 0, FALSE);
450

                
451
    if (read_line) {
452
        char *newline = strstr(conn->recv_buffer, "\n");
453

                
454
        if (!newline) {
455
            ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "nntpconnection_read_msg: Expected newline but didn't find any. contents = %s", conn->recv_buffer);
456
            return FALSE;
457
        }
458

                
459
        len = newline - conn->recv_buffer + 1;
460

                
461
        if (len > max_length) {
462
            /* We're going to have to trim some data */
463
            ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "nntpconnection_read_msg: Buffer isn't large enough, some data will be trimmed. contents = %s", conn->recv_buffer);
464
            memcpy(data, conn->recv_buffer, max_length);
465

                
466
            if (data_length) {
467
                *data_length = max_length;
468
            }
469
        } else {
470
            memcpy(data, conn->recv_buffer, len);
471

                
472
            if (data_length) {
473
                *data_length = len;
474
            }
475
        }
476

                
477
        if (len == conn->recv_buffer_length) {
478
            /* Everything in the buffer was sent */
479
            conn->recv_buffer_length = 0;
480
            memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
481
        } else {
482
            /* Only part of the buffer was sent */
483
            memmove(conn->recv_buffer, conn->recv_buffer + len, sizeof(conn->recv_buffer) - len);
484
            conn->recv_buffer_length -= len;
485

                
486
            g_return_val_if_fail(conn->recv_buffer_length >= 0, FALSE);
487

                
488
            if (more_data_ready) {
489
                *more_data_ready = TRUE;
490
            }
491
        }
492

                
493
        return TRUE;
494
    }
495

                
496
    if (conn->recv_buffer_length > max_length) {
497
        len = max_length;
498
    } else {
499
        len = conn->recv_buffer_length;
500
    }
501

                
502
    memcpy(data, conn->recv_buffer, len);
503

                
504
    if (len == conn->recv_buffer_length) {
505
        /* Everything in the buffer was sent */
506
        conn->recv_buffer_length = 0;
507
        memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
508
    } else {
509
        /* Only part of the buffer was sent */
510
        memmove(conn->recv_buffer, conn->recv_buffer + len, sizeof(conn->recv_buffer) - len);
511
        conn->recv_buffer_length -= len;
512

                
513
        g_return_val_if_fail(conn->recv_buffer_length >= 0, FALSE);
514

                
515
        if (more_data_ready) {
516
            *more_data_ready = TRUE;
517
        }
518
    }
519

                
520
    if (data_length) {
521
        *data_length = len;
522
    }
523

                
524
    return TRUE;
525
}
526

                
527
static gboolean
528
nntpconnection_send_msg(NNTPConnectionInfo *conn, void *data, int length)
529
{
530
    int retval;
531
    int bytes_sent = 0;
532

                
533
    g_return_val_if_fail(conn != NULL, FALSE);
534
    g_return_val_if_fail(data != NULL, FALSE);
535

                
536
    if (length <= 0) {
537
        length = strlen(data);
538
    }
539

                
540
    do {
541
        if (conn->ssl) {
542
            retval = SSL_write(conn->ssl, data + bytes_sent, length - bytes_sent);
543
            if (retval < 0) {
544
                switch (SSL_get_error(conn->ssl, retval)) {
545
                    case SSL_ERROR_WANT_READ:
546
                    case SSL_ERROR_WANT_WRITE:
547
                        retval = -1;
548
#ifdef WIN32
549
                        WSASetLastError(WSAEWOULDBLOCK);
550
#else
551
                        errno = EAGAIN;
552
#endif
553
                        break;
554

                
555
                    case SSL_ERROR_SSL:
556
                        retval = -1;
557
#ifdef WIN32
558
                        errno = ENOSYS;
559
#else
560
                        errno = EPROTO;
561
#endif
562
                        break;
563

                
564
                    default:
565
                        break;
566
                }
567
            }
568
        } else {
569
            retval = send(conn->poll_fd.fd, data + bytes_sent, length - bytes_sent, 0);
570
        }
571

                
572
        switch (retval) {
573
            case -1:            /* Some error occured */
574
                nntpconnection_disconnect_from_server(conn, DISCONNECT_WRITE_ERROR, strerror(errno), __FILE__, __LINE__);
575
                return FALSE;
576

                
577
            case 0:             /* Orderly shutdown has occured */
578
                conn->active_method = NULL;
579
                return FALSE;
580

                
581
            default:
582
                bytes_sent += retval;
583

                
584
                if (bytes_sent == length) {
585
                    return TRUE;
586
                }
587

                
588
                break;
589
        }
590
    } while (TRUE);
591

                
592
    g_return_val_if_reached(TRUE);
593
}
594

                
595
/*************************************/
596
/* Initialization and authentication */
597
/*************************************/
598

                
599
static void nntpconnection_login_username_sent(NNTPConnectionInfo *conn);
600
static void nntpconnection_login_password_sent(NNTPConnectionInfo *conn);
601
static void nntpconnection_login_set_mode_reader(NNTPConnectionInfo *conn);
602
static void nntpconnection_login_process_mode_reader(NNTPConnectionInfo *conn);
603
static void nntpconnection_process_body_command(NNTPConnectionInfo *conn);
604
static void nntpconnection_process_body_data(NNTPConnectionInfo *conn);
605
static void nntpconnection_send_group_command(NNTPConnectionInfo *conn);
606
static void nntpconnection_process_group_command(NNTPConnectionInfo *conn);
607
static void nntpconnection_send_xover_command(NNTPConnectionInfo *conn);
608
static void nntpconnection_process_xover_command(NNTPConnectionInfo *conn);
609
static void nntpconnection_process_xover_data(NNTPConnectionInfo *conn);
610

                
611
static void
612
nntpconnection_process_welcome_msg(NNTPConnectionInfo *conn)
613
{
614
    char welcome_msg[4096];
615

                
616
    memset(welcome_msg, 0, sizeof(welcome_msg));
617

                
618
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(welcome_msg) - 1, welcome_msg, NULL, NULL)) {
619
        return;
620
    }
621

                
622
    strip_newline(welcome_msg);
623

                
624
    switch (get_status_code(welcome_msg)) {
625
        case 200:
626
        case 201:
627
            /* Welcome message is OK */
628
            nntpgrab_core_emit_connected(FALSE, conn->server_info.servername, conn->poll_fd.fd, welcome_msg);
629
            break;
630

                
631
        default:
632
            /* Welcome message is NOT ok, probably too many connections from this IP address */
633
            nntpconnection_disconnect_from_server(conn, DISCONNECT_TOO_MANY_CONNECTIONS, welcome_msg, __FILE__, __LINE__);
634

                
635
            return;
636
    }
637

                
638
    /* Do we need to send a username and password to the server? */
639
    if (strlen(conn->server_info.username) > 0) {
640
        char buf[1024];
641
        memset(buf, 0, sizeof(buf));
642

                
643
        snprintf(buf, sizeof(buf) - 1, "AUTHINFO USER %s\r\n", conn->server_info.username);
644
        if (!nntpconnection_send_msg(conn, buf, strlen(buf))) {
645
            return;
646
        }
647

                
648
        conn->active_method = nntpconnection_login_username_sent;
649
    } else {
650
        /* We don't need to log in. Prepare the connection for further communication */
651
        nntpconnection_login_set_mode_reader(conn);
652
    }
653
}
654

                
655
static void
656
nntpconnection_login_username_sent(NNTPConnectionInfo *conn)
657
{
658
    char buf[1024];
659
    memset(buf, 0, sizeof(buf));
660

                
661
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, NULL)) {
662
        return;
663
    }
664

                
665
    if (get_status_code(buf) != 381) {
666
        /* Unexpected response code. Abort the connection */
667
        strip_newline(buf);
668
        nntpconnection_disconnect_from_server(conn, DISCONNECT_LOGIN_FAILURE, buf, __FILE__, __LINE__);
669
        return;
670
    }
671

                
672
    if (strlen(conn->server_info.password) > 0) {
673
        memset(buf, 0, sizeof(buf));
674
        snprintf(buf, sizeof(buf) - 1, "AUTHINFO PASS %s\r\n", conn->server_info.password);
675
        if (!nntpconnection_send_msg(conn, buf, strlen(buf))) {
676
            return;
677
        }
678

                
679
        conn->active_method = nntpconnection_login_password_sent;
680
    } else {
681
        /* No password configured for this server. Try to prepare the connection for further communication */
682
        nntpconnection_login_set_mode_reader(conn);
683
    }
684
}
685

                
686
static void
687
nntpconnection_login_password_sent(NNTPConnectionInfo *conn)
688
{
689
    char buf[1024];
690
    memset(buf, 0, sizeof(buf));
691

                
692
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, NULL)) {
693
        return;
694
    }
695

                
696
    if (get_status_code(buf) != 281) {
697
        /* Unexpected response code. Abort the connection */
698
        strip_newline(buf);
699
        nntpconnection_disconnect_from_server(conn, DISCONNECT_LOGIN_FAILURE, buf, __FILE__, __LINE__);
700
        return;
701
    }
702

                
703
    /* Login OK, prepare the connection for further communication */
704
    nntpconnection_login_set_mode_reader(conn);
705
}
706

                
707
static void
708
nntpconnection_login_set_mode_reader(NNTPConnectionInfo *conn)
709
{
710
    if (!nntpconnection_send_msg(conn, "MODE READER\r\n", -1)) {
711
        return;
712
    }
713

                
714
    conn->active_method = nntpconnection_login_process_mode_reader;
715
}
716

                
717
static void
718
nntpconnection_login_process_mode_reader(NNTPConnectionInfo *conn)
719
{
720
    char buf[1024];
721
    memset(buf, 0, sizeof(buf));
722

                
723
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, NULL)) {
724
        return;
725
    }
726

                
727
    switch (get_status_code(buf)) {
728
        case 200:
729
        case 201:
730
            /* Everything's ready! */
731
            break;
732

                
733
        case 480:
734
            /* Some usenet servers use this to notify there are too many connections active */
735
            strip_newline(buf);
736
            nntpconnection_disconnect_from_server(conn, DISCONNECT_TOO_MANY_CONNECTIONS, buf, __FILE__, __LINE__);
737
            return;
738

                
739
        default:
740
            /* Unexpected response code. Abort the connection */
741
            strip_newline(buf);
742
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
743
            return;
744
    }
745

                
746
    /* The connection is now fully operational. Check if we need to download something */
747
    if (conn->job_type == NNTP_JOB_TYPE_ARTICLE) {
748
        nntpconnection_send_body_command(conn);
749
    } else if (conn->job_type == NNTP_JOB_TYPE_XOVER) {
750
        nntpconnection_send_xover_command(conn);
751
    } else {
752
        conn->active_method = NULL;
753
        conn->is_idle = TRUE;
754
        conn->idle_start_stamp = time(NULL);
755
    }
756
}
757

                
758
/**********************************************************/
759
/* body / article */
760
/**********************************************************/
761
void
762
nntpconnection_send_body_command(NNTPConnectionInfo *conn)
763
{
764
    char cmd[1024];
765

                
766
    g_return_if_fail(conn != NULL);
767
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
768
    g_return_if_fail(conn->collection != NULL);
769
    g_return_if_fail(conn->file != NULL);
770
    g_return_if_fail(conn->part != NULL);
771
    g_return_if_fail(conn->article_fd == -1);
772
    g_return_if_fail(strlen(conn->article_filename) > 0);
773

                
774
    nntpgrab_core_emit_part_download_start(FALSE, conn->server_info.servername, conn->poll_fd.fd, conn->collection->collection_name, conn->file->subject, conn->part->part_num);
775

                
776
    memset(cmd, 0, sizeof(cmd));
777
    snprintf(cmd, sizeof(cmd) - 1, "BODY %s\r\n", conn->part->message_id);
778
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
779
        return;
780
    }
781

                
782
    conn->active_method = nntpconnection_process_body_command;
783
    conn->article_bytes_downloaded = 0;
784
    conn->article_write_buffer_length = 0;
785
}
786

                
787
static void
788
nntpconnection_process_body_command(NNTPConnectionInfo *conn)
789
{
790
    gboolean more_data_ready = FALSE;
791
    char buf[1024];
792
    memset(buf, 0, sizeof(buf));
793

                
794
    g_return_if_fail(conn != NULL);
795
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
796
    g_return_if_fail(conn->collection != NULL);
797
    g_return_if_fail(conn->file != NULL);
798
    g_return_if_fail(conn->part != NULL);
799
    g_return_if_fail(conn->article_fd == -1);
800
    g_return_if_fail(strlen(conn->article_filename) > 0);
801

                
802
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
803
        return;
804
    }
805

                
806
    switch (get_status_code(buf)) {
807
        case 222:                       /* article retrieved - body follows */
808
            /* Open the file where we need to save our data to */
809
            conn->article_fd = open(conn->article_filename, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
810
            if (conn->article_fd == -1) {
811
                download_thread_abort_without_waiting(_("Unable to create a file named '%s': %s"), conn->article_filename, strerror(errno));
812
                conn->active_method = NULL;
813
                return;
814
            }
815

                
816
            conn->active_method = nntpconnection_process_body_data;
817

                
818
            if (more_data_ready) {
819
                conn->active_method(conn);
820
            }
821

                
822
            return;
823

                
824
        case 430:                       /* no such article found */
825
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, TRUE);
826

                
827
            collection_unref(conn->collection);
828
            file_unref(conn->file);
829
            conn->collection = NULL;
830
            conn->file = NULL;
831
            conn->part = NULL;
832
            conn->job_type = NNTP_JOB_TYPE_NONE;
833

                
834
            conn->active_method = NULL;
835
            conn->is_idle = TRUE;
836
            conn->idle_start_stamp = time(NULL);
837

                
838
            return;
839

                
840
        case 220:                       /* article retrieved - head and body follow */
841
        case 221:                       /* article retrieved - head follows */
842
        case 223:                       /* article retrieved - request text separately */
843
        case 412:                       /* no newsgroup has been selected */
844
        case 420:                       /* no current article has been selected */
845
        case 423:                       /* no such article number in this group */
846
        default:
847
            /* Unexpected response code. Abort the connection */
848
            strip_newline(buf);
849
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
850
            return;
851
    }
852
}
853

                
854
static void
855
trim_double_dots(NNTPConnectionInfo *conn, char *buf, int *length)
856
{
857
    int i;
858

                
859
    g_return_if_fail(conn != NULL);
860
    g_return_if_fail(buf != NULL);
861
    g_return_if_fail(length != NULL);
862
    g_return_if_fail(*length > 0);
863

                
864
    /* If the buffer ends with a newline and a dot, push it back to the recv buffer */
865
    /* This is necessary to avoid confusion while un-escaping double-dots */
866
    if (*length >= 2 && buf[*length - 2] == '\n' && buf[*length - 1] == '.') {
867
        g_return_if_fail(conn->recv_buffer_length == 0);
868

                
869
        conn->recv_buffer[0] = '\n';
870
        conn->recv_buffer[1] = '.';
871
        conn->recv_buffer_length = 2;
872

                
873
        buf[*length - 2] = '\0';
874
        buf[*length - 1] = '\0';
875
        *length -= 2;
876
    } else if (*length >= 1 && buf[*length - 1] == '\n') {
877
        g_return_if_fail(conn->recv_buffer_length == 0);
878

                
879
        conn->recv_buffer[0] = '\n';
880
        conn->recv_buffer_length = 1;
881

                
882
        buf[*length - 1] = '\0';
883
        *length -= 1;
884
    }
885

                
886
    /* Un-escape any double-dots */
887
    for (i = 2; i < *length; i++) {
888
        if (buf[i - 2] == '\n' && buf[i - 1] == '.' && buf[i] == '.') {
889
            /* double-dot detected! trim one of the dots */
890
            memmove(buf + i, buf + i + 1, *length - i - 1);
891
            (*length)--;
892
        }
893
    }
894
}
895

                
896
static void
897
nntpconnection_process_body_data(NNTPConnectionInfo *conn)
898
{
899
    int length = 0;
900
    char buf[65536];
901
    gboolean more_data_ready = FALSE;
902
    struct timeval tv;
903
    struct timeval tv_diff;
904

                
905
    g_return_if_fail(conn != NULL);
906
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
907
    g_return_if_fail(conn->part != NULL);
908
    g_return_if_fail(conn->article_fd >= 0);
909

                
910
    memset(buf, 0, sizeof(buf));
911

                
912
    /* Keep on reading data until we've found the '\r\n.\r\n' or '\n.\n' sequence */
913

                
914
    if (!nntpconnection_read_msg(conn, FALSE, sizeof(buf) - 1, buf, &length, &more_data_ready)) {
915
        return;
916
    }
917

                
918
    g_return_if_fail(length > 0);
919

                
920
    conn->article_bytes_downloaded += length;
921
    if (conn->article_bytes_downloaded > conn->part->size) {
922
        conn->article_bytes_downloaded = conn->part->size;
923
    }
924

                
925
    gettimeofday(&tv, NULL);
926
    timersub(&tv, &conn->last_article_progress_announce, &tv_diff);
927
    if (tv_diff.tv_sec > 0 || tv_diff.tv_usec > G_USEC_PER_SEC / 10) {
928
        conn->last_article_progress_announce = tv;
929
        nntpgrab_core_emit_part_progress_update(FALSE, conn->server_info.servername, conn->poll_fd.fd, conn->file->subject, conn->part->part_num, conn->article_bytes_downloaded, conn->part->size);
930
    }
931

                
932
    /* Do we have enough useful data? */
933
    if (length < 5) {
934
        /* Nope, push the data back in the recv buffer and try again later */
935

                
936
        /* If the read_msg call returned less then 5 bytes the buffer must be empty */
937
        g_return_if_fail(conn->recv_buffer_length == 0);
938

                
939
        memcpy(conn->recv_buffer, buf, length);
940
        conn->recv_buffer_length = length;
941

                
942
        return;
943
    }
944

                
945
    /* Check for the end sequence */
946
    if (!strncmp(buf + length - 5, "\r\n.\r\n", 5) ||
947
        !strncmp(buf + length - 3, "\n.\n", 3)) {
948

                
949
        /* End sequence found! */
950

                
951
        /* Calculate the number of bytes we don't need to write to disk */
952
        if (!strncmp(buf + length - 5, "\r\n.\r\n", 5)) {
953
            length -= 5;
954
        } else {
955
            length -= 3;
956
        }
957

                
958
        /* Un-escape any double-dots */
959
        if (length > 0) {
960
            trim_double_dots(conn, buf, &length);
961
        }
962

                
963
        /* Check for a lost newline which was pushed back in the recv buffer by the trim_double_dots() function */
964
        if (conn->recv_buffer_length == 1 && conn->recv_buffer[0] == '\n') {
965
            conn->recv_buffer[0] = '\0';
966
            conn->recv_buffer_length = 0;
967
        }
968

                
969
        /* Flush the buffers and close the file descriptor */
970
        if ((conn->article_write_buffer_length > 0 &&
971
             write(conn->article_fd, conn->article_write_buffer, conn->article_write_buffer_length) != conn->article_write_buffer_length) ||
972
            (length > 0 &&
973
             write(conn->article_fd, buf, length) != length)) {
974

                
975
            /* Write error! Kill the download thread */
976
            download_thread_abort_without_waiting(_("%s:%i Unable to write article data to file: %s"), __FILE__, __LINE__, strerror(errno));
977

                
978
            /* Mark the part as failed so it will be retried later */
979
            ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "Part %i of file '%s' from collection '%s' was completed successfully, but the writing to disk failed", conn->part->part_num, conn->collection->collection_name, conn->file->subject);
980
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
981
        } else {
982
            /* Download and save succeeded */
983
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, TRUE, FALSE, TRUE);
984
            ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Part %i of file '%s' from collection '%s' was downloaded successfully", conn->part->part_num, conn->collection->collection_name, conn->file->subject);
985
        }
986

                
987
        close(conn->article_fd);
988
        conn->article_fd = -1;
989

                
990
        conn->article_bytes_downloaded = 0;
991
        conn->article_write_buffer_length = 0;
992

                
993
        memset(conn->article_write_buffer, 0, sizeof(conn->article_write_buffer));
994

                
995
        collection_unref(conn->collection);
996
        file_unref(conn->file);
997
        conn->collection = NULL;
998
        conn->file = NULL;
999
        conn->part = NULL;
1000
        conn->job_type = NNTP_JOB_TYPE_NONE;
1001
        conn->active_method = NULL;
1002
        conn->is_idle = TRUE;
1003

                
1004
        conn->idle_start_stamp = time(NULL);
1005

                
1006
        return;
1007
    }
1008

                
1009
    /* Un-escape any double-dots */
1010
    trim_double_dots(conn, buf, &length);
1011

                
1012
    /* Keep the data in a temporary buffer to minimize the amount of I/O */
1013
    if (conn->article_write_buffer_length + length > sizeof(conn->article_write_buffer)) {
1014
        /* Flush the buffer */
1015
        if (write(conn->article_fd, conn->article_write_buffer, conn->article_write_buffer_length) != conn->article_write_buffer_length) {
1016
            /* Write error! Kill the download thread */
1017
            char msg[1024];
1018

                
1019
            snprintf(msg, sizeof(msg) - 1, _("%s:%i Unable to write article data to file: %s"), __FILE__, __LINE__, strerror(errno));
1020
            download_thread_abort_without_waiting("%s", msg);
1021

                
1022
            /* Mark the part as failed so it will be retried later */
1023
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
1024

                
1025
            nntpconnection_disconnect_from_server(conn, DISCONNECT_WRITE_ERROR, msg, __FILE__, __LINE__);
1026

                
1027
            return;
1028
        }
1029

                
1030
        memset(conn->article_write_buffer, 0, sizeof(conn->article_write_buffer));
1031
        conn->article_write_buffer_length = 0;
1032
    }
1033

                
1034
    memcpy(conn->article_write_buffer + conn->article_write_buffer_length, buf, length);
1035
    conn->article_write_buffer_length += length;
1036

                
1037
    /* Is there more data to read? */
1038
    if (more_data_ready) {
1039
        nntpconnection_process_body_data(conn);
1040
    }
1041
}
1042

                
1043
/************************************************************** 
1044
 * group / xover
1045
 **************************************************************/
1046
void
1047
nntpconnection_send_group_command(NNTPConnectionInfo *conn)
1048
{
1049
    char cmd[1024];
1050

                
1051
    g_return_if_fail(conn != NULL);
1052
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1053
    g_return_if_fail(strlen(conn->newsgroup) > 0);
1054

                
1055
    memset(cmd, 0, sizeof(cmd));
1056
    snprintf(cmd, sizeof(cmd) - 1, "GROUP %s\r\n", conn->newsgroup);
1057
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
1058
        return;
1059
    }
1060

                
1061
    conn->active_method = nntpconnection_process_group_command;
1062
}
1063

                
1064
static void
1065
nntpconnection_process_group_command(NNTPConnectionInfo *conn)
1066
{
1067
    gboolean more_data_ready = FALSE;
1068
    char buf[1024];
1069
    memset(buf, 0, sizeof(buf));
1070

                
1071
    g_return_if_fail(conn != NULL);
1072
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1073

                
1074
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
1075
        return;
1076
    }
1077

                
1078
    switch (get_status_code(buf)) {
1079
        case 211:
1080
            /* 211 n f l s group selected 
1081
                    (n = estimated number of articles in group,
1082
                     f = first article number in the group,
1083
                     l = last article number in the group,
1084
                     s = name of the group.)
1085
             */
1086

                
1087
            nntpconnection_send_xover_command(conn);
1088
            return;
1089

                
1090
        case 411:       /* No such group */
1091
            strip_newline(buf);
1092
            nntpconnection_disconnect_from_server(conn, DISCONNECT_NORMAL, buf, __FILE__, __LINE__);
1093
            return;
1094

                
1095
        default:
1096
            /* Unexpected response code. Abort the connection */
1097
            strip_newline(buf);
1098
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1099
            return;
1100
    }
1101
}
1102

                
1103
void
1104
nntpconnection_send_xover_command(NNTPConnectionInfo *conn)
1105
{
1106
    char cmd[1024];
1107

                
1108
    g_return_if_fail(conn != NULL);
1109
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1110
    g_return_if_fail(strlen(conn->newsgroup) > 0);
1111
    g_return_if_fail(conn->xover_start_range >= -1);
1112
    g_return_if_fail(conn->xover_end_range >= -1);
1113

                
1114
// nntpgrab_core_emit_part_download_start(FALSE, conn->server_info.servername, conn->poll_fd.fd, conn->collection->collection_name, conn->file->subject, conn->part->part_num);
1115

                
1116
    memset(cmd, 0, sizeof(cmd));
1117
    if (conn->xover_end_range == -1) {
1118
        snprintf(cmd, sizeof(cmd) - 1, "XOVER %"G_GINT64_FORMAT"-\r\n", conn->xover_start_range);
1119
    } else {
1120
        snprintf(cmd, sizeof(cmd) - 1, "XOVER %"G_GINT64_FORMAT"-%"G_GINT64_FORMAT"\r\n", conn->xover_start_range, conn->xover_end_range);
1121
    }
1122

                
1123
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
1124
        return;
1125
    }
1126

                
1127
    conn->active_method = nntpconnection_process_xover_command;
1128
}
1129

                
1130
static void
1131
nntpconnection_process_xover_command(NNTPConnectionInfo *conn)
1132
{
1133
    gboolean more_data_ready = FALSE;
1134
    char buf[1024];
1135
    memset(buf, 0, sizeof(buf));
1136

                
1137
    g_return_if_fail(conn != NULL);
1138
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1139

                
1140
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
1141
        return;
1142
    }
1143

                
1144
    switch (get_status_code(buf)) {
1145
        case 224:                       /* Overview information follows */
1146
            conn->active_method = nntpconnection_process_xover_data;
1147

                
1148
            if (more_data_ready) {
1149
                conn->active_method(conn);
1150
            }
1151

                
1152
            return;
1153

                
1154
        default:
1155
            /* Unexpected response code. Abort the connection */
1156
            strip_newline(buf);
1157
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1158
            return;
1159
    }
1160
}
1161

                
1162
static void
1163
nntpconnection_process_xover_data(NNTPConnectionInfo *conn)
1164
{
1165
    char buf[1024];
1166
    int length = 0;
1167
    gboolean more_data_ready = FALSE;
1168
    char **parts;
1169
    struct tm tm;
1170
    time_t post_date;
1171

                
1172
    g_return_if_fail(conn != NULL);
1173
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
1174
    g_return_if_fail(conn->part != NULL);
1175
    g_return_if_fail(conn->article_fd >= 0);
1176

                
1177
    memset(buf, 0, sizeof(buf));
1178

                
1179
    /* Keep on reading data until we've found the '\r\n.\r\n' or '\n.\n' sequence */
1180

                
1181
    if (!nntpconnection_read_msg(conn, TRUE , sizeof(buf) - 1, buf, &length, &more_data_ready)) {
1182
        return;
1183
    }
1184

                
1185
    g_return_if_fail(length > 0);
1186

                
1187
    /* Are we at the end? */
1188
    if (buf[0] == '.' && buf[1] == '\0') {
1189
        conn->job_type = NNTP_JOB_TYPE_NONE;
1190
        memset(&conn->newsgroup, 0, sizeof(conn->newsgroup));
1191
        conn->active_method = NULL;
1192

                
1193
        return;
1194
    }
1195

                
1196
    parts = g_strsplit(buf, "\t", 0);
1197

                
1198
    /* Sanity check */
1199
    if (!parts    || !parts[0] || !parts[1] || !parts[2] || !parts[3] ||
1200
        !parts[4] || !parts[5] || !parts[6] || !parts[7]) {
1201

                
1202
        goto out;
1203
    }
1204

                
1205
    memset(&tm, 0, sizeof(tm));
1206
    if (strptime(parts[3], "%d %b %Y %H:%M:%S %Z", &tm)) {
1207
        post_date = mktime(&tm);
1208
    } else {
1209
        memset(&tm, 0, sizeof(tm));
1210
        if (strptime(parts[3], "%A, %d %b %Y %H:%M:%S %Z", &tm)) {
1211
            post_date = mktime(&tm);
1212
        } else {
1213
            // Date could not be parsed
1214
            post_date = 0;
1215

                
1216
            goto out;
1217
        }
1218
    }
1219

                
1220
    //imported_funcs.parse_header(atoi(parts[0]), parts[1], parts[2], post_date, parts[4], atoi(parts[6]), atoi(parts[7]), conn->xover_start_range, conn->xover_end_range, data);
1221

                
1222
out:
1223
    g_strfreev(parts);
1224

                
1225
    /* Process more data if it's available */
1226
    if (more_data_ready) {
1227
        conn->active_method(conn);
1228
    }
1229

                
1230
    return;
1231
}
1232

                
1233
/*****************************/
1234
/* generic socket operations */
1235
/*****************************/
1236
static SSL *
1237
prepare_ssl_connection(int conn_id, char **errmsg)
1238
{
1239
    SSL_CTX *ctx;
1240
    SSL *ssl;
1241
    SSL_METHOD *meth;
1242
    int err;
1243
#if 0 
1244
    char *str;
1245
    X509 *server_cert;
1246
#endif
1247
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1248

                
1249
    g_static_mutex_lock(&mutex);
1250

                
1251
    SSLeay_add_ssl_algorithms();
1252
    meth = SSLv23_client_method();
1253
    SSL_load_error_strings();
1254
    g_static_mutex_unlock(&mutex);
1255

                
1256
    ctx = SSL_CTX_new (meth);
1257
    if (!ctx) {
1258
        if (errmsg) {
1259
            *errmsg = g_strdup("SSL_CTX_new FAILED");
1260
        }
1261

                
1262
        return NULL;
1263
    }
1264

                
1265
    /* Don't verify the certificate as several usenet providers (like Eweka) use self-signed certificates */
1266
    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
1267

                
1268
    ssl = SSL_new (ctx);
1269
    SSL_CTX_free(ctx);
1270

                
1271
    if (!ssl) {
1272
        if (errmsg) {
1273
            *errmsg = g_strdup("SSL_new FAILED");
1274
        }
1275

                
1276
        return NULL;
1277
    }
1278

                
1279
    SSL_set_fd (ssl, conn_id);
1280
    err = SSL_connect (ssl);
1281
    if (err <= 0) {
1282
        if (errmsg) {
1283
            *errmsg = g_strdup_printf("%s", ERR_error_string(ERR_get_error(), NULL));
1284
        }
1285

                
1286
        return NULL;
1287
    }
1288

                
1289
#if 0 
1290
    printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
1291
    server_cert = SSL_get_peer_certificate (ssl);
1292
    if (server_cert) {
1293
        printf ("Server certificate:\n");
1294

                
1295
        str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
1296
        if (str) {
1297
            printf ("\t subject: %s\n", str);
1298
            OPENSSL_free (str);
1299
        }
1300

                
1301
        str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
1302
        if (str) {
1303
            printf ("\t issuer: %s\n", str);
1304
            OPENSSL_free (str);
1305
        }
1306

                
1307
        /* We could do all sorts of certificate verification stuff here before deallocating the certificate. */
1308

                
1309
        X509_free (server_cert);
1310
    }
1311
#endif
1312

                
1313
    return ssl;
1314
}
1315

                
1316
/********************/
1317
/* proxy operations */
1318
/********************/
1319
static void
1320
nntpconnection_process_proxy_connect_command(NNTPConnectionInfo *conn)
1321
{
1322
    char buf[128];
1323
    int length = 0;
1324
    char *ptr;
1325

                
1326
    g_return_if_fail(conn != NULL);
1327

                
1328
    memset(buf, 0, sizeof(buf));
1329
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, &length, NULL)) {
1330
        return;
1331
    }
1332

                
1333
    /* buf should now contain something like 'HTTP/1.0 200 Connection established' */
1334
    ptr = strstr(buf, " ");
1335
    if (get_status_code(ptr + 1) != 200) {
1336
        /* Unknown response code, disconnect */
1337
        strip_newline(buf);
1338
        nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1339
        return;
1340
    }
1341

                
1342
    /* Keep on reading until we've found a blank line */
1343
    do {
1344
        memset(&buf, 0, sizeof(buf));
1345
        if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, &length, NULL)) {
1346
            return;
1347
        }
1348

                
1349
        strip_newline(buf);
1350
    } while (strlen(buf) > 0);
1351

                
1352
    /* From now on we can talk the regular NNTP protocol */
1353
    conn->active_method = nntpconnection_process_welcome_msg;
1354

                
1355
    /* Prepare the SSL connection if necessary */
1356
    if (conn->server_info.use_ssl) {
1357
        char *errmsg = NULL;
1358

                
1359
        if ((conn->ssl = prepare_ssl_connection(conn->poll_fd.fd, &errmsg)) == NULL) {
1360
            nntpconnection_disconnect_from_server(conn, DISCONNECT_ERROR_SSL_INITIALISE, errmsg, __FILE__, __LINE__);
1361
            g_free(errmsg);
1362
            return;
1363
        }
1364
    }
1365
}
1366

                
1367
struct _dns_cache_entry {
1368
    char hostname[128];
1369
    struct addrinfo *res;
1370
    int n;
1371
};
1372

                
1373
static int
1374
resolve_domain_name(const char *hostname, int port, struct addrinfo **res)
1375
{
1376
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1377
    static GList *cache = NULL;
1378
    GList *list;
1379
    struct addrinfo hints;
1380
    int n = 0;
1381
    char str_port[16];
1382
    struct _dns_cache_entry *entry;
1383

                
1384
    g_return_val_if_fail(hostname != NULL, EAI_FAIL);
1385
    g_return_val_if_fail(port > 0, EAI_FAIL);
1386
    g_return_val_if_fail(port <= 65535, EAI_FAIL);
1387
    g_return_val_if_fail(res != NULL, EAI_FAIL);
1388

                
1389
    *res = NULL;
1390

                
1391
    /* Search the DNS cache for a match first */
1392
    g_static_mutex_lock(&mutex);
1393
    list = cache;
1394
    while (list) {
1395
        entry = list->data;
1396

                
1397
        if (!strcmp(entry->hostname, hostname)) {
1398
            *res = entry->res;
1399
            n = entry->n;
1400

                
1401
            g_static_mutex_unlock(&mutex);
1402

                
1403
            return n;
1404
        }
1405

                
1406
        list = g_list_next(list);
1407
    }
1408

                
1409
    /* DNS entry wasn't found in the cache yet. Perform a DNS resolve */
1410
    entry = g_slice_new0(struct _dns_cache_entry);
1411
    strncpy(entry->hostname, hostname, sizeof(entry->hostname) -1);
1412
    cache = g_list_append(cache, entry);
1413

                
1414
    memset(&hints, 0, sizeof(struct addrinfo));
1415

                
1416
    hints.ai_family = AF_UNSPEC;
1417
    hints.ai_socktype = SOCK_STREAM;
1418

                
1419
    memset(&str_port, 0, sizeof(str_port));
1420
    snprintf(str_port, sizeof(str_port) - 1, "%i", port);
1421
    entry->n = getaddrinfo(hostname, str_port, &hints, &entry->res);
1422

                
1423
    *res = entry->res;
1424
    n = entry->n;
1425

                
1426
    g_static_mutex_unlock(&mutex);
1427

                
1428
    return n;
1429
}
1430

                
1431
NNTPConnectionErrCode
1432
nntpconnection_connect_to_server(NNTPConnectionInfo *conn, char **errmsg)
1433
{
1434
    static gboolean traffic_monitor_started = FALSE;
1435
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1436
    struct addrinfo *res = NULL;
1437
    int n;
1438
    int last_errno;
1439
#ifdef WIN32
1440
    int tv;
1441
    u_long ioctlArg;
1442
#else
1443
    struct timeval tv;
1444
    int mode;
1445
#endif
1446
    NNTPDisconnectType disconnect_type;
1447
    char *proxy_host = NULL;
1448
    int proxy_port = 0;
1449

                
1450
    g_return_val_if_fail(conn != NULL, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1451
    g_return_val_if_fail(conn->poll_fd.fd == -1, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1452
    g_return_val_if_fail(strlen(conn->server_info.hostname) > 0, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1453
    g_return_val_if_fail(conn->server_info.port > 0, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1454

                
1455
    g_static_mutex_lock(&mutex);
1456
    if (!traffic_monitor_started) {
1457
        traffic_monitor_started = TRUE;
1458
        memset(&bytes_received, 0, sizeof(bytes_received));
1459

                
1460
        /* TODO: implement a nice clean up */
1461
        abort_traffic_thread = FALSE;
1462
        /*traffic_thread = */g_thread_create(traffic_thread_func, NULL, TRUE, NULL);
1463
    }
1464
    g_static_mutex_unlock(&mutex);
1465

                
1466
    conn->is_idle = FALSE;
1467
    conn->last_disconnect_stamp = time(NULL);       /* Assume that the connection attempt has failed by default */
1468

                
1469
    get_proxy_settings(conn->server_info.hostname, &proxy_host, &proxy_port);
1470

                
1471
    if (proxy_host) {
1472
        n = resolve_domain_name(proxy_host, proxy_port, &res);
1473
    } else {
1474
        n = resolve_domain_name(conn->server_info.hostname, conn->server_info.port, &res);
1475
    }
1476

                
1477
    if (n != 0) {
1478
        if (errmsg) {
1479
            *errmsg = g_strdup(gai_strerror(n));
1480
        }
1481
        return NNTP_CONNECTION_ERROR_HOST_NOT_FOUND;
1482
    }
1483

                
1484
    last_errno = 0;
1485

                
1486
    while (res) {
1487
        int ret;
1488

                
1489
        conn->poll_fd.fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
1490

                
1491
#ifdef WIN32
1492
        if ((conn->poll_fd.fd == INVALID_SOCKET)) {
1493
#else
1494
        if ((conn->poll_fd.fd == -1)) {
1495
#endif
1496
            // The socket couldn't be created
1497
            // Save the errno and try the next item in the list
1498
#ifdef WIN32
1499
            last_errno = WSAGetLastError();
1500
#else
1501
            last_errno = errno;
1502
#endif
1503
            res = res->ai_next;
1504
            conn->poll_fd.fd = -1;
1505

                
1506
            if (errmsg) {
1507
                if (*errmsg) {
1508
                    g_free(*errmsg);
1509
                }
1510

                
1511
#ifdef WIN32
1512
                *errmsg = WSAGetStrError(last_errno);
1513
#else
1514
                *errmsg = g_strdup(strerror(last_errno));
1515
#endif
1516
            }
1517

                
1518
            continue;
1519
        }
1520

                
1521
        // Set the connection timeout on the socket
1522
#ifdef WIN32
1523
        tv = 5000;   /* F*cking Winsock uses an integer for the timeout and it's value is in milliseconds */
1524

                
1525
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_RCVTIMEO, (char*) &tv, sizeof(tv));
1526
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_SNDTIMEO, (char*) &tv, sizeof(tv));
1527
#else
1528
        tv.tv_sec = 5;
1529
        tv.tv_usec = 0;
1530

                
1531
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_RCVTIMEO, (const void*) &tv, sizeof(tv));
1532
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_SNDTIMEO, (const void*) &tv, sizeof(tv));
1533
#endif
1534

                
1535
        // Try to connect to the server
1536
        nntpgrab_core_emit_connecting(FALSE, conn->server_info.servername, conn->poll_fd.fd);
1537

                
1538
        // Mark the socket as non-blocking
1539
#ifdef WIN32
1540
        ioctlArg = 1;
1541
        ioctlsocket(conn->poll_fd.fd, FIONBIO, &ioctlArg);
1542
#else
1543
        mode = fcntl(conn->poll_fd.fd, F_GETFL, 0);
1544
        fcntl(conn->poll_fd.fd, F_SETFL, mode | O_NONBLOCK);
1545
#endif
1546

                
1547
        ret = connect(conn->poll_fd.fd, res->ai_addr, (int) res->ai_addrlen);
1548
        if (ret == 0) {
1549
            // Connection succesfull
1550
            last_errno = 0;
1551
            break;
1552
#ifndef WIN32
1553
        } else if (errno == EINPROGRESS) {
1554
            // Wait for at most 5 seconds
1555
            socklen_t len;
1556

                
1557
            time_t now = time(NULL);
1558
            do {
1559
                // According to https://cr.yp.to/docs/connect.html there are various
1560
                // ways to detect if a socket is connected. The getpeername() method
1561
                // should be the most portable one
1562
                if (res->ai_family == AF_INET6) {
1563
                    struct sockaddr_in6 name;
1564
                    len = sizeof(name);
1565
                    if (getpeername(conn->poll_fd.fd, (struct sockaddr*) &name, &len) == 0) {
1566
                        errno = 0;
1567
                        break;
1568
                    }
1569
                } else {
1570
                    struct sockaddr_in name;
1571
                    len = sizeof(name);
1572
                    if (getpeername(conn->poll_fd.fd, (struct sockaddr*) &name, &len) == 0) {
1573
                        errno = 0;
1574
                        break;
1575
                    }
1576
                }
1577

                
1578
                g_usleep(G_USEC_PER_SEC / 10);
1579

                
1580
                if (download_thread_get_state() != SCHEDULAR_STATE_RUNNING) {
1581
                    break;
1582
                }
1583
            } while (now + 5 > time(NULL));
1584

                
1585
            if (errno == 0) {
1586
                last_errno = 0;
1587
                break;
1588
            }
1589
#else
1590
        } else if (WSAGetLastError() == WSAEWOULDBLOCK) {
1591
            // And Windows again has different behaviour....
1592
            struct timeval tv;
1593
            fd_set send_fds;
1594
            int len;
1595

                
1596
            FD_ZERO(&send_fds);
1597
            FD_SET(conn->poll_fd.fd, &send_fds);
1598

                
1599
            tv.tv_sec = 5;
1600
            tv.tv_usec = 0;
1601

                
1602
            len = select(conn->poll_fd.fd + 1, NULL, &send_fds, NULL, &tv);
1603
            if (len <= 0) {
1604
                // Read timeout or some other error
1605
                if (len == 0) {
1606
                    errno = WSAETIMEDOUT;
1607
                }
1608
            } else {
1609
                // Connection is made
1610
                last_errno = 0;
1611
                break;
1612
            }
1613
#endif
1614
        }
1615

                
1616
        // Connection could not be made, save the errno
1617
#ifndef WIN32
1618
        if (errno == EINPROGRESS) {
1619
            // The connection could not be established within the connect time limit.
1620
            // This provides a more clear error message
1621
            last_errno = ETIMEDOUT;
1622
        } else
1623
#endif
1624
        {
1625
            last_errno = errno;
1626
        }
1627

                
1628
        if (errmsg) {
1629
            if (*errmsg) {
1630
                g_free(*errmsg);
1631
            }
1632

                
1633
#ifdef WIN32
1634
            *errmsg = WSAGetStrError(last_errno);
1635
#else
1636
            *errmsg = g_strdup(strerror(last_errno));
1637
#endif
1638
        }
1639

                
1640
#if WIN32
1641
        if (last_errno == WSAETIMEDOUT) {
1642
#else
1643
        if (last_errno == ETIMEDOUT) {
1644
#endif
1645
            disconnect_type = DISCONNECT_CONNECT_TIMEOUT;
1646
        } else {
1647
            disconnect_type = DISCONNECT_CONNECTION_REFUSED;
1648
        }
1649

                
1650
        nntpconnection_disconnect_from_server(conn, disconnect_type, strerror(last_errno), __FILE__, __LINE__);
1651

                
1652
        res = res->ai_next;
1653
    }
1654

                
1655
    if (conn->poll_fd.fd == -1) {
1656
        switch (last_errno) {
1657
#if WIN32
1658
            case WSAETIMEDOUT:
1659
#else
1660
            case ETIMEDOUT:
1661
#endif
1662
                return NNTP_CONNECTION_ERROR_CONNECTION_TIMEOUT;
1663

                
1664
            default:
1665
                return NNTP_CONNECTION_ERROR_CONNECTION_REFUSED;
1666
        };
1667
    }
1668

                
1669
    // Mark the socket as blocking
1670
#ifdef WIN32
1671
        ioctlArg = 0;
1672
        ioctlsocket(conn->poll_fd.fd, FIONBIO, &ioctlArg);
1673
#else
1674
        mode = fcntl(conn->poll_fd.fd, F_GETFL, 0);
1675
        fcntl(conn->poll_fd.fd, F_SETFL, mode  ^ O_NONBLOCK);
1676
#endif
1677

                
1678
    if (conn->server_info.use_ssl && !proxy_host) {
1679
        if ((conn->ssl = prepare_ssl_connection(conn->poll_fd.fd, errmsg)) == NULL) {
1680
            nntpconnection_disconnect_from_server(conn, DISCONNECT_ERROR_SSL_INITIALISE, *errmsg, __FILE__, __LINE__);
1681
            return NNTP_CONNECTION_ERROR_SSL_INITIALISE;
1682
        }
1683
    }
1684

                
1685
    if (proxy_host) {
1686
        char cmd[128];
1687

                
1688
        memset(&cmd, 0, sizeof(cmd));
1689
        snprintf(cmd, sizeof(cmd) - 1, "CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n\r\n", conn->server_info.hostname, conn->server_info.port, conn->server_info.hostname, conn->server_info.port);
1690
        send(conn->poll_fd.fd, cmd, strlen(cmd), 0);
1691

                
1692
        g_free(proxy_host);
1693

                
1694
        conn->active_method = nntpconnection_process_proxy_connect_command;
1695
    } else {
1696
        conn->active_method = nntpconnection_process_welcome_msg;
1697
    }
1698
    memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
1699
    conn->recv_buffer_length = 0;
1700
    conn->last_activity_stamp = time(NULL);
1701
    conn->last_disconnect_stamp = 0;
1702

                
1703
    return NNTP_CONNECTION_ERROR_NONE;
1704
    //return login(username, password, errmsg, conn->poll_fd.fd, data);
1705
}
1706

                
1707
void
1708
nntpconnection_disconnect_from_server(NNTPConnectionInfo *conn, NNTPDisconnectType disconnect_type, const char *reason, const char *cause_file, int cause_lineno)
1709
{
1710
    g_return_if_fail(conn != NULL);
1711
    g_return_if_fail(conn->poll_fd.fd >= 0);
1712
    g_return_if_fail(cause_file != NULL);
1713

                
1714
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "Request received to disconnect connection %i, disconnect_type = %i, reason = %s, cause = %s:%i, recv_buf_len = %i, recv_buf=%s", conn->poll_fd.fd, disconnect_type, reason, cause_file, cause_lineno, conn->recv_buffer_length, conn->recv_buffer);
1715

                
1716
#ifdef WIN32
1717
    /* DisconnectEx(conn->poll_fd.fd, NULL, 0, 0); * MinGW doesn't have DisconnectEx exported... */
1718
    shutdown(conn->poll_fd.fd, SD_BOTH);            /* The shutdown() function is broken on Win32, see 
1719
                                                     * https://msdn.microsoft.com/en-us/library/ms738547%28VS.85%29.aspx
1720
                                                     * for details*/
1721
    closesocket(conn->poll_fd.fd);
1722
#else
1723
    shutdown(conn->poll_fd.fd, SHUT_RDWR);
1724
    close(conn->poll_fd.fd);
1725
#endif
1726

                
1727
    if (conn->collection && conn->file && conn->part) {
1728
        /* Update the file status */
1729
        download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
1730
    }
1731

                
1732
    nntpgrab_core_emit_disconnect(FALSE, conn->server_info.servername, conn->poll_fd.fd, disconnect_type, reason);
1733

                
1734
    conn->poll_fd.fd = -1;
1735
    conn->is_idle = TRUE;
1736
    if (disconnect_type == DISCONNECT_NORMAL) {
1737
        conn->last_disconnect_stamp = 0;
1738
    } else {
1739
        conn->last_disconnect_stamp = time(NULL);
1740
    }
1741
    conn->last_activity_stamp = 0;
1742

                
1743
    if (conn->collection) {
1744
        collection_unref(conn->collection);
1745
        conn->collection = NULL;
1746
    }
1747

                
1748
    if (conn->file) {
1749
        file_unref(conn->file);
1750
        conn->file = NULL;
1751
    }
1752

                
1753
    if (conn->part) {
1754
        conn->part = NULL;
1755
    }
1756

                
1757
    conn->job_type = NNTP_JOB_TYPE_NONE;
1758

                
1759
    if (conn->article_fd >= 0) {
1760
        close(conn->article_fd);
1761
        conn->article_fd = -1;
1762
    }
1763
}
1764

                
1765
void
1766
nntpconnection_process_socket_activity(NNTPConnectionInfo *conn)
1767
{
1768
    g_return_if_fail(conn != NULL);
1769

                
1770
    if (!conn->active_method) {
1771
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "NNTP connection to server '%s' (socket_id %i) is in an undefined state", conn->server_info.servername, conn->poll_fd.fd);
1772
        nntpconnection_disconnect_from_server(conn, DISCONNECT_UNEXPECTED, _("NNTP Connection is in an undefined state"), __FILE__, __LINE__);
1773
        return;
1774
    }
1775

                
1776
    conn->active_method(conn);
1777
    conn->last_activity_stamp = time(NULL);
1778
}