Statistics
| Revision:

root / trunk / nntpgrab_core / nntpconnection.c @ 1641

History | View | Annotate | Download (54.5 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
    /* Check for the end sequence */
933
    if ((length >= 5 && !strncmp(buf + length - 5, "\r\n.\r\n", 5)) ||
934
        (length >= 3 && !strncmp(buf + length - 3, "\n.\n", 3))) {
935

                
936
        /* End sequence found! */
937

                
938
        /* Calculate the number of bytes we don't need to write to disk */
939
        if (length >= 5 && !strncmp(buf + length - 5, "\r\n.\r\n", 5)) {
940
            length -= 5;
941
        } else {
942
            length -= 3;
943
        }
944

                
945
        /* Un-escape any double-dots */
946
        if (length > 0) {
947
            trim_double_dots(conn, buf, &length);
948
        }
949

                
950
        /* Check for a lost newline which was pushed back in the recv buffer by the trim_double_dots() function */
951
        if (conn->recv_buffer_length == 1 && conn->recv_buffer[0] == '\n') {
952
            conn->recv_buffer[0] = '\0';
953
            conn->recv_buffer_length = 0;
954
        }
955

                
956
        /* Flush the buffers and close the file descriptor */
957
        if ((conn->article_write_buffer_length > 0 &&
958
             write(conn->article_fd, conn->article_write_buffer, conn->article_write_buffer_length) != conn->article_write_buffer_length) ||
959
            (length > 0 &&
960
             write(conn->article_fd, buf, length) != length)) {
961

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

                
965
            /* Mark the part as failed so it will be retried later */
966
            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);
967
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
968
        } else {
969
            /* Download and save succeeded */
970
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, TRUE, FALSE, TRUE);
971
            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);
972
        }
973

                
974
        close(conn->article_fd);
975
        conn->article_fd = -1;
976

                
977
        conn->article_bytes_downloaded = 0;
978
        conn->article_write_buffer_length = 0;
979

                
980
        memset(conn->article_write_buffer, 0, sizeof(conn->article_write_buffer));
981

                
982
        collection_unref(conn->collection);
983
        file_unref(conn->file);
984
        conn->collection = NULL;
985
        conn->file = NULL;
986
        conn->part = NULL;
987
        conn->job_type = NNTP_JOB_TYPE_NONE;
988
        conn->active_method = NULL;
989
        conn->is_idle = TRUE;
990

                
991
        conn->idle_start_stamp = time(NULL);
992

                
993
        return;
994
    }
995

                
996
    /* Un-escape any double-dots */
997
    trim_double_dots(conn, buf, &length);
998

                
999
    /* Keep the data in a temporary buffer to minimize the amount of I/O */
1000
    if (conn->article_write_buffer_length + length > sizeof(conn->article_write_buffer)) {
1001
        /* Flush the buffer */
1002
        if (write(conn->article_fd, conn->article_write_buffer, conn->article_write_buffer_length) != conn->article_write_buffer_length) {
1003
            /* Write error! Kill the download thread */
1004
            char msg[1024];
1005

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

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

                
1012
            nntpconnection_disconnect_from_server(conn, DISCONNECT_WRITE_ERROR, msg, __FILE__, __LINE__);
1013

                
1014
            return;
1015
        }
1016

                
1017
        memset(conn->article_write_buffer, 0, sizeof(conn->article_write_buffer));
1018
        conn->article_write_buffer_length = 0;
1019
    }
1020

                
1021
    memcpy(conn->article_write_buffer + conn->article_write_buffer_length, buf, length);
1022
    conn->article_write_buffer_length += length;
1023

                
1024
    /* Is there more data to read? */
1025
    if (more_data_ready) {
1026
        nntpconnection_process_body_data(conn);
1027
    }
1028
}
1029

                
1030
/************************************************************** 
1031
 * group / xover
1032
 **************************************************************/
