Statistics
| Revision:

root / trunk / nntpgrab_core / nntpconnection.c @ 1798

History | View | Annotate | Download (56.3 KB)

1
/* 
2
    Copyright (C) 2005-2010  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 volatile int bytes_received_in_last_sec;
65
static gboolean abort_traffic_thread = FALSE;
66

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

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

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

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

                
92
    return atoi(line);
93
}
94

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

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

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

                
115
    LocalFree(lpMsgBuf);
116

                
117
    return ret;
118
}
119

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

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

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

                
144
gpointer
145
traffic_thread_func(gpointer data)
146
{
147
    int bytes_received[10];
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(&bytes_received, 0, sizeof(bytes_received));
156
    memset(empty_buf, 0, sizeof(empty_buf));
157

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

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

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

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

                
176
            /* This is not fully atomic, but will do for our goal */
177
            val = g_atomic_int_get(&bytes_received_in_last_sec);           
178
            bytes_received[9] = g_atomic_int_exchange_and_add(&bytes_received_in_last_sec, -val);
179
            if (bytes_received[9] < 0) {
180
                bytes_received[9] = 0;
181
            }
182
            memcpy(bytes_received_copy, bytes_received, sizeof(bytes_received));
183

                
184
            // Shift all the previous values by 'diff'
185
            memmove(bytes_received, &bytes_received[diff], (10 - diff) * sizeof(int));
186
            memset(&bytes_received[10 - diff], 0, diff * sizeof(int));
187

                
188
            gettimeofday(&tv_last_traffic_monitor_flush, NULL);
189

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

                
202
#if 0 
203
            g_print("stamp = %li\n", timestamp);
204
            for (i = 0; i < 10; i++) {
205
                g_print("bytes_received[%i] = %i\n", i, bytes_received[i]);
206
            }
207
            g_print("\n");
208
#endif
209
        }
210

                
211
        g_usleep(G_USEC_PER_SEC * 1);
212
    }
213

                
214
    return NULL;
215
}
216

                
217
static void
218
update_traffic_monitor(int bytes_read)
219
{
220
    g_atomic_int_add(&bytes_received_in_last_sec, bytes_read);
221
}
222

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

                
234
    g_return_val_if_fail(hostname != NULL, FALSE);
235
    g_return_val_if_fail(proxy_host != NULL, FALSE);
236
    g_return_val_if_fail(proxy_port != NULL, FALSE);
237

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

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

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

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

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

                
265
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "proxies[0] = %s", proxies[0]);
266

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

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

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

                
287
    *proxy_host = g_strdup(values[0]);
288
    *proxy_port = atoi(values[1]);
289

                
290
    g_strfreev(values);
291

                
292
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Returned proxy '%s:%i' for host '%s'", *proxy_host, *proxy_port, hostname);
293

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

                
301
void
302
nntpconnection_init_libproxy(void)
303
{
304
    char *proxy_host = NULL;
305
    int proxy_port = 0;
306

                
307
    /* To avoid the situation where libproxy calls some X11 functions from a different 
308
     * thread than the main loop we initialize libproxy here by performing a dummy query */
309
    if (get_proxy_settings("localhost", &proxy_host, &proxy_port)) {
310
        g_free(proxy_host);
311
    }
312
}
313

                
314
static gboolean
315
perform_recv(NNTPConnectionInfo *conn)
316
{
317
    int retval;
318
#ifdef WIN32
319
    char *errmsg = NULL;
320
#endif
321

                
322
    g_return_val_if_fail(conn != NULL, FALSE);
323

                
324
#ifdef HAVE_SSL
325
#ifdef HAVE_GNUTLS
326
    if (conn->server_info.use_ssl) {
327
        retval = gnutls_record_recv(conn->ssl_session, conn->recv_buffer + conn->recv_buffer_length, sizeof(conn->recv_buffer) - conn->recv_buffer_length - 1);
328
        if (retval < 0) {
329
            switch (retval) {
330
                case GNUTLS_E_INTERRUPTED:
331
                case GNUTLS_E_AGAIN:
332
                    retval = -1;
333
#ifdef WIN32
334
                    WSASetLastError(WSAEWOULDBLOCK);
335
#else
336
                    errno = EAGAIN;
337
#endif
338
                    break;
339

                
340
                default:
341
                    g_print(__FILE__ ":%i gnutls_record_recv returned unknown value: %s (%i)\n", __LINE__, gnutls_strerror(retval), retval);
342
                    retval = 0;
343
                    break;
344
            }
345
        }
346
    } else
347
#else
348
    if (conn->ssl) {
349
        int len = MIN(sizeof(conn->recv_buffer) - conn->recv_buffer_length - 1, 16384);     /* SSL packets are at most 16KB large */
350
        retval = SSL_read(conn->ssl, conn->recv_buffer + conn->recv_buffer_length, len);
351
        if (retval < 0) {
352
            switch (SSL_get_error(conn->ssl, retval)) {
353
                case SSL_ERROR_WANT_READ:
354
                case SSL_ERROR_WANT_WRITE:
355
#ifdef HAVE_NSS_COMPAT_OSSL
356
                case PR_PENDING_INTERRUPT_ERROR:
357
#endif
358
                    retval = -1;
359
#ifdef WIN32
360
                    WSASetLastError(WSAEWOULDBLOCK);
361
#else
362
                    errno = EAGAIN;
363
#endif
364
                    break;
365

                
366
                case SSL_ERROR_SSL:
367
                    retval = -1;
368
#ifdef WIN32
369
                    errno = ENOSYS;
370
#else
371
                    errno = EPROTO;
372
#endif
373
                    break;
374

                
375
                default:
376
                    g_print("Unknown SSL error detected on socket %i: %i\n", conn->poll_fd.fd, SSL_get_error(conn->ssl, retval));
377
                    break;
378
            }
379
        }
380
    } else
381
#endif
382
#endif
383
    {
384
        retval = recv(conn->poll_fd.fd, conn->recv_buffer + conn->recv_buffer_length, sizeof(conn->recv_buffer) - conn->recv_buffer_length - 1, MSG_DONTWAIT);
385
    }
386

                
387
    switch (retval) {
388
        case -1:            /* Some error occured */
389
#ifdef WIN32
390
            if (WSAGetLastError() == WSAEWOULDBLOCK) {
391
#else
392
            if (errno == EAGAIN) {
393
#endif
394
                /* Try again right now */
395
                //g_print("EAGAIN detected on socket %i (SSL = %i)\n", conn->poll_fd.fd, conn->server_info.use_ssl);
396
                //return perform_recv(conn);
397
                return FALSE;
398
            }
399

                
400
#ifdef WIN32
401
            errmsg = WSAGetStrError(WSAGetLastError());
402
            nntpconnection_disconnect_from_server(conn, DISCONNECT_READ_ERROR, errmsg, __FILE__, __LINE__);
403
            g_free(errmsg);
404
#else
405
            nntpconnection_disconnect_from_server(conn, DISCONNECT_READ_ERROR, strerror(errno), __FILE__, __LINE__);
406
#endif
407

                
408
            return FALSE;
409

                
410
        case 0:             /* Orderly shutdown has occured */
411
            conn->active_method = NULL;
412
            return FALSE;
413

                
414
        default:
415
            conn->recv_buffer_length += retval;
416

                
417
            /* Perform traffic shaping if necessary */
418
            if (conn->max_bandwidth > 0) {
419
                /* Calculate the sum of the number of bytes received in the last 10 seconds */
420
                int num_bytes_received = 0;
421

                
422
                num_bytes_received = g_atomic_int_get(&bytes_received_in_last_sec);
423
                num_bytes_received += retval;
424
                throttle_pause(tv_last_traffic_monitor_flush, num_bytes_received, conn->max_bandwidth);
425
            }
426

                
427
#ifdef HAVE_GNUTLS
428
            if (!conn->server_info.use_ssl)
429
#endif
430
                update_traffic_monitor(retval);
431

                
432
            break;
433
    }
434

                
435
// nntpgrab_core_emit_debug_message(FALSE, "Received data from socket %i: %s", conn->connection_id, data);
436

                
437
    return TRUE;
438
}
439

                
440
static gboolean
441
nntpconnection_read_msg(NNTPConnectionInfo *conn, gboolean read_line, int max_length, void *data, int *data_length, gboolean *more_data_ready)
442
{
443
    int len;
444

                
445
    g_return_val_if_fail(conn != NULL, FALSE);
446
    g_return_val_if_fail(max_length > 0, FALSE);
447
    g_return_val_if_fail(data != NULL, FALSE);
448
    /* NOTE: data_length MIGHT be NULL */
449
    /* NOTE: more_data_ready MIGHT be NULL */
450

                
451
    if (more_data_ready) {
452
        *more_data_ready = FALSE;
453
    }
454

                
455
    /* Avoid a buffer overflow */
456
    /* This situation can happen when a line is really long */
457
    if (conn->recv_buffer_length >= sizeof(conn->recv_buffer) - 1) {
458
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "nntpconnection_read_msg: Very long line received from server. Ignoring");
459
        memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
460
        conn->recv_buffer_length = 0;
461
    }
462

                
463
    /* Only read from the socket when there's no newline in the buffer */
464
    if (!memchr(conn->recv_buffer, '\n', conn->recv_buffer_length)) {
465
        if (!perform_recv(conn)) {
466
            return FALSE;
467
        }
468

                
469
        /* Does the buffer contain a newline now? */
470
        if (!memchr(conn->recv_buffer, '\n', conn->recv_buffer_length)) {
471
            /* Try again later */
472
            return FALSE;
473
        }
474
    }
475

                
476
    g_return_val_if_fail(conn->recv_buffer_length > 0, FALSE);
477

                
478
    /* Calculate the size of the buffer which needs to be sent back to the caller */
479
    char *newline = memchr(conn->recv_buffer, '\n', conn->recv_buffer_length);
480
    if (!newline) {
481
        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);
482
        return FALSE;
483
    }
