Statistics
| Revision:

root / trunk / nntpgrab_core / nntpconnection.c @ 1638

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) ||
440
        (read_line && !strstr(conn->recv_buffer, "\n"))) {
441

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

                
447
    g_return_val_if_fail(conn->recv_buffer_length > 0, FALSE);
448

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

                
452
        if (!newline) {
453
            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);
454
            return FALSE;
455
        }
456

                
457
        len = newline - conn->recv_buffer + 1;
458

                
459
        if (len > max_length) {
460
            /* We're going to have to trim some data */
461
            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);
462
            memcpy(data, conn->recv_buffer, max_length);
463

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

                
470
            if (data_length) {
471
                *data_length = len;
472
            }
473
        }
474

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

                
484
            g_return_val_if_fail(conn->recv_buffer_length >= 0, FALSE);
485

                
486
            if (more_data_ready) {
487
                *more_data_ready = TRUE;
488
            }
489
        }
490

                
491
        return TRUE;
492
    }
493

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

                
500
    memcpy(data, conn->recv_buffer, len);
501

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

                
511
        g_return_val_if_fail(conn->recv_buffer_length >= 0, FALSE);
512

                
513
        if (more_data_ready) {
514
            *more_data_ready = TRUE;
515
        }
516
    }
517

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

                
522
    return TRUE;
523
}
524

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

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

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

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

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

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

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

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

                
579
            default:
580
                bytes_sent += retval;
581

                
582
                if (bytes_sent == length) {
583
                    return TRUE;
584
                }
585

                
586
                break;
587
        }
588
    } while (TRUE);
589

                
590
    g_return_val_if_reached(TRUE);
591
}
592

                
593
/*************************************/
594
/* Initialization and authentication */
595
/*************************************/
596

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

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

                
614
    memset(welcome_msg, 0, sizeof(welcome_msg));
615

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

                
620
    strip_newline(welcome_msg);
621

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

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

                
633
            return;
634
    }
635

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

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

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

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

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

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

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

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

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

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

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

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

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

                
712
    conn->active_method = nntpconnection_login_process_mode_reader;
713
}
714

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

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

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

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

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

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

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

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

                
772
    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);
773

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

                
780
    conn->active_method = nntpconnection_process_body_command;
781
    conn->article_bytes_downloaded = 0;
782
    conn->article_write_buffer_length = 0;
783
}
784

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

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

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

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

                
814
            conn->active_method = nntpconnection_process_body_data;
815

                
816
            if (more_data_ready) {
817
                conn->active_method(conn);
818
            }
819

                
820
            return;
821

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

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

                
832
            conn->active_method = NULL;
833
            conn->is_idle = TRUE;
834
            conn->idle_start_stamp = time(NULL);
835

                
836
            return;
837

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

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

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

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

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

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

                
877
        conn->recv_buffer[0] = '\n';
878
        conn->recv_buffer_length = 1;
879

                
880
        buf[*length - 1] = '\0';
881
        *length -= 1;
882
    }
883

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

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

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

                
908
    memset(buf, 0, sizeof(buf));
909

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

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

                
916
    g_return_if_fail(length > 0);
917

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

                
923
    gettimeofday(&tv, NULL);
924
    timersub(&tv, &conn->last_article_progress_announce, &tv_diff);
925
    if (tv_diff.tv_sec > 0 || tv_diff.tv_usec > G_USEC_PER_SEC / 10) {
926
        conn->last_article_progress_announce = tv;
927
        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);
928
    }
929

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

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

                
937
        memcpy(conn->recv_buffer, buf, length);
938
        conn->recv_buffer_length = length;
939

                
940
        return;
941
    }
942

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

                
947
        /* End sequence found! */
948

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

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

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

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

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

                
976
            /* Mark the part as failed so it will be retried later */
977
            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);
978
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, FALSE, FALSE, FALSE);
979
        } else {
980
            /* Download and save succeeded */
981
            download_queue_update_part_status(conn, conn->collection, conn->file, conn->part, conn->server_id, TRUE, FALSE, TRUE);
982
            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);
983
        }
984

                
985
        close(conn->article_fd);
986
        conn->article_fd = -1;
987

                
988
        conn->article_bytes_downloaded = 0;
989
        conn->article_write_buffer_length = 0;
990

                
991
        memset(conn->article_write_buffer, 0, sizeof(conn->article_write_buffer));
992

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

                
1002
        conn->idle_start_stamp = time(NULL);
1003

                
1004
        return;
1005
    }
1006

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

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

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

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

                
1023
            nntpconnection_disconnect_from_server(conn, DISCONNECT_WRITE_ERROR, msg, __FILE__, __LINE__);
1024

                
1025
            return;
1026
        }
1027

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

                
1032
    memcpy(conn->article_write_buffer + conn->article_write_buffer_length, buf, length);
1033
    conn->article_write_buffer_length += length;
1034

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

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

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

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

                
1059
    conn->active_method = nntpconnection_process_group_command;
1060
}
1061

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

                
1069
    g_return_if_fail(conn != NULL);
1070
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1071

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

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

                
1085
            nntpconnection_send_xover_command(conn);