1033
void
1034
nntpconnection_send_group_command(NNTPConnectionInfo *conn)
1035
{
1036
    char cmd[1024];
1037

                
1038
    g_return_if_fail(conn != NULL);
1039
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1040
    g_return_if_fail(strlen(conn->newsgroup) > 0);
1041

                
1042
    memset(cmd, 0, sizeof(cmd));
1043
    snprintf(cmd, sizeof(cmd) - 1, "GROUP %s\r\n", conn->newsgroup);
1044
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
1045
        return;
1046
    }
1047

                
1048
    conn->active_method = nntpconnection_process_group_command;
1049
}
1050

                
1051
static void
1052
nntpconnection_process_group_command(NNTPConnectionInfo *conn)
1053
{
1054
    gboolean more_data_ready = FALSE;
1055
    char buf[1024];
1056
    memset(buf, 0, sizeof(buf));
1057

                
1058
    g_return_if_fail(conn != NULL);
1059
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1060

                
1061
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
1062
        return;
1063
    }
1064

                
1065
    switch (get_status_code(buf)) {
1066
        case 211:
1067
            /* 211 n f l s group selected 
1068
                    (n = estimated number of articles in group,
1069
                     f = first article number in the group,
1070
                     l = last article number in the group,
1071
                     s = name of the group.)
1072
             */
1073

                
1074
            nntpconnection_send_xover_command(conn);
1075
            return;
1076

                
1077
        case 411:       /* No such group */
1078
            strip_newline(buf);
1079
            nntpconnection_disconnect_from_server(conn, DISCONNECT_NORMAL, buf, __FILE__, __LINE__);
1080
            return;
1081

                
1082
        default:
1083
            /* Unexpected response code. Abort the connection */
1084
            strip_newline(buf);
1085
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1086
            return;
1087
    }
1088
}
1089

                
1090
void
1091
nntpconnection_send_xover_command(NNTPConnectionInfo *conn)
1092
{
1093
    char cmd[1024];
1094

                
1095
    g_return_if_fail(conn != NULL);
1096
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1097
    g_return_if_fail(strlen(conn->newsgroup) > 0);
1098
    g_return_if_fail(conn->xover_start_range >= -1);
1099
    g_return_if_fail(conn->xover_end_range >= -1);
1100

                
1101
// 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);
1102

                
1103
    memset(cmd, 0, sizeof(cmd));
1104
    if (conn->xover_end_range == -1) {
1105
        snprintf(cmd, sizeof(cmd) - 1, "XOVER %"G_GINT64_FORMAT"-\r\n", conn->xover_start_range);
1106
    } else {
1107
        snprintf(cmd, sizeof(cmd) - 1, "XOVER %"G_GINT64_FORMAT"-%"G_GINT64_FORMAT"\r\n", conn->xover_start_range, conn->xover_end_range);
1108
    }
1109

                
1110
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
1111
        return;
1112
    }
1113

                
1114
    conn->active_method = nntpconnection_process_xover_command;
1115
}
1116

                
1117
static void
1118
nntpconnection_process_xover_command(NNTPConnectionInfo *conn)
1119
{
1120
    gboolean more_data_ready = FALSE;
1121
    char buf[1024];
1122
    memset(buf, 0, sizeof(buf));
1123

                
1124
    g_return_if_fail(conn != NULL);
1125
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1126

                
1127
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, NULL, &more_data_ready)) {
1128
        return;
1129
    }
1130

                
1131
    switch (get_status_code(buf)) {
1132
        case 224:                       /* Overview information follows */
1133
            conn->active_method = nntpconnection_process_xover_data;
1134

                
1135
            if (more_data_ready) {
1136
                conn->active_method(conn);
1137
            }
1138

                
1139
            return;
1140

                
1141
        default:
1142
            /* Unexpected response code. Abort the connection */
1143
            strip_newline(buf);
1144
            nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1145
            return;
1146
    }