484

                
485
    len = newline - conn->recv_buffer + 1;
486
    if (len > max_length) {
487
        /* We're going to have to trim some data */
488
        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);
489
        len = max_length;
490
    }
491

                
492
    memcpy(data, conn->recv_buffer, len);
493

                
494
    if (read_line) {
495
        strip_newline(data);
496
    }
497

                
498
    if (len == conn->recv_buffer_length) {
499
        /* Everything in the buffer was sent */
500
        conn->recv_buffer_length = 0;
501
        memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
502
    } else {
503
        /* Only part of the buffer was sent */
504
        memmove(conn->recv_buffer, conn->recv_buffer + len, sizeof(conn->recv_buffer) - len);
505
        conn->recv_buffer_length -= len;
506
        conn->recv_buffer[conn->recv_buffer_length] = '\0';
507

                
508
        g_return_val_if_fail(conn->recv_buffer_length >= 0, FALSE);
509

                
510
        if (more_data_ready) {
511
            *more_data_ready = (memchr(conn->recv_buffer, '\n', conn->recv_buffer_length) != NULL);
512
        }
513
    }
514

                
515
    memset(conn->recv_buffer2, 0, sizeof(conn->recv_buffer2));
516
    memcpy(conn->recv_buffer2, data, len);
517
    conn->recv_buffer_length2 = len;
518

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

                
523
    return TRUE;
524
}
525

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

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

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

                
539
    do {
540
#ifdef HAVE_SSL
541
#ifdef HAVE_GNUTLS
542
        if (conn->server_info.use_ssl) {
543
            retval = gnutls_record_send(conn->ssl_session, data + bytes_sent, length - bytes_sent);
544
        } else
545
#else
546
        if (conn->ssl) {
547
            retval = SSL_write(conn->ssl, data + bytes_sent, length - bytes_sent);
548
            if (retval < 0) {
549
                switch (SSL_get_error(conn->ssl, retval)) {
550
                    case SSL_ERROR_WANT_READ:
551
                    case SSL_ERROR_WANT_WRITE:
552
                        retval = -1;
553
#ifdef WIN32
554
                        WSASetLastError(WSAEWOULDBLOCK);
555
#else
556
                        errno = EAGAIN;
557
#endif
558
                        break;
559

                
560
                    case SSL_ERROR_SSL:
561
                        retval = -1;
562
#ifdef WIN32
563
                        errno = ENOSYS;
564
#else
565
                        errno = EPROTO;
566
#endif
567
                        break;
568

                
569
                    default:
570
                        break;
571
                }
572
            }
573
        } else
574
#endif
575
#endif
576
        {
577
            retval = send(conn->poll_fd.fd, data + bytes_sent, length - bytes_sent, 0);
578
        }
579

                
580
        switch (retval) {
581
            case -1:            /* Some error occured */
582
                nntpconnection_disconnect_from_server(conn, DISCONNECT_WRITE_ERROR, strerror(errno), __FILE__, __LINE__);
583
                return FALSE;
584

                
585
            case 0:             /* Orderly shutdown has occured */
586
                conn->active_method = NULL;
587
                return FALSE;
588

                
589
            default:
590
                bytes_sent += retval;
591

                
592
                if (bytes_sent == length) {
593
                    return TRUE;
594
                }
595

                
596
                break;
597
        }
598
    } while (TRUE);
599

                
600
    g_return_val_if_reached(TRUE);
601
}
602

                
603
/*************************************/
604
/* Initialization and authentication */
605
/*************************************/
606

                
607
static void nntpconnection_login_username_sent(NNTPConnectionInfo *conn);
608
static void nntpconnection_login_password_sent(NNTPConnectionInfo *conn);
609
static void nntpconnection_login_set_mode_reader(NNTPConnectionInfo *conn);
610
static void nntpconnection_login_process_mode_reader(NNTPConnectionInfo *conn);
611
static void nntpconnection_process_body_command(NNTPConnectionInfo *conn);
612
static void nntpconnection_process_body_data(NNTPConnectionInfo *conn);
613
static void nntpconnection_send_group_command(NNTPConnectionInfo *conn);
614
static void nntpconnection_process_group_command(NNTPConnectionInfo *conn);
615
static void nntpconnection_send_xover_command(NNTPConnectionInfo *conn);
616
static void nntpconnection_process_xover_command(NNTPConnectionInfo *conn);
617
static void nntpconnection_process_xover_data(NNTPConnectionInfo *conn);
618

                
619
static void
620
nntpconnection_process_welcome_msg(NNTPConnectionInfo *conn)
621
{
622
    char welcome_msg[4096];
623

                
624
    memset(welcome_msg, 0, sizeof(welcome_msg));
625

                
626
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(welcome_msg) - 1, welcome_msg, NULL, NULL)) {
627
        return;
628
    }