1086
            return;
1087

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

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

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

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

                
1112
// 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);
1113

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

                
1121
    if (!nntpconnection_send_msg(conn, cmd, strlen(cmd))) {
1122
        return;
1123
    }
1124

                
1125
    conn->active_method = nntpconnection_process_xover_command;
1126
}
1127

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

                
1135
    g_return_if_fail(conn != NULL);
1136
    g_return_if_fail(conn->job_type == NNTP_JOB_TYPE_XOVER);
1137

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

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

                
1146
            if (more_data_ready) {
1147
                conn->active_method(conn);
1148
            }
1149

                
1150
            return;
1151

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

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

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

                
1175
    memset(buf, 0, sizeof(buf));
1176

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

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

                
1183
    g_return_if_fail(length > 0);
1184

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

                
1191
        return;
1192
    }
1193

                
1194
    parts = g_strsplit(buf, "\t", 0);
1195

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

                
1200
        goto out;
1201
    }
1202

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

                
1214
            goto out;
1215
        }
1216
    }
1217

                
1218
    //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);
1219

                
1220
out:
1221
    g_strfreev(parts);
1222

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

                
1228
    return;
1229
}
1230

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

                
1247
    g_static_mutex_lock(&mutex);
1248

                
1249
    SSLeay_add_ssl_algorithms();
1250
    meth = SSLv23_client_method();
1251
    SSL_load_error_strings();
1252
    g_static_mutex_unlock(&mutex);
1253

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

                
1260
        return NULL;
1261
    }
1262

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

                
1266
    ssl = SSL_new (ctx);
1267
    SSL_CTX_free(ctx);
1268

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

                
1274
        return NULL;
1275
    }
1276

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

                
1284
        return NULL;
1285
    }
1286

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

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

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

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

                
1307
        X509_free (server_cert);
1308
    }
1309
#endif
1310

                
1311
    return ssl;
1312
}
1313

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

                
1324
    g_return_if_fail(conn != NULL);
1325

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

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

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

                
1347
        strip_newline(buf);
1348
    } while (strlen(buf) > 0);
1349

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

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

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

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

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

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

                
1387
    *res = NULL;
1388

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

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

                
1399
            g_static_mutex_unlock(&mutex);
1400

                
1401
            return n;
1402
        }
1403

                
1404
        list = g_list_next(list);
1405
    }
1406

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

                
1412
    memset(&hints, 0, sizeof(struct addrinfo));
1413

                
1414
    hints.ai_family = AF_UNSPEC;
1415
    hints.ai_socktype = SOCK_STREAM;
1416

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

                
1421
    *res = entry->res;
1422
    n = entry->n;
1423

                
1424
    g_static_mutex_unlock(&mutex);
1425

                
1426
    return n;
1427
}
1428

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

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

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

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

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

                
1467
    get_proxy_settings(conn->server_info.hostname, &proxy_host, &proxy_port);
1468

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

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

                
1482
    last_errno = 0;
1483

                
1484
    while (res) {
1485
        int ret;
1486

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

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

                
1504
            if (errmsg) {
1505
                if (*errmsg) {
1506
                    g_free(*errmsg);
1507
                }
1508

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

                
1516
            continue;
1517
        }
1518

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

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

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

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

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

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

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

                
1576
                g_usleep(G_USEC_PER_SEC / 10);
1577

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

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

                
1594
            FD_ZERO(&send_fds);
1595
            FD_SET(conn->poll_fd.fd, &send_fds);
1596

                
1597
            tv.tv_sec = 5;
1598
            tv.tv_usec = 0;
1599

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

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

                
1626
        if (errmsg) {
1627
            if (*errmsg) {
1628
                g_free(*errmsg);
1629
            }
1630

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

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

                
1648
        nntpconnection_disconnect_from_server(conn, disconnect_type, strerror(last_errno), __FILE__, __LINE__);
1649

                
1650
        res = res->ai_next;
1651
    }
1652

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

                
1662
            default:
1663
                return NNTP_CONNECTION_ERROR_CONNECTION_REFUSED;
1664
        };
1665
    }
1666

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

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

                
1683
    if (proxy_host) {
1684
        char cmd[128];
1685

                
1686
        memset(&cmd, 0, sizeof(cmd));
1687
        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);
1688
        send(conn->poll_fd.fd, cmd, strlen(cmd), 0);
1689

                
1690
        g_free(proxy_host);
1691

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

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

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

                
1712
    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);
1713

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

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

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

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

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

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

                
1751
    if (conn->part) {
1752
        conn->part = NULL;
1753
    }
1754

                
1755
    conn->job_type = NNTP_JOB_TYPE_NONE;
1756

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

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

                
1768
    if (!conn->active_method) {
1769
        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);
1770
        nntpconnection_disconnect_from_server(conn, DISCONNECT_UNEXPECTED, _("NNTP Connection is in an undefined state"), __FILE__, __LINE__);
1771
        return;
1772
    }
1773

                
1774
    conn->active_method(conn);
1775
    conn->last_activity_stamp = time(NULL);
1776
}