1147
}
1148

                
1149
static void
1150
nntpconnection_process_xover_data(NNTPConnectionInfo *conn)
1151
{
1152
    char buf[1024];
1153
    int length = 0;
1154
    gboolean more_data_ready = FALSE;
1155
    char **parts;
1156
    struct tm tm;
1157
    time_t post_date;
1158

                
1159
    g_return_if_fail(conn != NULL);
1160
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_ARTICLE);
1161
    g_return_if_fail(conn->part != NULL);
1162
    g_return_if_fail(conn->article_fd >= 0);
1163

                
1164
    memset(buf, 0, sizeof(buf));
1165

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

                
1168
    if (!nntpconnection_read_msg(conn, TRUE , sizeof(buf) - 1, buf, &length, &more_data_ready)) {
1169
        return;
1170
    }
1171

                
1172
    g_return_if_fail(length > 0);
1173

                
1174
    /* Are we at the end? */
1175
    if (buf[0] == '.' && buf[1] == '\0') {
1176
        conn->job_type = NNTP_JOB_TYPE_NONE;
1177
        memset(&conn->newsgroup, 0, sizeof(conn->newsgroup));
1178
        conn->active_method = NULL;
1179

                
1180
        return;
1181
    }
1182

                
1183
    parts = g_strsplit(buf, "\t", 0);
1184

                
1185
    /* Sanity check */
1186
    if (!parts    || !parts[0] || !parts[1] || !parts[2] || !parts[3] ||
1187
        !parts[4] || !parts[5] || !parts[6] || !parts[7]) {
1188

                
1189
        goto out;
1190
    }
1191

                
1192
    memset(&tm, 0, sizeof(tm));
1193
    if (strptime(parts[3], "%d %b %Y %H:%M:%S %Z", &tm)) {
1194
        post_date = mktime(&tm);
1195
    } else {
1196
        memset(&tm, 0, sizeof(tm));
1197
        if (strptime(parts[3], "%A, %d %b %Y %H:%M:%S %Z", &tm)) {
1198
            post_date = mktime(&tm);
1199
        } else {
1200
            // Date could not be parsed
1201
            post_date = 0;
1202

                
1203
            goto out;
1204
        }
1205
    }
1206

                
1207
    //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);
1208

                
1209
out:
1210
    g_strfreev(parts);
1211

                
1212
    /* Process more data if it's available */
1213
    if (more_data_ready) {
1214
        conn->active_method(conn);
1215
    }
1216

                
1217
    return;