629

                
630
    strip_newline(welcome_msg);
631

                
632
    switch (get_status_code(welcome_msg)) {
633
        case 200:
634
        case 201:
635
            /* Welcome message is OK */
636
            nntpgrab_core_emit_connected(FALSE, conn->server_info.servername, conn->connection_id, welcome_msg);
637
            break;
638

                
639
        default:
640
            /* Welcome message is NOT ok, probably too many connections from this IP address */
641
            nntpconnection_disconnect_from_server(conn, DISCONNECT_TOO_MANY_CONNECTIONS, welcome_msg, __FILE__, __LINE__);
642

                
643
            return;
644
    }
645

                
646
    /* Do we need to send a username and password to the server? */
647
    if (strlen(conn->server_info.username) > 0) {
648
        char buf[1024];
649
        memset(buf, 0, sizeof(buf));
650

                
651
        snprintf(buf, sizeof(buf) - 1, "AUTHINFO USER %s\r\n", conn->server_info.username);
652
        if (!nntpconnection_send_msg(conn, buf, strlen(buf))) {
653
            return;
654
        }
655

                
656
        conn->active_method = nntpconnection_login_username_sent;
657
    } else {
658
        /* We don't need to log in. Prepare the connection for further communication */
659
        nntpconnection_login_set_mode_reader(conn);
660
    }
661
}
662

                
663
static void
664
nntpconnection_login_username_sent(NNTPConnectionInfo *conn)
665
{
666
    char buf[1024];
667
    memset(buf, 0, sizeof(buf));
668

                
669
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, NULL)) {
670
        return;
671
    }
672

                
673
    if (get_status_code(buf) != 381) {
674
        /* Unexpected response code. Abort the connection */
675
        strip_newline(buf);
676
        nntpconnection_disconnect_from_server(conn, DISCONNECT_LOGIN_FAILURE, buf, __FILE__, __LINE__);
677
        return;
678
    }
679

                
680
    if (strlen(conn->server_info.password) > 0) {
681
        memset(buf, 0, sizeof(buf));
682
        snprintf(buf, sizeof(buf) - 1, "AUTHINFO PASS %s\r\n", conn->server_info.password);
683
        if (!nntpconnection_send_msg(conn, buf, strlen(buf))) {
684
            return;
685
        }
686

                
687
        conn->active_method = nntpconnection_login_password_sent;
688
    } else {
689
        /* No password configured for this server. Try to prepare the connection for further communication */
690
        nntpconnection_login_set_mode_reader(conn);
691
    }
692
}
693

                
694
static void
695
nntpconnection_login_password_sent(NNTPConnectionInfo *conn)
696
{
697
    char buf[1024];
698
    memset(buf, 0, sizeof(buf));
699

                
700
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, NULL)) {
701
        return;
702
    }
703

                
704
    if (get_status_code(buf) != 281) {
705
        /* Unexpected response code. Abort the connection */
706
        strip_newline(buf);
707
        nntpconnection_disconnect_from_server(conn, DISCONNECT_LOGIN_FAILURE, buf, __FILE__, __LINE__);
708
        return;
709
    }
710

                
711
    /* Login OK, prepare the connection for further communication */
712
    nntpconnection_login_set_mode_reader(conn);
713
}
714

                
715
static void
716
nntpconnection_login_set_mode_reader(NNTPConnectionInfo *conn)
717
{
718
    if (!nntpconnection_send_msg(conn, "MODE READER\r\n", -1)) {
719
        return;
720
    }
721

                
722
    conn->active_method = nntpconnection_login_process_mode_reader;
723
}
724

                
725
static void
726
nntpconnection_login_process_mode_reader(NNTPConnectionInfo *conn)
727
{
728
    char buf[1024];
729
    memset(buf, 0, sizeof(buf));
730

                
731
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, NULL)) {
732
        return;
733
    }
734

                
735
    switch (get_status_code(buf)) {
736
        case 200:
737
        case 201:
738
            /* Everything's ready! */
739
            break;
740

                
741
        case 480:
742
            /* Some usenet servers use this to notify there are too many connections active */
743
            strip_newline(buf);
744
            nntpconnection_disconnect_from_server(conn, DISCONNECT_TOO_MANY_CONNECTIONS, buf, __FILE__, __LINE__);
745
            return;
746

                
747
        default:
748
            /* Unexpected response code. Abort the connection */
749
            strip_newline(buf);
750
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
751
            return;
752
    }
753

                
754
    /* The connection is now fully operational. Check if we need to download something */
755
    if (conn->job_type == NNTP_JOB_TYPE_ARTICLE) {
756
        nntpconnection_send_body_command(conn);
757
    } else if (conn->job_type == NNTP_JOB_TYPE_XOVER) {
758
        nntpconnection_send_xover_command(conn);
759
    } else {
760
        conn->active_method = NULL;
761
        conn->is_idle = TRUE;
762
        conn->idle_start_stamp = time(NULL);
763
    }
764
}
765

                
766
/**********************************************************/
767
/* body / article */
768
/**********************************************************/
769
void
770
nntpconnection_send_body_command(NNTPConnectionInfo *conn)
771
{
772
    char cmd[1024];
773

                
774
    g_return_if_fail(conn != NULL);
775
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
776
    g_return_if_fail(conn->collection != NULL);
777
    g_return_if_fail(conn->file != NULL);
778
    g_return_if_fail(conn->part != NULL);
779
    g_return_if_fail(conn->article_fd == -1);
780
    g_return_if_fail(strlen(conn->article_filename) > 0);
781

                
782
    nntpgrab_core_emit_part_download_start(FALSE, conn->server_info.servername, conn->connection_id, conn->collection->collection_name, conn->file->subject, conn->part->part_num);
783

                
784
    memset(cmd, 0, sizeof(cmd));
785
    snprintf(cmd, sizeof(cmd) - 1, "BODY %s\r\n", conn->part->message_id);
786
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
787
        return;
788
    }
789

                
790
    conn->active_method = nntpconnection_process_body_command;
791
    conn->article_bytes_downloaded = 0;
792
    conn->article_write_buffer_length = 0;
793
}
794

                
795
static void
796
nntpconnection_process_body_command(NNTPConnectionInfo *conn)
797
{
798
    gboolean more_data_ready = FALSE;
799
    char buf[1024];
800
    memset(buf, 0, sizeof(buf));
801

                
802
    g_return_if_fail(conn != NULL);
803
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
804
    g_return_if_fail(conn->collection != NULL);
805
    g_return_if_fail(conn->file != NULL);
806
    g_return_if_fail(conn->part != NULL);
807
    g_return_if_fail(conn->article_fd == -1);
808
    g_return_if_fail(strlen(conn->article_filename) > 0);
809

                
810
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
811
        return;
812
    }
813

                
814
    switch (get_status_code(buf)) {
815
        case 222:                       /* article retrieved - body follows */
816
            /* Open the file where we need to save our data to */
817
            conn->article_fd = open(conn->article_filename, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
818
            if (conn->article_fd == -1) {
819
                download_thread_abort_without_waiting(_("Unable to create a file named '%s': %s"), conn->article_filename, strerror(errno));
820
                conn->active_method = NULL;
821
                return;
822
            }
823

                
824
            conn->active_method = nntpconnection_process_body_data;
825

                
826
            if (more_data_ready) {
827
                conn->active_method(conn);
828
            }
829

                
830
            return;
831

                
832
        case 430:                       /* no such article found */
833
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, TRUE);
834

                
835
            collection_unref(conn->collection);
836
            file_unref(conn->file);
837
            conn->collection = NULL;
838
            conn->file = NULL;
839
            conn->part = NULL;
840
            conn->job_type = NNTP_JOB_TYPE_NONE;
841

                
842
            conn->active_method = NULL;
843
            conn->is_idle = TRUE;
844
            conn->idle_start_stamp = time(NULL);
845

                
846
            return;
847

                
848
        case 220:                       /* article retrieved - head and body follow */
849
        case 221:                       /* article retrieved - head follows */
850
        case 223:                       /* article retrieved - request text separately */
851
        case 412:                       /* no newsgroup has been selected */
852
        case 420:                       /* no current article has been selected */
853
        case 423:                       /* no such article number in this group */
854
        default:
855
            /* Unexpected response code. Abort the connection */
856
            strip_newline(buf);
857
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
858
            return;
859
    }
860
}
861

                
862
static void
863
trim_double_dots(NNTPConnectionInfo *conn, char *buf, int *length)
864
{
865
    g_return_if_fail(conn != NULL);
866
    g_return_if_fail(buf != NULL);
867
    g_return_if_fail(length != NULL);
868
    g_return_if_fail(*length > 0);
869

                
870
    if (*length >= 2 && buf[0] == '.' && buf[1] == '.') {
871
        memmove(buf, buf + 1, *length - 1);
872
        buf[*length - 1] = '\0';
873
        (*length)--;
874
    }
875
}
876

                
877
static void
878
nntpconnection_process_body_data(NNTPConnectionInfo *conn)
879
{
880
    int length = 0;
881
    char buf[65536];
882
    gboolean more_data_ready = TRUE;
883
    struct timeval tv;
884
    struct timeval tv_diff;
885

                
886
    g_return_if_fail(conn != NULL);
887
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
888
    g_return_if_fail(conn->part != NULL);
889
    g_return_if_fail(conn->article_fd >= 0);
890

                
891
    while (more_data_ready) {
892
        memset(buf, 0, sizeof(buf));
893

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

                
896
        if (!nntpconnection_read_msg(conn, FALSE, sizeof(buf) - 1, buf, &length, &more_data_ready)) {
897
            return;
898
        }
899

                
900
        g_return_if_fail(length > 0);
901

                
902
        conn->article_bytes_downloaded += length;
903
        if (conn->article_bytes_downloaded > conn->part->size) {
904
            conn->article_bytes_downloaded = conn->part->size;
905
        }
906

                
907
        gettimeofday(&tv, NULL);
908
        timersub(&tv, &conn->last_article_progress_announce, &tv_diff);
909
        if (tv_diff.tv_sec > 0 || tv_diff.tv_usec > G_USEC_PER_SEC / 10) {
910
            conn->last_article_progress_announce = tv;
911
            nntpgrab_core_emit_part_progress_update(FALSE, conn->server_info.servername, conn->connection_id, conn->collection->collection_name, conn->file->subject, conn->part->part_num, conn->article_bytes_downloaded, conn->part->size);
912
        }
913

                
914
        /* Check for the end sequence */
915
        if ((length == 3 && !strncmp(buf, ".\r\n", 3)) ||
916
            (length == 2 && !strncmp(buf, ".\n", 2))) {
917

                
918
            /* End sequence found! */
919

                
920
            /* Flush the buffers and close the file descriptor */
921
            if ((conn->article_write_buffer_length > 0 && write(conn->article_fd, conn->article_write_buffer, conn->article_write_buffer_length) != conn->article_write_buffer_length)) {
922
                /* Write error! Kill the download thread */
923
                download_thread_abort_without_waiting(_("%s:%i Unable to write article data to file: %s"), __FILE__, __LINE__, strerror(errno));
924

                
925
                /* Mark the part as failed so it will be retried later */
926
                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);
927
                download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
928
            } else {
929
                /* Download and save succeeded */
930
                download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, TRUE, FALSE, TRUE);
931
                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);
932
            }
933

                
934
            close(conn->article_fd);
935
            conn->article_fd = -1;
936

                
937
            conn->article_bytes_downloaded = 0;
938
            conn->article_write_buffer_length = 0;
939

                
940
            memset(conn->article_write_buffer, 0, sizeof(conn->article_write_buffer));
941

                
942
            collection_unref(conn->collection);
943
            file_unref(conn->file);
944
            conn->collection = NULL;
945
            conn->file = NULL;
946
            conn->part = NULL;
947
            conn->job_type = NNTP_JOB_TYPE_NONE;
948
            conn->active_method = NULL;
949
            conn->is_idle = TRUE;
950

                
951
            conn->idle_start_stamp = time(NULL);
952

                
953
            return;
954
        }
955

                
956
        /* Un-escape any double-dots */
957
        trim_double_dots(conn, buf, &length);
958

                
959
        /* Keep the data in a temporary buffer to minimize the amount of I/O */
960
        if (conn->article_write_buffer_length + length > sizeof(conn->article_write_buffer)) {
961
            /* Flush the buffer */
962
            int pos = 0;
963

                
964
            do {
965
                int retval = write(conn->article_fd, conn->article_write_buffer + pos, conn->article_write_buffer_length - pos);
966
                if (retval == -1 || retval == 0) {
967
                    /* Write error! Kill the download thread */
968
                    char msg[1024];
969

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

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

                
976
                    nntpconnection_disconnect_from_server(conn, DISCONNECT_WRITE_ERROR, msg, __FILE__, __LINE__);
977

                
978
                    return;
979
                } else {
980
                    pos += retval;
981
                }
982
            } while (pos != conn->article_write_buffer_length);
983

                
984
            memset(conn->article_write_buffer, 0, sizeof(conn->article_write_buffer));
985
            conn->article_write_buffer_length = 0;
986
        }
987

                
988
        memcpy(conn->article_write_buffer + conn->article_write_buffer_length, buf, length);
989
        conn->article_write_buffer_length += length;
990
    }
991
}
992

                
993
/************************************************************** 
994
 * group / xover
995
 **************************************************************/
996
void
997
nntpconnection_send_group_command(NNTPConnectionInfo *conn)
998
{
999
    char cmd[1024];
1000

                
1001
    g_return_if_fail(conn != NULL);
1002
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1003
    g_return_if_fail(strlen(conn->newsgroup) > 0);
1004

                
1005
    memset(cmd, 0, sizeof(cmd));
1006
    snprintf(cmd, sizeof(cmd) - 1, "GROUP %s\r\n", conn->newsgroup);
1007
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
1008
        return;
1009
    }
1010

                
1011
    conn->active_method = nntpconnection_process_group_command;
1012
}
1013

                
1014
static void
1015
nntpconnection_process_group_command(NNTPConnectionInfo *conn)
1016
{
1017
    gboolean more_data_ready = FALSE;
1018
    char buf[1024];
1019
    memset(buf, 0, sizeof(buf));
1020

                
1021
    g_return_if_fail(conn != NULL);
1022
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1023

                
1024
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
1025
        return;
1026
    }