1218
}
1219

                
1220
/*****************************/
1221
/* generic socket operations */
1222
/*****************************/
1223
static SSL *
1224
prepare_ssl_connection(int conn_id, char **errmsg)
1225
{
1226
    SSL_CTX *ctx;
1227
    SSL *ssl;
1228
    SSL_METHOD *meth;
1229
    int err;
1230
#if 0 
1231
    char *str;
1232
    X509 *server_cert;
1233
#endif
1234
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1235

                
1236
    g_static_mutex_lock(&mutex);
1237

                
1238
    SSLeay_add_ssl_algorithms();
1239
    meth = SSLv23_client_method();
1240
    SSL_load_error_strings();
1241
    g_static_mutex_unlock(&mutex);
1242

                
1243
    ctx = SSL_CTX_new (meth);
1244
    if (!ctx) {
1245
        if (errmsg) {
1246
            *errmsg = g_strdup("SSL_CTX_new FAILED");
1247
        }
1248

                
1249
        return NULL;
1250
    }
1251

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

                
1255
    ssl = SSL_new (ctx);
1256
    SSL_CTX_free(ctx);
1257

                
1258
    if (!ssl) {
1259
        if (errmsg) {
1260
            *errmsg = g_strdup("SSL_new FAILED");
1261
        }
1262

                
1263
        return NULL;
1264
    }
1265

                
1266
    SSL_set_fd (ssl, conn_id);
1267
    err = SSL_connect (ssl);
1268
    if (err <= 0) {
1269
        if (errmsg) {
1270
            *errmsg = g_strdup_printf("%s", ERR_error_string(ERR_get_error(), NULL));
1271
        }
1272

                
1273
        return NULL;
1274
    }
1275

                
1276
#if 0 
1277
    printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
1278
    server_cert = SSL_get_peer_certificate (ssl);
1279
    if (server_cert) {
1280
        printf ("Server certificate:\n");
1281

                
1282
        str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
1283
        if (str) {
1284
            printf ("\t subject: %s\n", str);
1285
            OPENSSL_free (str);
1286
        }
1287

                
1288
        str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
1289
        if (str) {
1290
            printf ("\t issuer: %s\n", str);
1291
            OPENSSL_free (str);
1292
        }
1293

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

                
1296
        X509_free (server_cert);
1297
    }
1298
#endif
1299

                
1300
    return ssl;
1301
}
1302

                
1303
/********************/
1304
/* proxy operations */
1305
/********************/
1306
static void
1307
nntpconnection_process_proxy_connect_command(NNTPConnectionInfo *conn)
1308
{
1309
    char buf[128];
1310
    int length = 0;
1311
    char *ptr;
1312

                
1313
    g_return_if_fail(conn != NULL);
1314

                
1315
    memset(buf, 0, sizeof(buf));
1316
    if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, &length, NULL)) {
1317
        return;
1318
    }
1319

                
1320
    /* buf should now contain something like 'HTTP/1.0 200 Connection established' */
1321
    ptr = strstr(buf, " ");
1322
    if (get_status_code(ptr + 1) != 200) {
1323
        /* Unknown response code, disconnect */
1324
        strip_newline(buf);
1325
        nntpconnection_disconnect_from_server(conn, DISCONNECT_INVALID_MSG, buf, __FILE__, __LINE__);
1326
        return;
1327
    }
1328

                
1329
    /* Keep on reading until we've found a blank line */
1330
    do {
1331
        memset(&buf, 0, sizeof(buf));
1332
        if (!nntpconnection_read_msg(conn, TRUE, sizeof(buf) - 1, buf, &length, NULL)) {
1333
            return;
1334
        }
1335

                
1336
        strip_newline(buf);
1337
    } while (strlen(buf) > 0);
1338

                
1339
    /* From now on we can talk the regular NNTP protocol */
1340
    conn->active_method = nntpconnection_process_welcome_msg;
1341

                
1342
    /* Prepare the SSL connection if necessary */
1343
    if (conn->server_info.use_ssl) {
1344
        char *errmsg = NULL;
1345

                
1346
        if ((conn->ssl = prepare_ssl_connection(conn->poll_fd.fd, &errmsg)) == NULL) {
1347
            nntpconnection_disconnect_from_server(conn, DISCONNECT_ERROR_SSL_INITIALISE, errmsg, __FILE__, __LINE__);
1348
            g_free(errmsg);
1349
            return;
1350
        }
1351
    }
1352
}
1353

                
1354
struct _dns_cache_entry {
1355
    char hostname[128];
1356
    struct addrinfo *res;
1357
    int n;
1358
};
1359

                
1360
static int
1361
resolve_domain_name(const char *hostname, int port, struct addrinfo **res)
1362
{
1363
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1364
    static GList *cache = NULL;
1365
    GList *list;
1366
    struct addrinfo hints;
1367
    int n = 0;
1368
    char str_port[16];
1369
    struct _dns_cache_entry *entry;
1370

                
1371
    g_return_val_if_fail(hostname != NULL, EAI_FAIL);
1372
    g_return_val_if_fail(port > 0, EAI_FAIL);
1373
    g_return_val_if_fail(port <= 65535, EAI_FAIL);
1374
    g_return_val_if_fail(res != NULL, EAI_FAIL);
1375

                
1376
    *res = NULL;
1377

                
1378
    /* Search the DNS cache for a match first */
1379
    g_static_mutex_lock(&mutex);
1380
    list = cache;
1381
    while (list) {
1382
        entry = list->data;
1383

                
1384
        if (!strcmp(entry->hostname, hostname)) {
1385
            *res = entry->res;
1386
            n = entry->n;
1387

                
1388
            g_static_mutex_unlock(&mutex);
1389

                
1390
            return n;
1391
        }
1392

                
1393
        list = g_list_next(list);
1394
    }
1395

                
1396
    /* DNS entry wasn't found in the cache yet. Perform a DNS resolve */
1397
    entry = g_slice_new0(struct _dns_cache_entry);
1398
    strncpy(entry->hostname, hostname, sizeof(entry->hostname) -1);
1399
    cache = g_list_append(cache, entry);
1400

                
1401
    memset(&hints, 0, sizeof(struct addrinfo));
1402

                
1403
    hints.ai_family = AF_UNSPEC;
1404
    hints.ai_socktype = SOCK_STREAM;
1405

                
1406
    memset(&str_port, 0, sizeof(str_port));
1407
    snprintf(str_port, sizeof(str_port) - 1, "%i", port);
1408
    entry->n = getaddrinfo(hostname, str_port, &hints, &entry->res);
1409

                
1410
    *res = entry->res;
1411
    n = entry->n;
1412

                
1413
    g_static_mutex_unlock(&mutex);
1414

                
1415
    return n;
1416
}
1417

                
1418
NNTPConnectionErrCode
1419
nntpconnection_connect_to_server(NNTPConnectionInfo *conn, char **errmsg)
1420
{
1421
    static gboolean traffic_monitor_started = FALSE;
1422
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
1423
    struct addrinfo *res = NULL;
1424
    int n;
1425
    int last_errno;
1426
#ifdef WIN32
1427
    int tv;
1428
    u_long ioctlArg;
1429
#else
1430
    struct timeval tv;
1431
    int mode;
1432
#endif
1433
    NNTPDisconnectType disconnect_type;
1434
    char *proxy_host = NULL;
1435
    int proxy_port = 0;
1436

                
1437
    g_return_val_if_fail(conn != NULL, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1438
    g_return_val_if_fail(conn->poll_fd.fd == -1, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1439
    g_return_val_if_fail(strlen(conn->server_info.hostname) > 0, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1440
    g_return_val_if_fail(conn->server_info.port > 0, NNTP_CONNECTION_ERROR_INVALID_ARGUMENT);
1441

                
1442
    g_static_mutex_lock(&mutex);
1443
    if (!traffic_monitor_started) {
1444
        traffic_monitor_started = TRUE;
1445
        memset(&bytes_received, 0, sizeof(bytes_received));
1446

                
1447
        /* TODO: implement a nice clean up */
1448
        abort_traffic_thread = FALSE;
1449
        /*traffic_thread = */g_thread_create(traffic_thread_func, NULL, TRUE, NULL);
1450
    }
1451
    g_static_mutex_unlock(&mutex);
1452

                
1453
    conn->is_idle = FALSE;
1454
    conn->last_disconnect_stamp = time(NULL);       /* Assume that the connection attempt has failed by default */
1455

                
1456
    get_proxy_settings(conn->server_info.hostname, &proxy_host, &proxy_port);
1457

                
1458
    if (proxy_host) {
1459
        n = resolve_domain_name(proxy_host, proxy_port, &res);
1460
    } else {
1461
        n = resolve_domain_name(conn->server_info.hostname, conn->server_info.port, &res);
1462
    }
1463

                
1464
    if (n != 0) {
1465
        if (errmsg) {
1466
            *errmsg = g_strdup(gai_strerror(n));
1467
        }
1468
        return NNTP_CONNECTION_ERROR_HOST_NOT_FOUND;
1469
    }
1470

                
1471
    last_errno = 0;
1472

                
1473
    while (res) {
1474
        int ret;
1475

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

                
1478
#ifdef WIN32
1479
        if ((conn->poll_fd.fd == INVALID_SOCKET)) {
1480
#else
1481
        if ((conn->poll_fd.fd == -1)) {
1482
#endif
1483
            // The socket couldn't be created
1484
            // Save the errno and try the next item in the list
1485
#ifdef WIN32
1486
            last_errno = WSAGetLastError();
1487
#else
1488
            last_errno = errno;
1489
#endif
1490
            res = res->ai_next;
1491
            conn->poll_fd.fd = -1;
1492

                
1493
            if (errmsg) {
1494
                if (*errmsg) {
1495
                    g_free(*errmsg);
1496
                }
1497

                
1498
#ifdef WIN32
1499
                *errmsg = WSAGetStrError(last_errno);
1500
#else
1501
                *errmsg = g_strdup(strerror(last_errno));
1502
#endif
1503
            }
1504

                
1505
            continue;
1506
        }
1507

                
1508
        // Set the connection timeout on the socket
1509
#ifdef WIN32
1510
        tv = 5000;   /* F*cking Winsock uses an integer for the timeout and it's value is in milliseconds */
1511

                
1512
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_RCVTIMEO, (char*) &tv, sizeof(tv));
1513
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_SNDTIMEO, (char*) &tv, sizeof(tv));
1514
#else
1515
        tv.tv_sec = 5;
1516
        tv.tv_usec = 0;
1517

                
1518
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_RCVTIMEO, (const void*) &tv, sizeof(tv));
1519
        setsockopt(conn->poll_fd.fd, SOL_SOCKET, SO_SNDTIMEO, (const void*) &tv, sizeof(tv));
1520
#endif
1521

                
1522
        // Try to connect to the server
1523
        nntpgrab_core_emit_connecting(FALSE, conn->server_info.servername, conn->poll_fd.fd);
1524

                
1525
        // Mark the socket as non-blocking
1526
#ifdef WIN32
1527
        ioctlArg = 1;
1528
        ioctlsocket(conn->poll_fd.fd, FIONBIO, &ioctlArg);
1529
#else
1530
        mode = fcntl(conn->poll_fd.fd, F_GETFL, 0);
1531
        fcntl(conn->poll_fd.fd, F_SETFL, mode | O_NONBLOCK);
1532
#endif
1533

                
1534
        ret = connect(conn->poll_fd.fd, res->ai_addr, (int) res->ai_addrlen);
1535
        if (ret == 0) {
1536
            // Connection succesfull
1537
            last_errno = 0;
1538
            break;
1539
#ifndef WIN32
1540
        } else if (errno == EINPROGRESS) {
1541
            // Wait for at most 5 seconds
1542
            socklen_t len;
1543

                
1544
            time_t now = time(NULL);
1545
            do {
1546
                // According to https://cr.yp.to/docs/connect.html there are various
1547
                // ways to detect if a socket is connected. The getpeername() method
1548
                // should be the most portable one
1549
                if (res->ai_family == AF_INET6) {
1550
                    struct sockaddr_in6 name;
1551
                    len = sizeof(name);
1552
                    if (getpeername(conn->poll_fd.fd, (struct sockaddr*) &name, &len) == 0) {
1553
                        errno = 0;
1554
                        break;
1555
                    }
1556
                } else {
1557
                    struct sockaddr_in name;
1558
                    len = sizeof(name);
1559
                    if (getpeername(conn->poll_fd.fd, (struct sockaddr*) &name, &len) == 0) {
1560
                        errno = 0;
1561
                        break;
1562
                    }
1563
                }
1564

                
1565
                g_usleep(G_USEC_PER_SEC / 10);
1566

                
1567
                if (download_thread_get_state() != SCHEDULAR_STATE_RUNNING) {
1568
                    break;
1569
                }
1570
            } while (now + 5 > time(NULL));
1571

                
1572
            if (errno == 0) {
1573
                last_errno = 0;
1574
                break;
1575
            }
1576
#else
1577
        } else if (WSAGetLastError() == WSAEWOULDBLOCK) {
1578
            // And Windows again has different behaviour....
1579
            struct timeval tv;
1580
            fd_set send_fds;
1581
            int len;
1582

                
1583
            FD_ZERO(&send_fds);
1584
            FD_SET(conn->poll_fd.fd, &send_fds);
1585

                
1586
            tv.tv_sec = 5;
1587
            tv.tv_usec = 0;
1588

                
1589
            len = select(conn->poll_fd.fd + 1, NULL, &send_fds, NULL, &tv);
1590
            if (len <= 0) {
1591
                // Read timeout or some other error
1592
                if (len == 0) {
1593
                    errno = WSAETIMEDOUT;
1594
                }
1595
            } else {
1596
                // Connection is made
1597
                last_errno = 0;
1598
                break;
1599
            }
1600
#endif
1601
        }
1602

                
1603
        // Connection could not be made, save the errno
1604
#ifndef WIN32
1605
        if (errno == EINPROGRESS) {
1606
            // The connection could not be established within the connect time limit.
1607
            // This provides a more clear error message
1608
            last_errno = ETIMEDOUT;
1609
        } else
1610
#endif
1611
        {
1612
            last_errno = errno;
1613
        }
1614

                
1615
        if (errmsg) {
1616
            if (*errmsg) {
1617
                g_free(*errmsg);
1618
            }
1619

                
1620
#ifdef WIN32
1621
            *errmsg = WSAGetStrError(last_errno);
1622
#else
1623
            *errmsg = g_strdup(strerror(last_errno));
1624
#endif
1625
        }
1626

                
1627
#if WIN32
1628
        if (last_errno == WSAETIMEDOUT) {
1629
#else
1630
        if (last_errno == ETIMEDOUT) {
1631
#endif
1632
            disconnect_type = DISCONNECT_CONNECT_TIMEOUT;
1633
        } else {
1634
            disconnect_type = DISCONNECT_CONNECTION_REFUSED;
1635
        }
1636

                
1637
        nntpconnection_disconnect_from_server(conn, disconnect_type, strerror(last_errno), __FILE__, __LINE__);
1638

                
1639
        res = res->ai_next;
1640
    }
1641

                
1642
    if (conn->poll_fd.fd == -1) {
1643
        switch (last_errno) {
1644
#if WIN32
1645
            case WSAETIMEDOUT:
1646
#else
1647
            case ETIMEDOUT:
1648
#endif
1649
                return NNTP_CONNECTION_ERROR_CONNECTION_TIMEOUT;
1650

                
1651
            default:
1652
                return NNTP_CONNECTION_ERROR_CONNECTION_REFUSED;
1653
        };
1654
    }
1655

                
1656
    // Mark the socket as blocking
1657
#ifdef WIN32
1658
        ioctlArg = 0;
1659
        ioctlsocket(conn->poll_fd.fd, FIONBIO, &ioctlArg);
1660
#else
1661
        mode = fcntl(conn->poll_fd.fd, F_GETFL, 0);
1662
        fcntl(conn->poll_fd.fd, F_SETFL, mode  ^ O_NONBLOCK);
1663
#endif
1664

                
1665
    if (conn->server_info.use_ssl && !proxy_host) {
1666
        if ((conn->ssl = prepare_ssl_connection(conn->poll_fd.fd, errmsg)) == NULL) {
1667
            nntpconnection_disconnect_from_server(conn, DISCONNECT_ERROR_SSL_INITIALISE, *errmsg, __FILE__, __LINE__);
1668
            return NNTP_CONNECTION_ERROR_SSL_INITIALISE;
1669
        }
1670
    }
1671

                
1672
    if (proxy_host) {
1673
        char cmd[128];
1674

                
1675
        memset(&cmd, 0, sizeof(cmd));
1676
        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);
1677
        send(conn->poll_fd.fd, cmd, strlen(cmd), 0);
1678

                
1679
        g_free(proxy_host);
1680

                
1681
        conn->active_method = nntpconnection_process_proxy_connect_command;
1682
    } else {
1683
        conn->active_method = nntpconnection_process_welcome_msg;
1684
    }
1685
    memset(conn->recv_buffer, 0, sizeof(conn->recv_buffer));
1686
    conn->recv_buffer_length = 0;
1687
    conn->last_activity_stamp = time(NULL);
1688
    conn->last_disconnect_stamp = 0;
1689

                
1690
    return NNTP_CONNECTION_ERROR_NONE;
1691
    //return login(username, password, errmsg, conn->poll_fd.fd, data);
1692
}
1693

                
1694
void
1695
nntpconnection_disconnect_from_server(NNTPConnectionInfo *conn, NNTPDisconnectType disconnect_type, const char *reason, const char *cause_file, int cause_lineno)
1696
{
1697
    g_return_if_fail(conn != NULL);
1698
    g_return_if_fail(conn->poll_fd.fd >= 0);
1699
    g_return_if_fail(cause_file != NULL);
1700

                
1701
    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);
1702

                
1703
#ifdef WIN32
1704
    /* DisconnectEx(conn->poll_fd.fd, NULL, 0, 0); * MinGW doesn't have DisconnectEx exported... */
1705
    shutdown(conn->poll_fd.fd, SD_BOTH);            /* The shutdown() function is broken on Win32, see 
1706
                                                     * https://msdn.microsoft.com/en-us/library/ms738547%28VS.85%29.aspx
1707
                                                     * for details*/
1708
    closesocket(conn->poll_fd.fd);
1709
#else
1710
    shutdown(conn->poll_fd.fd, SHUT_RDWR);
1711
    close(conn->poll_fd.fd);
1712
#endif
1713

                
1714
    if (conn->collection && conn->file && conn->part) {
1715
        /* Update the file status */
1716
        download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
1717
    }
1718

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

                
1721
    conn->poll_fd.fd = -1;
1722
    conn->is_idle = TRUE;
1723
    if (disconnect_type == DISCONNECT_NORMAL) {
1724
        conn->last_disconnect_stamp = 0;
1725
    } else {
1726
        conn->last_disconnect_stamp = time(NULL);
1727
    }
1728
    conn->last_activity_stamp = 0;
1729

                
1730
    if (conn->collection) {
1731
        collection_unref(conn->collection);
1732
        conn->collection = NULL;
1733
    }
1734

                
1735
    if (conn->file) {
1736
        file_unref(conn->file);
1737
        conn->file = NULL;
1738
    }
1739

                
1740
    if (conn->part) {
1741
        conn->part = NULL;
1742
    }
1743

                
1744
    conn->job_type = NNTP_JOB_TYPE_NONE;
1745

                
1746
    if (conn->article_fd >= 0) {
1747
        close(conn->article_fd);
1748
        conn->article_fd = -1;
1749
    }
1750
}
1751

                
1752
void
1753
nntpconnection_process_socket_activity(NNTPConnectionInfo *conn)
1754
{
1755
    g_return_if_fail(conn != NULL);
1756

                
1757
    if (!conn->active_method) {
1758
        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);
1759
        nntpconnection_disconnect_from_server(conn, DISCONNECT_UNEXPECTED, _("NNTP Connection is in an undefined state"), __FILE__, __LINE__);
1760
        return;
1761
    }
1762

                
1763
    conn->active_method(conn);
1764
    conn->last_activity_stamp = time(NULL);
1765
}