1027

                
1028
    switch (get_status_code(buf)) {
1029
        case 211:
1030
            /* 211 n f l s group selected 
1031
                    (n = estimated number of articles in group,
1032
                     f = first article number in the group,
1033
                     l = last article number in the group,
1034
                     s = name of the group.)
1035
             */
1036

                
1037
            nntpconnection_send_xover_command(conn);
1038
            return;
1039

                
1040
        case 411:       /* No such group */
1041
            strip_newline(buf);
1042
            nntpconnection_disconnect_from_server(conn, DISCONNECT_NORMAL, buf, __FILE__, __LINE__);
1043
            return;
1044

                
1045
        default:
1046
            /* Unexpected response code. Abort the connection */
1047
            strip_newline(buf);
1048
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1049
            return;
1050
    }
1051
}
1052

                
1053
void
1054
nntpconnection_send_xover_command(NNTPConnectionInfo *conn)
1055
{
1056
    char cmd[1024];
1057

                
1058
    g_return_if_fail(conn != NULL);
1059
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1060
    g_return_if_fail(strlen(conn->newsgroup) > 0);
1061
    g_return_if_fail(conn->xover_start_range >= -1);
1062
    g_return_if_fail(conn->xover_end_range >= -1);
1063

                
1064
// nntpgrab_core_emit_part_download_start(FALSE, conn->server_info.servername, conn->connection_id, conn->collection->collection_name, conn->file->subject, conn->part->part_num);
1065

                
1066
    memset(cmd, 0, sizeof(cmd));
1067
    if (conn->xover_end_range == -1) {
1068
        snprintf(cmd, sizeof(cmd) - 1, "XOVER %"G_GINT64_FORMAT"-\r\n", conn->xover_start_range);
1069
    } else {
1070
        snprintf(cmd, sizeof(cmd) - 1, "XOVER %"G_GINT64_FORMAT"-%"G_GINT64_FORMAT"\r\n", conn->xover_start_range, conn->xover_end_range);
1071
    }
1072

                
1073
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
1074
        return;
1075
    }
1076

                
1077
    conn->active_method = nntpconnection_process_xover_command;
1078
}
1079

                
1080
static void
1081
nntpconnection_process_xover_command(NNTPConnectionInfo *conn)
1082
{
1083
    gboolean more_data_ready = FALSE;
1084
    char buf[1024];
1085
    memset(buf, 0, sizeof(buf));
1086

                
1087
    g_return_if_fail(conn != NULL);
1088
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1089

                
1090
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
1091
        return;
1092
    }
1093

                
1094
    switch (get_status_code(buf)) {
1095
        case 224:                       /* Overview information follows */
1096
            conn->active_method = nntpconnection_process_xover_data;
1097

                
1098
            if (more_data_ready) {
1099
                conn->active_method(conn);
1100
            }
1101

                
1102
            return;
1103

                
1104
        default:
1105
            /* Unexpected response code. Abort the connection */
1106
            strip_newline(buf);
1107
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1108
            return;
1109
    }
1110
}
1111

                
1112
static void
1113
nntpconnection_process_xover_data(NNTPConnectionInfo *conn)
1114
{
1115
    char buf[1024];
1116
    int length = 0;
1117
    gboolean more_data_ready = FALSE;
1118
    char **parts;
1119
    struct tm tm;
1120
    time_t post_date;
1121

                
1122
    g_return_if_fail(conn != NULL);
1123
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
1124
    g_return_if_fail(conn->part != NULL);
1125
    g_return_if_fail(conn->article_fd >= 0);
1126

                
1127
    memset(buf, 0, sizeof(buf));
1128

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

                
1131
    if (!nntpconnection_read_msg(conn, TRUE , sizeof(buf) - 1, buf, &length, &more_data_ready)) {
1132
        return;
1133
    }
1134

                
1135
    g_return_if_fail(length > 0);
1136

                
1137
    /* Are we at the end? */
1138
    if (buf[0] == '.' && buf[1] == '\0') {
1139
        conn->job_type = NNTP_JOB_TYPE_NONE;
1140
        memset(&conn->newsgroup, 0, sizeof(conn->newsgroup));
1141
        conn->active_method = NULL;
1142

                
1143
        return;
1144
    }
1145

                
1146
    parts = g_strsplit(buf, "\t", 0);
1147

                
1148
    /* Sanity check */
1149
    if (!parts    || !parts[0] || !parts[1] || !parts[2] || !parts[3] ||
1150
        !parts[4] || !parts[5] || !parts[6] || !parts[7]) {
1151

                
1152
        goto out;
1153
    }
1154

                
1155
    memset(&tm, 0, sizeof(tm));
1156
    if (strptime(parts[3], "%d %b %Y %H:%M:%S %Z", &tm)) {
1157
        post_date = mktime(&tm);
1158
    } else {
1159
        memset(&tm, 0, sizeof(tm));
1160
        if (strptime(parts[3], "%A, %d %b %Y %H:%M:%S %Z", &tm)) {
1161
            post_date = mktime(&tm);
1162
        } else {
1163
            // Date could not be parsed
1164
            post_date = 0;
1165

                
1166
            goto out;
1167
        }
1168
    }
1169

                
1170
    //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);
1171

                
1172
out:
1173
    g_strfreev(parts);
1174

                
1175
    /* Process more data if it's available */
1176
    if (more_data_ready) {
1177
        conn->active_method(conn);
1178
    }
1179

                
1180
    return;
1181
}
1182

                
1183
/*****************************/
1184
/* generic socket operations */
1185
/*****************************/
1186
#ifdef HAVE_SSL
1187

                
1188
#ifdef HAVE_GNUTLS
1189
static ssize_t pull_func (gnutls_transport_ptr_t fd, void *buf, size_t len)
1190
{
1191
    ssize_t retval = recv(GPOINTER_TO_INT(fd), buf, len, MSG_DONTWAIT);
1192
    update_traffic_monitor(retval);
1193
    return retval;
1194
}
1195

                
1196
#define GNUTLS_CHECK_VERSION(major,minor,micro)    \
1197
    (GNUTLS_VERSION_MAJOR > (major) || \
1198
     (GNUTLS_VERSION_MAJOR == (major) && GNUTLS_VERSION_MINOR > (minor)) || \
1199
     (GNUTLS_VERSION_MAJOR == (major) && GNUTLS_VERSION_MINOR == (minor) && \
1200
      GNUTLS_VERSION_MICRO >= (micro)))
1201

                
1202
static gboolean
1203
prepare_gnutls_connection(NNTPConnectionInfo *conn, char **errmsg)
1204
{
1205
    static GStaticMutex mutex;
1206
    static gboolean been_here = FALSE;
1207
    gnutls_certificate_credentials_t cred;
1208
    int ret;
1209

                
1210
    g_static_mutex_lock(&mutex);
1211
    if (!been_here) {
1212
        gnutls_global_init();
1213
        been_here = TRUE;
1214
    }
1215
    g_static_mutex_unlock(&mutex);
1216

                
1217
    /* Initialize TLS session */
1218
    gnutls_init (&conn->ssl_session, GNUTLS_CLIENT);
1219

                
1220
    /* Use default priorities */
1221
#if GNUTLS_CHECK_VERSION(2,1,7)
1222
    gnutls_priority_set_direct (conn->ssl_session, "PERFORMANCE:+ANON-DH:!ARCFOUR-128", NULL);
1223
#endif
1224

                
1225
    /* put the anonymous credentials to the current session */
1226
    gnutls_certificate_allocate_credentials(&cred);
1227
    gnutls_credentials_set(conn->ssl_session, GNUTLS_CRD_CERTIFICATE, cred);
1228

                
1229
    gnutls_transport_set_ptr (conn->ssl_session, (gnutls_transport_ptr_t) GINT_TO_POINTER(conn->poll_fd.fd));
1230

                
1231
    /* Perform the TLS handshake */
1232
    do {
1233
        ret = gnutls_handshake (conn->ssl_session);
1234
    } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
1235

                
1236
    if (ret < 0) {
1237
        if (errmsg) {
1238
            *errmsg = g_strdup_printf("Handshake failed: %s", gnutls_strerror(ret));
1239
        }
1240
        return FALSE;
1241
    }
1242

                
1243
    /* Override the read function so that we can monitor the traffic on socket level */
1244
    gnutls_transport_set_pull_function(conn->ssl_session, pull_func);
1245
    //gnutls_transport_set_ptr2 (conn->ssl_session, (gnutls_transport_ptr_t) pull_func, (gnutls_transport_ptr_t) push_func);
1246

                
1247
    return TRUE;
1248
}
1249
#else
1250
static SSL *
1251
prepare_ssl_connection(int conn_id, char **errmsg)
1252
{
1253
    SSL_CTX *ctx;
1254
    SSL *ssl;
1255
    SSL_METHOD *meth;
1256
    int err;
1257
#if 0 
1258
    char *str;
1259
    X509 *server_cert;
1260
#endif
1261
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1262

                
1263
    g_static_mutex_lock(&mutex);
1264

                
1265
    SSLeay_add_ssl_algorithms();
1266
    meth = SSLv23_client_method();
1267
    SSL_load_error_strings();
1268
    g_static_mutex_unlock(&mutex);
1269

                
1270
    ctx = SSL_CTX_new (meth);
1271
    if (!ctx) {
1272
        if (errmsg) {
1273
            *errmsg = g_strdup("SSL_CTX_new FAILED");
1274
        }
1275

                
1276
        return NULL;
1277
    }
1278

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

                
1282
    ssl = SSL_new (ctx);
1283
    SSL_CTX_free(ctx);
1284

                
1285
    if (!ssl) {
1286
        if (errmsg) {
1287
            *errmsg = g_strdup("SSL_new FAILED");
1288
        }
1289

                
1290
        return NULL;
1291
    }
1292

                
1293
    SSL_set_fd (ssl, conn_id);
1294
    err = SSL_connect (ssl);
1295
    if (err <= 0) {
1296
        if (errmsg) {
1297
            *errmsg = g_strdup_printf("%s", ERR_error_string(ERR_get_error(), NULL));
1298
        }
1299

                
1300
        return NULL;
1301
    }
1302

                
1303
#if 0 
1304
    printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
1305
    server_cert = SSL_get_peer_certificate (ssl);
1306
    if (server_cert) {
1307
        printf ("Server certificate:\n");
1308

                
1309
        str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
1310
        if (str) {
1311
            printf ("\t subject: %s\n", str);
1312
            OPENSSL_free (str);
1313
        }
1314

                
1315
        str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
1316
        if (str) {
1317
            printf ("\t issuer: %s\n", str);
1318
            OPENSSL_free (str);
1319
        }
1320

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

                
1323
        X509_free (server_cert);
1324
    }
1325
#endif
1326

                
1327
    return ssl;
1328
}
1329
#endif
1330
#endif
1331

                
1332
/********************/
1333
/* proxy operations */
1334
/********************/
1335
static void
1336
nntpconnection_process_proxy_connect_command(NNTPConnectionInfo *conn)
1337
{
1338
    char buf[128];
1339
    int length = 0;
1340
    char *ptr;
1341

                
1342
    g_return_if_fail(conn != NULL);
1343

                
1344
    memset(buf, 0, sizeof(buf));
1345
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, &length, NULL)) {
1346
        return;
1347
    }
1348

                
1349
    /* buf should now contain something like 'HTTP/1.0 200 Connection established' */
1350
    ptr = strstr(buf, " ");
1351
    if (get_status_code(ptr + 1) != 200) {
1352
        /* Unknown response code, disconnect */
1353
        strip_newline(buf);
1354
        nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1355
        return;
1356
    }
1357

                
1358
    /* Keep on reading until we've found a blank line */
1359
    do {
1360
        memset(&buf, 0, sizeof(buf));
1361
        if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, &length, NULL)) {
1362
            return;
1363
        }
1364

                
1365
        strip_newline(buf);
1366
    } while (strlen(buf) > 0);
1367

                
1368
    /* From now on we can talk the regular NNTP protocol */
1369
    conn->active_method = nntpconnection_process_welcome_msg;
1370

                
1371
#ifdef HAVE_SSL
1372
    /* Prepare the SSL connection if necessary */
1373
    if (conn->server_info.use_ssl) {
1374
        char *errmsg = NULL;
1375

                
1376
#ifdef HAVE_GNUTLS
1377
        if (!prepare_gnutls_connection(conn, &errmsg)) {
1378
#else
1379
        if ((conn->ssl = prepare_ssl_connection(conn->poll_fd.fd, &errmsg)) == NULL) {
1380
#endif
1381
            nntpconnection_disconnect_from_server(conn, DISCONNECT_ERROR_SSL_INITIALISE, errmsg, __FILE__, __LINE__);
1382
            g_free(errmsg);
1383
            return;
1384
        }
1385
    }
1386
#endif
1387
}
1388

                
1389
struct _dns_cache_entry {
1390
    char hostname[128];
1391
    int port;
1392
    struct addrinfo *res;
1393
    int n;
1394
};
1395

                
1396
static int
1397
resolve_domain_name(const char *hostname, int port, struct addrinfo **res)
1398
{
1399
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1400
    static GList *cache = NULL;
1401
    GList *list;
1402
    struct addrinfo hints;
1403
    int n = 0;
1404
    char str_port[16];
1405
    struct _dns_cache_entry *entry;
1406

                
1407
    g_return_val_if_fail(hostname != NULL, EAI_FAIL);
1408
    g_return_val_if_fail(port > 0, EAI_FAIL);
1409
    g_return_val_if_fail(port <= 65535, EAI_FAIL);
1410
    g_return_val_if_fail(res != NULL, EAI_FAIL);
1411

                
1412
    *res = NULL;
1413

                
1414
    /* Search the DNS cache for a match first */
1415
    g_static_mutex_lock(&mutex);
1416
    list = cache;
1417
    while (list) {
1418
        entry = list->data;
1419

                
1420
        if (!strcmp(entry->hostname, hostname) && entry->port == port) {
1421
            *res = entry->res;
1422
            n = entry->n;
1423

                
1424
            g_static_mutex_unlock(&mutex);
1425

                
1426
            return n;
1427
        }
1428

                
1429
        list = g_list_next(list);
1430
    }
1431

                
1432
    /* DNS entry wasn't found in the cache yet. Perform a DNS resolve */
1433
    entry = g_slice_new0(struct _dns_cache_entry);
1434
    strncpy(entry->hostname, hostname, sizeof(entry->hostname) -1);
1435
    entry->port = port;
1436
    cache = g_list_append(cache, entry);
1437

                
1438
    memset(&hints, 0, sizeof(struct addrinfo));
1439

                
1440
    hints.ai_family = AF_UNSPEC;
1441
    hints.ai_socktype = SOCK_STREAM;
1442

                
1443
    memset(&str_port, 0, sizeof(str_port));
1444
    snprintf(str_port, sizeof(str_port) - 1, "%i", port);
1445
    entry->n = getaddrinfo(hostname, str_port, &hints, &entry->res);
1446

                
1447
    *res = entry->res;
1448
    n = entry->n;
1449

                
1450
    g_static_mutex_unlock(&mutex);
1451

                
1452
    return n;
1453
}
1454

                
1455
NNTPConnectionErrCode
1456
nntpconnection_connect_to_server(NNTPConnectionInfo *conn, char **errmsg)
1457
{
1458
    static gboolean traffic_monitor_started = FALSE;
1459
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1460
    struct addrinfo *res = NULL;
1461
    int n;
1462
    int last_errno;
1463
#ifdef WIN32
1464
    int tv;
1465
    u_long ioctlArg;
1466
#else
1467
    struct timeval tv;
1468
    int mode;
1469
#endif
1470
    NNTPDisconnectType disconnect_type;
1471
    char *proxy_host = NULL;
1472
    int proxy_port = 0;
1473

                
1474
    g_return_val_if_fail(conn != NULL, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1475
    g_return_val_if_fail(conn->poll_fd.fd == -1, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1476
    g_return_val_if_fail(strlen(conn->server_info.hostname) > 0, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1477
    g_return_val_if_fail(conn->server_info.port > 0, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1478

                
1479
    g_static_mutex_lock(&mutex);
1480
    if (!traffic_monitor_started) {
1481
        traffic_monitor_started = TRUE;
1482
        g_atomic_int_set(&bytes_received_in_last_sec, 0);
1483

                
1484
        /* TODO: implement a nice clean up */
1485
        abort_traffic_thread = FALSE;
1486
        /*traffic_thread = */g_thread_create(traffic_thread_func, NULL, TRUE, NULL);
1487
    }
1488
    g_static_mutex_unlock(&mutex);
1489

                
1490
    conn->is_idle = FALSE;
1491
    conn->last_disconnect_stamp = time(NULL);       /* Assume that the connection attempt has failed by default */
1492

                
1493
    get_proxy_settings(conn->server_info.hostname, &proxy_host, &proxy_port);
1494

                
1495
    if (proxy_host) {
1496
        n = resolve_domain_name(proxy_host, proxy_port, &res);
1497
    } else {
1498
        n = resolve_domain_name(conn->server_info.hostname, conn->server_info.port, &res);
1499
    }
1500

                
1501
    if (n != 0) {
1502
        if (errmsg) {
1503
            *errmsg = g_strdup(gai_strerror(n));
1504
        }
1505
        return NNTP_CONNECTION_ERROR_HOST_NOT_FOUND;
1506
    }
1507

                
1508
    last_errno = 0;
1509

                
1510
    while (res) {
1511
        int ret;
1512

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

                
1515
#ifdef WIN32
1516
        if ((conn->poll_fd.fd == INVALID_SOCKET)) {
1517
#else
1518
        if ((conn->poll_fd.fd == -1)) {
1519
#endif
1520
            // The socket couldn't be created
1521
            // Save the errno and try the next item in the list
1522
#ifdef WIN32
1523
            last_errno = WSAGetLastError();
1524
#else
1525
            last_errno = errno;
1526
#endif
1527
            res = res->ai_next;
1528
            conn->poll_fd.fd = -1;
1529

                
1530
            if (errmsg) {
1531
                if (*errmsg) {
1532
                    g_free(*errmsg);
1533
                }
1534

                
1535
#ifdef WIN32
1536
                *errmsg = WSAGetStrError(last_errno);
1537
#else
1538
                *errmsg = g_strdup(strerror(last_errno));
1539
#endif
1540
            }
1541

                
1542
            continue;
1543
        }
1544

                
1545
        // Set the connection timeout on the socket
1546
#ifdef WIN32
1547
        tv = 5000;   /* F*cking Winsock uses an integer for the timeout and it's value is in milliseconds */
1548

                
1549
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_RCVTIMEO, (char*) &tv, sizeof(tv));
1550
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_SNDTIMEO, (char*) &tv, sizeof(tv));
1551
#else
1552
        tv.tv_sec = 5;
1553
        tv.tv_usec = 0;
1554

                
1555
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_RCVTIMEO, (const void*) &tv, sizeof(tv));
1556
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_SNDTIMEO, (const void*) &tv, sizeof(tv));
1557
#endif
1558

                
1559
        // Try to connect to the server
1560
        nntpgrab_core_emit_connecting(FALSE, conn->server_info.servername, conn->connection_id);
1561

                
1562
        // Mark the socket as non-blocking
1563
#ifdef WIN32
1564
        ioctlArg = 1;
1565
        ioctlsocket(conn->poll_fd.fd, FIONBIO, &ioctlArg);
1566
#else
1567
        mode = fcntl(conn->poll_fd.fd, F_GETFL, 0);
1568
        fcntl(conn->poll_fd.fd, F_SETFL, mode | O_NONBLOCK);
1569
#endif
1570

                
1571
        ret = connect(conn->poll_fd.fd, res->ai_addr, (int) res->ai_addrlen);
1572
        if (ret == 0) {
1573
            // Connection succesfull
1574
            last_errno = 0;
1575
            break;
1576
#ifndef WIN32
1577
        } else if (errno == EINPROGRESS) {
1578
            // Wait for at most 5 seconds
1579
            socklen_t len;
1580

                
1581
            time_t now = time(NULL);
1582
            do {
1583
                // According to https://cr.yp.to/docs/connect.html there are various
1584
                // ways to detect if a socket is connected. The getpeername() method
1585
                // should be the most portable one
1586
                if (res->ai_family == AF_INET6) {
1587
                    struct sockaddr_in6 name;
1588
                    len = sizeof(name);
1589
                    if (getpeername(conn->poll_fd.fd, (struct sockaddr*) &name, &len) == 0) {
1590
                        errno = 0;
1591
                        break;
1592
                    }
1593
                } else {
1594
                    struct sockaddr_in name;
1595
                    len = sizeof(name);
1596
                    if (getpeername(conn->poll_fd.fd, (struct sockaddr*) &name, &len) == 0) {
1597
                        errno = 0;
1598
                        break;
1599
                    }
1600
                }
1601

                
1602
                g_usleep(G_USEC_PER_SEC / 10);
1603

                
1604
                if (download_thread_get_state() != SCHEDULAR_STATE_RUNNING) {
1605
                    break;
1606
                }
1607
            } while (now + 5 > time(NULL));
1608

                
1609
            if (errno == 0) {
1610
                last_errno = 0;
1611
                break;
1612
            }
1613
#else
1614
        } else if (WSAGetLastError() == WSAEWOULDBLOCK) {
1615
            // And Windows again has different behaviour....
1616
            struct timeval tv;
1617
            fd_set send_fds;
1618
            int len;
1619

                
1620
            FD_ZERO(&send_fds);
1621
            FD_SET(conn->poll_fd.fd, &send_fds);
1622

                
1623
            tv.tv_sec = 5;
1624
            tv.tv_usec = 0;
1625

                
1626
            len = select(conn->poll_fd.fd + 1, NULL, &send_fds, NULL, &tv);
1627
            if (len <= 0) {
1628
                // Read timeout or some other error
1629
                if (len == 0) {
1630
                    errno = WSAETIMEDOUT;
1631
                }
1632
            } else {
1633
                // Connection is made
1634
                last_errno = 0;
1635
                break;
1636
            }
1637
#endif
1638
        }
1639

                
1640
        // Connection could not be made, save the errno
1641
#ifndef WIN32
1642
        if (errno == EINPROGRESS) {
1643
            // The connection could not be established within the connect time limit.
1644
            // This provides a more clear error message
1645
            last_errno = ETIMEDOUT;
1646
        } else
1647
#endif
1648
        {
1649
            last_errno = errno;
1650
        }
1651

                
1652
        if (errmsg) {
1653
            if (*errmsg) {
1654
                g_free(*errmsg);
1655
            }
1656

                
1657
#ifdef WIN32
1658
            *errmsg = WSAGetStrError(last_errno);
1659
#else
1660
            *errmsg = g_strdup(strerror(last_errno));
1661
#endif
1662
        }
1663

                
1664
#if WIN32
1665
        if (last_errno == WSAETIMEDOUT) {
1666
#else
1667
        if (last_errno == ETIMEDOUT) {
1668
#endif
1669
            disconnect_type = DISCONNECT_CONNECT_TIMEOUT;
1670
        } else {
1671
            disconnect_type = DISCONNECT_CONNECTION_REFUSED;
1672
        }
1673

                
1674
#ifdef WIN32
1675
        nntpconnection_disconnect_from_server(conn, disconnect_type, WSAGetStrError(last_errno), __FILE__, __LINE__);
1676
#else
1677
        nntpconnection_disconnect_from_server(conn, disconnect_type, strerror(last_errno), __FILE__, __LINE__);
1678
#endif
1679

                
1680
        res = res->ai_next;
1681
    }
1682

                
1683
    if (conn->poll_fd.fd == -1) {
1684
        switch (last_errno) {
1685
#if WIN32
1686
            case WSAETIMEDOUT:
1687
#else
1688
            case ETIMEDOUT:
1689
#endif
1690
                return NNTP_CONNECTION_ERROR_CONNECTION_TIMEOUT;
1691

                
1692
            default:
1693
                return NNTP_CONNECTION_ERROR_CONNECTION_REFUSED;
1694
        };
1695
    }
1696

                
1697
    // Mark the socket as blocking
1698
#ifdef WIN32
1699
        ioctlArg = 0;
1700
        ioctlsocket(conn->poll_fd.fd, FIONBIO, &ioctlArg);
1701
#else
1702
        mode = fcntl(conn->poll_fd.fd, F_GETFL, 0);
1703
        fcntl(conn->poll_fd.fd, F_SETFL, mode  ^ O_NONBLOCK);
1704
#endif
1705

                
1706
#ifdef HAVE_SSL
1707
    if (conn->server_info.use_ssl && !proxy_host) {
1708
#ifdef HAVE_GNUTLS
1709
        if (!prepare_gnutls_connection(conn, errmsg)) {
1710
#else
1711
        if ((conn->ssl = prepare_ssl_connection(conn->poll_fd.fd, errmsg)) == NULL) {
1712
#endif
1713
            nntpconnection_disconnect_from_server(conn, DISCONNECT_ERROR_SSL_INITIALISE, *errmsg, __FILE__, __LINE__);
1714
            return NNTP_CONNECTION_ERROR_SSL_INITIALISE;
1715
        }
1716
    }
1717
#endif
1718

                
1719
    if (proxy_host) {
1720
        char cmd[128];
1721

                
1722
        memset(&cmd, 0, sizeof(cmd));
1723
        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);
1724
        send(conn->poll_fd.fd, cmd, strlen(cmd), 0);
1725

                
1726
        g_free(proxy_host);
1727

                
1728
        conn->active_method = nntpconnection_process_proxy_connect_command;
1729
    } else {
1730
        conn->active_method = nntpconnection_process_welcome_msg;
1731
    }
1732
    memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
1733
    conn->recv_buffer_length = 0;
1734
    conn->last_activity_stamp = time(NULL);
1735
    conn->last_disconnect_stamp = 0;
1736

                
1737
    return NNTP_CONNECTION_ERROR_NONE;
1738
}
1739

                
1740
void
1741
nntpconnection_disconnect_from_server(NNTPConnectionInfo *conn, NNTPDisconnectType disconnect_type, const char *reason, const char *cause_file, int cause_lineno)
1742
{
1743
    g_return_if_fail(conn != NULL);
1744
    g_return_if_fail(conn->poll_fd.fd >= 0);
1745
    g_return_if_fail(cause_file != NULL);
1746

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

                
1749
#ifdef WIN32
1750
    /* DisconnectEx(conn->poll_fd.fd, NULL, 0, 0); * MinGW doesn't have DisconnectEx exported... */
1751
    shutdown(conn->poll_fd.fd, SD_BOTH);            /* The shutdown() function is broken on Win32, see 
1752
                                                     * https://msdn.microsoft.com/en-us/library/ms738547%28VS.85%29.aspx
1753
                                                     * for details*/
1754
    closesocket(conn->poll_fd.fd);
1755
#else
1756
    shutdown(conn->poll_fd.fd, SHUT_RDWR);
1757
    close(conn->poll_fd.fd);
1758
#endif
1759

                
1760
    if (conn->collection && conn->file && conn->part) {
1761
        /* Update the file status */
1762
        download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
1763
    }
1764

                
1765
    nntpgrab_core_emit_disconnect(FALSE, conn->server_info.servername, conn->connection_id, disconnect_type, reason);
1766

                
1767
    conn->poll_fd.fd = -1;
1768
    conn->is_idle = TRUE;
1769
    if (disconnect_type == DISCONNECT_NORMAL) {
1770
        conn->last_disconnect_stamp = 0;
1771
    } else {
1772
        conn->last_disconnect_stamp = time(NULL);
1773
    }
1774
    conn->last_activity_stamp = 0;
1775

                
1776
    if (conn->collection) {
1777
        collection_unref(conn->collection);
1778
        conn->collection = NULL;
1779
    }
1780

                
1781
    if (conn->file) {
1782
        file_unref(conn->file);
1783
        conn->file = NULL;
1784
    }
1785

                
1786
    if (conn->part) {
1787
        conn->part = NULL;
1788
    }
1789

                
1790
    conn->job_type = NNTP_JOB_TYPE_NONE;
1791

                
1792
    if (conn->article_fd >= 0) {
1793
        close(conn->article_fd);
1794
        conn->article_fd = -1;
1795
    }
1796
}
1797

                
1798
void
1799
nntpconnection_process_socket_activity(NNTPConnectionInfo *conn)
1800
{
1801
    g_return_if_fail(conn != NULL);
1802

                
1803
    if (!conn->active_method) {
1804
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, "NNTP connection to server '%s' (connection_id %i, poll_fd %i) is in an undefined state", conn->server_info.servername, conn->connection_id, conn->poll_fd.fd);
1805
        nntpconnection_disconnect_from_server(conn, DISCONNECT_UNEXPECTED, _("NNTP Connection is in an undefined state"), __FILE__, __LINE__);
1806
        return;
1807
    }
1808

                
1809
    conn->active_method(conn);
1810
    conn->last_activity_stamp = time(NULL);
1811
}