1 /*
2     prot_nntp.* - nntp protocol handler
3     Copyright (C) 1999-2004  Matthew Mueller <donut AT dakotacom.net>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 #include "prot_nntp.h"
24 #include <list>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <ctype.h>
28 
29 #include "misc.h"
30 #include "path.h"
31 #include "termstuff.h"
32 #include "strreps.h"
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <time.h>
37 #include <unistd.h>
38 #include "log.h"
39 #include "file.h"
40 #include "nget.h"
41 #include "status.h"
42 #include "strtoker.h"
43 #include "par.h"
44 #include "decode.h"
45 
46 extern SockPool sockpool;
47 
48 
printCommEx(const baseEx & e,const c_server::ptr & s,int redone,const nget_options & options)49 static void printCommEx(const baseEx &e, const c_server::ptr &s, int redone, const nget_options &options) {
50 	if (e.isfatal())
51 		print_ex_with_message(e, "fatal error, won't try %s again", s->alias.c_str());
52 	else if (redone+1 < options.maxretry)
53 		print_ex_with_message(e, "error, will try %s again", s->alias.c_str());
54 	else //if this is the last retry, don't say that we will try it again.
55 		print_ex_with_message(e, "error, retries exhausted, won't try %s again", s->alias.c_str());
56 }
57 
putline(int echo,const char * str,...)58 int c_prot_nntp::putline(int echo,const char * str,...){
59 	va_list ap;
60 	va_start(ap,str);
61 	int i=connection->doputline(echo,str,ap);
62 	va_end(ap);
63 	return i;
64 }
stdputline(int echo,const char * str,...)65 int c_prot_nntp::stdputline(int echo,const char * str,...){
66 	va_list ap;
67 	int i;
68 	va_start(ap,str);
69 	connection->doputline(echo,str,ap);
70 	va_end(ap);
71 	i=getreply(echo);
72 	if (i==450 || i==480) {
73 		nntp_auth();
74 		va_start(ap,str);
75 		connection->doputline(echo,str,ap);
76 		va_end(ap);
77 		i=getreply(echo);
78 	}
79 	return i;
80 }
81 
chkreply(int reply) const82 int c_prot_nntp::chkreply(int reply) const {
83 //	int i=getreply(echo);
84 	if (reply==400) {
85 		// 400 response is used on connection for "Service temporarily unavailable" or during a session if the server has to terminate the connection for some reason.
86 		connection->close(1);
87 		throw ProtocolExError(Ex_INIT,"server says byebye: %s", cbuf);
88 	}
89 	if (reply/100!=2)
90 		throw ProtocolExFatal(Ex_INIT,"bad reply %i: %s",reply,cbuf);
91 	return reply;
92 }
93 
chkreply_setok(int reply)94 int c_prot_nntp::chkreply_setok(int reply){
95 	//only set the server_ok flag if the command had a "normal" error status (like group not found, article expired, etc).  If the command sequence completes successfully, then the server_ok will be set before releasing the ConnectionHolder.
96 	if (reply/100==4 && reply!=400)
97 		connection->server_ok = true;
98 	return chkreply(reply);
99 }
100 
getline(int echo)101 int c_prot_nntp::getline(int echo){
102 	int r = connection->getline(echo);
103 	cbuf = connection->sock.rbufp();
104 	return r;
105 }
106 
getreply(int echo)107 int c_prot_nntp::getreply(int echo){
108 	int code;
109 	if ((code=getline(echo))>=0)
110 		code=atoi(cbuf);
111 	return code;
112 }
113 
114 
115 class Progress {
116 	public:
117 		time_t lasttime, starttime, curt;
needupdate(void)118 		bool needupdate(void) {
119 			time(&curt);
120 			return !quiet && curt>lasttime;
121 		};
Progress(void)122 		Progress(void) {
123 			lasttime = 0;
124 			starttime = time(NULL);
125 		};
126 };
127 
128 
129 class DumbProgress : public Progress {
130 	public:
print_retrieving(const char * what,ulong done,ulong bytes)131 		void print_retrieving(const char *what, ulong done, ulong bytes){
132 			time(&lasttime);
133 			time_t dtime=lasttime-starttime;
134 			long Bps=(dtime>0)?bytes/dtime:0;
135 			if (!quiet) clear_line_and_return();
136 			printf("Retrieving %s: %lu %liB/s %s",what,done,Bps,durationstr(dtime).c_str());
137 
138 			fflush(stdout);//@@@@
139 		}
140 };
141 
nntp_dogetgrouplist(void)142 void c_prot_nntp::nntp_dogetgrouplist(void){
143 	ulong bytes=0, done=0;
144 	DumbProgress progress;
145 	while (1) {
146 		if (progress.needupdate())
147 			progress.print_retrieving("group list", done, bytes);
148 		bytes+=getline(debug>=DEBUG_ALL);
149 		if (cbuf[0]=='.' && cbuf[1]=='\0')break;
150 		char * p = strpbrk(cbuf, " \t");
151 		if (p)
152 			*p = '\0';
153 		//####do something with last/first/postingallowed info?
154 
155 		glist->addgroup(connection->server->serverid, cbuf);
156 		done++;
157 	}
158 	if(quiet<2){
159 		progress.print_retrieving("group list", done, bytes);
160 		printf("\n");
161 	}
162 }
nntp_dogrouplist(void)163 void c_prot_nntp::nntp_dogrouplist(void){
164 	c_nntp_grouplist_server_info::ptr servinfo = glist->getserverinfo(connection->server->serverid);
165 	string newdate;
166 	int r=stdputline(quiet<2,"DATE");
167 	if (r==111)
168 		newdate = cbuf+4;
169 	else {
170 		char tbuf[40];
171 		time_t t = time(NULL);
172 		tconv(tbuf, 40, &t, "%Y%m%d%H%M%S", 0);
173 		printf("bad DATE reply '%s', using local date %s\n", cbuf, tbuf);
174 		newdate = tbuf;
175 	}
176 
177 	if (!servinfo->date.empty()) {
178 		int dsepn = servinfo->date.size()-6;
179 		chkreply(stdputline(quiet<2,"NEWGROUPS %s %s GMT",servinfo->date.substr(0,dsepn).c_str(), servinfo->date.substr(dsepn,6).c_str()));
180 	}
181 	if (servinfo->date.empty()) {
182 		chkreply(stdputline(quiet<2,"LIST"));
183 	}
184 
185 	nntp_dogetgrouplist();
186 	servinfo->date = newdate;
187 }
188 
nntp_dogrouplist(const char * wildmat)189 void c_prot_nntp::nntp_dogrouplist(const char *wildmat){
190 	chkreply(stdputline(quiet<2,"LIST ACTIVE %s", wildmat));
191 	nntp_dogetgrouplist();
192 }
193 
nntp_dogroupdescriptions(const char * wildmat)194 void c_prot_nntp::nntp_dogroupdescriptions(const char *wildmat){
195 	ulong bytes=0, done=0;
196 	int r;
197 	if (wildmat)
198 		r=stdputline(quiet<2,"LIST NEWSGROUPS %s", wildmat);
199 	else
200 		r=stdputline(quiet<2,"LIST NEWSGROUPS");
201 	if (r/100!=2) {
202 		// if server doesn't support LIST NEWSGROUPS, just ignore the error.
203 		return;
204 	}
205 	DumbProgress progress;
206 	while (1) {
207 		if (progress.needupdate())
208 			progress.print_retrieving("group descriptions", done, bytes);
209 
210 		bytes+=getline(debug>=DEBUG_ALL);
211 		if (cbuf[0]=='.' && cbuf[1]=='\0')break;
212 		char * desc = strpbrk(cbuf, " \t");
213 		if (desc) {
214 			*desc = '\0';
215 			desc++;
216 			desc += strspn(desc, " \t");
217 		}
218 
219 		glist->addgroupdesc(connection->server->serverid, cbuf, desc ? desc : "");
220 		done++;
221 	}
222 	if(quiet<2){
223 		progress.print_retrieving("group descriptions", done, bytes);
224 		printf("\n");
225 	}
226 }
227 
nntp_grouplist(int update,const nget_options & options)228 void c_prot_nntp::nntp_grouplist(int update, const nget_options &options){
229 	if (!glist)
230 		glist = new c_nntp_grouplist(ngcachehome+"newsgroups");
231 	if (update) {
232 		c_server::ptr s;
233 		list<c_server::ptr> doservers;
234 		c_server_priority_grouping *priogroup;
235 		if (!(priogroup=nconfig.getpriogrouping("_grouplist")))
236 			priogroup=nconfig.getpriogrouping("default");
237 		nntp_simple_prioritize(priogroup, doservers);
238 
239 		int redone=0, succeeded=0, attempted=doservers.size();
240 		while (!doservers.empty() && redone<options.maxretry) {
241 			if (redone){
242 				printf("nntp_grouplist: trying again. %i\n",redone);
243 				if (options.retrydelay)
244 					sleep(options.retrydelay);
245 			}
246 			list<c_server::ptr>::iterator dsi = doservers.begin();
247 			list<c_server::ptr>::iterator ds_erase_i;
248 			while (dsi != doservers.end()){
249 				s=(*dsi);
250 				assert(s);
251 				PDEBUG(DEBUG_MED,"nntp_grouplist: serv(%s) %f>=%f",s->alias.c_str(),priogroup->getserverpriority(s),priogroup->defglevel);
252 				try {
253 					ConnectionHolder holder(&sockpool, &connection, s);
254 					nntp_doopen();
255 					nntp_dogrouplist();
256 					nntp_dogroupdescriptions();//####make this a seperate option?
257 					succeeded++;
258 					connection->server_ok=true;
259 				} catch (baseCommEx &e) {
260 					printCommEx(e, s, redone, options);
261 					if (!e.isfatal()) {
262 						++dsi;
263 						continue;//don't remove server from list
264 					}
265 				}
266 				ds_erase_i = dsi;
267 				++dsi;
268 				doservers.erase(ds_erase_i);
269 			}
270 			redone++;
271 		}
272 		if (succeeded) {
273 			set_grouplist_warn_status(attempted - succeeded);
274 			set_grouplist_ok_status();
275 		}else {
276 			set_grouplist_error_status();
277 			printf("no servers queried successfully\n");
278 		}
279 	}
280 }
281 
nntp_xgrouplist(const t_xpat_list & patinfos,const nget_options & options)282 void c_prot_nntp::nntp_xgrouplist(const t_xpat_list &patinfos, const nget_options &options){
283 	glist = new c_nntp_grouplist();
284 
285 	c_server::ptr s;
286 	list<c_server::ptr> doservers;
287 	c_server_priority_grouping *priogroup;
288 	if (!(priogroup=nconfig.getpriogrouping("_grouplist")))
289 		priogroup=nconfig.getpriogrouping("default");
290 	nntp_simple_prioritize(priogroup, doservers);
291 
292 	int redone=0, succeeded=0, attempted=doservers.size();
293 	while (!doservers.empty() && redone<options.maxretry) {
294 		if (redone){
295 			printf("nntp_xgrouplist: trying again. %i\n",redone);
296 			if (options.retrydelay)
297 				sleep(options.retrydelay);
298 		}
299 		list<c_server::ptr>::iterator dsi = doservers.begin();
300 		list<c_server::ptr>::iterator ds_erase_i;
301 		while (dsi != doservers.end()){
302 			s=(*dsi);
303 			assert(s);
304 			PDEBUG(DEBUG_MED,"nntp_xgrouplist: serv(%s) %f>=%f",s->alias.c_str(),priogroup->getserverpriority(s),priogroup->defglevel);
305 			try {
306 				ConnectionHolder holder(&sockpool, &connection, s);
307 				nntp_doopen();
308 				for (t_xpat_list::const_iterator i = patinfos.begin(); i != patinfos.end(); ++i) {
309 					nntp_dogrouplist((*i)->wildmat.c_str());
310 					nntp_dogroupdescriptions((*i)->wildmat.c_str());//####make this a seperate option? //######handle (skip?) servers that ignore wildmat option to LIST NEWSGROUPS somehow?  .. not that its really possible to tell before hand whether the server will ignore it.
311 				}
312 				succeeded++;
313 				connection->server_ok=true;
314 			} catch (baseCommEx &e) {
315 				printCommEx(e, s, redone, options);
316 				if (!e.isfatal()) {
317 					++dsi;
318 					continue;//don't remove server from list
319 				}
320 			}
321 			ds_erase_i = dsi;
322 			++dsi;
323 			doservers.erase(ds_erase_i);
324 		}
325 		redone++;
326 	}
327 	if (succeeded) {
328 		set_grouplist_warn_status(attempted - succeeded);
329 		set_grouplist_ok_status();
330 	}else {
331 		set_grouplist_error_status();
332 		printf("no servers queried successfully\n");
333 	}
334 }
335 
nntp_grouplist_search(const t_grouplist_getinfo_list & getinfos,const nget_options & options)336 void c_prot_nntp::nntp_grouplist_search(const t_grouplist_getinfo_list &getinfos, const nget_options &options){
337 	if (glist) {
338 		glist->printinfos(getinfos);
339 		//####should we glist=NULL; here?
340 	} else {
341 		nntp_grouplist_printinfos(getinfos);
342 	}
343 }
344 
nntp_grouplist_search(const t_grouplist_getinfo_list & getinfos,const t_xpat_list & patinfos,const nget_options & options)345 void c_prot_nntp::nntp_grouplist_search(const t_grouplist_getinfo_list &getinfos, const t_xpat_list &patinfos, const nget_options &options){
346 	nntp_xgrouplist(patinfos, options);
347 	glist->printinfos(getinfos);
348 	//####should we glist=NULL; here?
349 }
350 
351 
352 class XoverProgress : public Progress {
353 	public:
print_retrieving_headers(ulong low,ulong high,ulong done,ulong realtotal,ulong total,ulong bytes,int doneranges,int streamed,int totalranges)354 		void print_retrieving_headers(ulong low,ulong high,ulong done,ulong realtotal,ulong total,ulong bytes,int doneranges,int streamed,int totalranges){
355 			time(&lasttime);
356 			time_t dtime=lasttime-starttime;
357 			long Bps=(dtime>0)?bytes/dtime:0;
358 			long Bph=(done>0)?bytes/done:3;//if no headers have been retrieved yet, set the bytes per header to 3 just to get some sort of timeleft display.  (3=strlen(".\r\n"))
359 			if (!quiet) clear_line_and_return();
360 			printf("Retrieving headers %lu-%lu : %lu/%lu/%lu %3li%% %liB/s %s",low,high,done,realtotal,total,(realtotal!=0)?((done+(total-realtotal))*100/total):0,Bps,durationstr(realtotal==done?dtime:(Bps>0)?((realtotal-done)*Bph)/(Bps):0).c_str());
361 			if (totalranges>1)
362 				printf(" (%i/%i/%i)",doneranges,doneranges+streamed,totalranges);
363 			fflush(stdout);//@@@@
364 		};
365 };
366 /*
367 2019    Re: question    Katya Moon <MoonAngel@shadowrealm.com>  Wed, 17 Nov 1999 09:42:53 -0600 <xMwyOI+pJ=mVLqMociXeHexHGW92@4ax.com>      <3830CF31.D41E1511@tampabay.rr.com>     1145    9       Xref: rQdQ alt.chocobo:2019
368 2020    Re: well then!  Katya Moon <MoonAngel@shadowrealm.com>  Wed, 17 Nov 1999 09:44:37 -0600 <Fs0yOEVKaIamwKGBgE=82Fk21OcM@4ax.com>      <3831B98A.72815A01@tampabay.rr.com>     1209    10      Xref: rQdQ alt.chocobo:2020
369 2021    free me from this hideous thing!        Selah <sslanka@tampabay.rr.com> Wed, 17 Nov 1999 20:17:35 GMT   <38330E93.C8AC2671@tampabay.rr.com>         1142    8       Xref: rQdQ alt.chocobo:2021
370 .
371 0=articlenum
372 1=subject
373 2=author
374 3=date
375 4=message id
376 5=references (aka in-reply-to) (used for threading)
377 6=bytes
378 7=lines
379 ... following are optional (and possibly different):
380 8=crossreferences
381 
382 The sequence of fields must be in this order:
383 subject, author, date, message-id, references, byte count, and line count.
384 Other optional fields may follow line count. These fields are specified by
385 examining the response to the LIST OVERVIEW.FMT command. Where no data
386 exists, a null field must be provided
387 */
doxover(const c_group_info::ptr & group,c_nrange * r)388 void c_prot_nntp::doxover(const c_group_info::ptr &group, c_nrange *r){
389 	if (r->empty())
390 		return;
391 	XoverProgress progress;
392 	ulong lowest=r->rlist.begin()->second, highest=r->rlist.rbegin()->first;
393 	ulong bytes=0, realnum=0, realtotal=r->get_total(), last;
394 	ulong total=realtotal;
395 	assert(connection);
396 
397 	t_rlist::iterator r_ri;
398 	t_rlist::iterator w_ri=r->rlist.begin();
399 	int streamed = 0, totalranges = r->num_ranges(), doneranges = 0;
400 	for (r_ri=r->rlist.begin();r_ri!=r->rlist.end();++r_ri, ++doneranges){
401 		if (progress.needupdate())
402 			progress.print_retrieving_headers(lowest,highest,realnum,realtotal,total,bytes,doneranges,streamed,totalranges);
403 		char *p;
404 		char *t[10];
405 		int i;
406 		while (w_ri!=r->rlist.end() && streamed<=connection->server->maxstreaming) {
407 			ulong low=(*w_ri).second, high=(*w_ri).first;
408 			if (low==high)
409 				putline(debug>=DEBUG_MED,"XOVER %lu",low);//save a few bytes
410 			else
411 				putline(debug>=DEBUG_MED,"XOVER %lu-%lu",low,high);
412 			++w_ri;
413 			++streamed;
414 		}
415 		ulong low=(*r_ri).second, high=(*r_ri).first;
416 		last = low;
417 		chkreply_setok(getreply(debug>=DEBUG_MED));
418 		bytes+=strlen(cbuf)+2;//#### ugly.
419 		--streamed;
420 		unsigned long an=0;
421 		c_nntp_header nh;
422 		nh.group = group;
423 		char * tp;
424 		do {
425 			bytes+=getline(debug>=DEBUG_ALL);
426 			if (cbuf[0]=='.')break;
427 			p=cbuf;
428 			for(i=0;i<10;i++){
429 				if((t[i]=goodstrtok(&p,'\t'))==NULL){
430 					break;
431 				}
432 				// fields 0, 6, 7 must all be numeric
433 				// otherwise restart
434 				if (i==0 || i==6 || i==7){
435 					tp=t[i];
436 					while (*tp){
437 						if (!isdigit(*tp) && !isspace(*tp))
438 							break;
439 						tp++;
440 					}
441 					// did the test finish, and/or was it a blank field?
442 					if (*tp && tp!=t[i]){
443 						// no - get out and read the next line
444 						// Is this how we want to handle it? SMW
445 						printf("error retrieving xover (%i non-numeric)\n",i);
446 						break;
447 					}
448 				}
449 			}
450 			if (i<=1 && streamed) {
451 				if (atoul(cbuf)==224){//some servers (DNEWS) will drop data while doing xover streaming, but seeminly only the ends of the requests.  Then you get a 224 data follows message for the next range without getting the rest of the current range or the "." line.  Retrying starting from the current range would be better, but this is easier ;)
452 					connection->server->maxstreaming=0;
453 					connection->close(1);
454 					set_xover_warn_status();
455 					throw ProtocolExError(Ex_INIT, "XOVER streaming error: recieved start of next range without end of current.  Setting maxstreaming to 0. (You may wish to decrease or disable maxstreaming for this server in your ngetrc.)");
456 				}
457 			}
458 			if (i>7){
459 				//	c=new c_nntp_cache_item(atol(t[0]),	decode_textdate(t[3]), atol(t[6]), atol(t[7]),t[1],t[2]);
460 				//gcache->additem(c);
461 				an=atoul(t[0]);
462 				nh.set(t[1],t[2],an,decode_textdate(t[3]),atoul(t[6]),atoul(t[7]),t[4],t[5]);
463 				nh.serverid=connection->server->serverid;
464 				//gcache->additem(an, decode_textdate(t[3]), atol(t[6]), atol(t[7]),t[1],t[2]);
465 				gcache->additem(&nh);
466 				//delete nh;
467 				realnum++;
468 				if (an<low || an>high || an<last) {
469 					printf("weird: articlenum %lu not in range %lu-%lu (last=%lu)\n",an,low,high,last);
470 					set_xover_warn_status();
471 				} else {
472 #ifndef NDEBUG
473 					ulong ort=realtotal;
474 #endif
475 					realtotal -= an - last;
476 					assert(ort>=realtotal);
477 					last = an + 1;
478 				}
479 				if (progress.needupdate())
480 					progress.print_retrieving_headers(lowest,highest,realnum,realtotal,total,bytes,doneranges,streamed,totalranges);
481 			}else{
482 				set_xover_warn_status();
483 				printf("error retrieving xover (%i<=7): ",i);
484 				for (int j=0;j<=i;j++)
485 					if (t[j])
486 						printf("%i:%s ",j,t[j]);
487 				if (p)
488 					printf("*:%s",p);
489 				printf("\n");
490 			}
491 		}while(1);
492 #ifndef NDEBUG
493 		ulong ort=realtotal;
494 #endif
495 		realtotal -= high - last + 1;
496 		assert(ort>=realtotal);
497 	}
498 	if(quiet<2 /*&& an*/){
499 		progress.print_retrieving_headers(lowest,highest,realnum,realtotal,total,bytes,doneranges,streamed,totalranges);
500 		printf("\n");
501 	}
502 }
doxover(const c_group_info::ptr & group,ulong low,ulong high)503 void c_prot_nntp::doxover(const c_group_info::ptr &group, ulong low, ulong high){
504 	c_nrange r;
505 	r.insert(low, high);
506 	doxover(group, &r);
507 }
508 
509 class ListgroupProgress : public Progress {
510 	public:
print_retrieving_article_list(ulong low,ulong high,ulong done,ulong total,ulong bytes,bool finished=false)511 		void print_retrieving_article_list(ulong low, ulong high, ulong done, ulong total, ulong bytes, bool finished=false){
512 			time(&lasttime);
513 			time_t dtime=lasttime-starttime;
514 			long Bps=(dtime>0)?bytes/dtime:0;
515 			long Bph=(done>0)?bytes/done:3;//if no headers have been retrieved yet, set the bytes per header to 3 just to get some sort of timeleft display.  (3=strlen(".\r\n"))
516 			if (!quiet) clear_line_and_return();
517 			printf("Retrieving article list %lu-%lu : %lu/%lu %3li%% %liB/s %s",low,high,done,total,(finished || total==0)?100:done*100/total,Bps,durationstr(finished?dtime:(Bps>0)?((total-done)*Bph)/(Bps):0).c_str());
518 
519 			fflush(stdout);//@@@@
520 		}
521 };
dolistgroup(c_nrange & existing,ulong lowest,ulong highest,ulong total)522 void c_prot_nntp::dolistgroup(c_nrange &existing, ulong lowest, ulong highest, ulong total) {
523 	ListgroupProgress progress;
524 	chkreply(stdputline(debug>=DEBUG_MED,"LISTGROUP"));
525 	ulong bytes=0, count=0, an;
526 	char *eptr;
527 	do {
528 		if (progress.needupdate())
529 			progress.print_retrieving_article_list(lowest,highest,count,total,bytes);
530 		bytes+=getline(debug>=DEBUG_ALL);
531 		if (cbuf[0]=='.')break;
532 		an = strtoul(cbuf, &eptr, 10);
533 		if (*cbuf=='\0' || *eptr!='\0') {
534 			printf("error retrieving article number\n");
535 			continue;
536 		}
537 		existing.insert(an);
538 		count++;
539 	}while (1);
540 	if(quiet<2){
541 		progress.print_retrieving_article_list(lowest,highest,count,total,bytes,true);
542 		printf("\n");
543 	}
544 }
545 
nntp_dogroup(const c_group_info::ptr & group,ulong & num,ulong & low,ulong & high)546 void c_prot_nntp::nntp_dogroup(const c_group_info::ptr &group, ulong &num, ulong &low, ulong &high) {
547 	assert(connection);
548 	connection->curgroup=NULL; //unset curgroup, in case the group we asked for does not exist, some servers will keep us in the old group, whereas others will put us into the no group selected state.
549 	int reply = stdputline(quiet<2,"GROUP %s",group->group.c_str());
550 	if (reply/100==4) // if group doesn't exist, set ok flag.  Otherwise let XOVER/ARTICLE reply set it. (If we always set it here, failure of xover/article would never result in penalization.  But if we only set it after xover/article, then a host could be incorrectly penalized just because it didn't have the group (eg, if maxconnections=1 so it closed connection before any other commands could succeed and setok))
551 		chkreply_setok(reply);
552 	else
553 		chkreply(reply);
554 	connection->curgroup=group;
555 
556 	char *p;
557 	p=strchr(cbuf,' ')+1;
558 	num=atoul(p);
559 	p=strchr(p,' ')+1;
560 	low=atoul(p);
561 	p=strchr(p,' ')+1;
562 	high=atoul(p);
563 	//printf("%i, %i, %i\n",num,low,high);
564 }
565 
nntp_dogroup(const c_group_info::ptr & group,bool getheaders,int forcefullxover)566 void c_prot_nntp::nntp_dogroup(const c_group_info::ptr &group, bool getheaders, int forcefullxover){
567 	ulong num,low,high;
568 	if (connection->curgroup!=group || getheaders){
569 		nntp_dogroup(group, num,low,high);
570 	}
571 
572 	if (getheaders){
573 		assert(gcache);
574 		const int fullxover = forcefullxover==-1 ? connection->server->fullxover : forcefullxover;
575 
576 		c_nntp_server_info* servinfo=gcache->getserverinfo(connection->server->serverid);
577 		assert(servinfo);
578 		//shouldn't do fullxover2 on first update of group or if cached headers are ALL older than available headers
579 		if (servinfo->high!=0 && servinfo->high>=low && fullxover==2){
580 			c_nrange existing;
581 			dolistgroup(existing, low, high, num);
582 
583 			c_nrange nonexistant;
584 			nonexistant.invert(existing);
585 			gcache->flush(servinfo,nonexistant,midinfo);
586 			servinfo->low=existing.empty()?low:existing.low();//####this ok?
587 
588 			c_nrange r(existing);
589 			gcache->getxrange(servinfo,&r);//remove the article numbers we already have, leaving only the ones we still need to get.
590 
591 			doxover(group, &r);
592 		}else{
593 			if (low>servinfo->low)
594 				gcache->flushlow(servinfo,low,midinfo);
595 			if (fullxover){
596 				c_nrange r;
597 				gcache->getxrange(servinfo,low,high,&r);
598 				doxover(group, &r);
599 			}else{
600 				c_nrange r;
601 
602 				if (servinfo->high==0)
603 					r.insert(low, high);
604 				else {
605 					if (servinfo->high<high)
606 						r.insert(servinfo->high+1, high);
607 					if (servinfo->low>low)
608 						r.insert(low, servinfo->low-1);
609 				}
610 				doxover(group, &r);
611 			}
612 		}
613 	}
614 };
615 
nntp_simple_prioritize(c_server_priority_grouping * priogroup,list<c_server::ptr> & doservers)616 void c_prot_nntp::nntp_simple_prioritize(c_server_priority_grouping *priogroup, list<c_server::ptr> &doservers){
617 	if (force_host){
618 		doservers.push_front(force_host);
619 	} else {
620 		t_server_list::iterator sli = nconfig.serv.begin();
621 		for (;sli!=nconfig.serv.end();++sli){
622 			c_server::ptr &s=(*sli).second;
623 			assert(s);
624 			if (priogroup->getserverpriority(s) >= priogroup->defglevel) {
625 				if (sockpool.is_connected(s)) //put current connected hosts at start of list
626 					doservers.push_front(s);
627 				else
628 					doservers.push_back(s);
629 			}
630 		}
631 	}
632 }
633 
634 
doxpat(c_nrange & r,c_xpat::ptr xpat,ulong total,ulong lowest,ulong highest)635 void c_prot_nntp::doxpat(c_nrange &r, c_xpat::ptr xpat, ulong total, ulong lowest, ulong highest) {
636 	assert(gcache);
637 	assert(connection);
638 
639 	chkreply_setok(stdputline(debug>=DEBUG_MED,"XPAT %s %lu-%lu %s", xpat->field.c_str(), lowest, highest, xpat->wildmat.c_str()));
640 
641 	ListgroupProgress progress;
642 	ulong bytes=0, realnum=0;
643 	ulong an;
644 	char *eptr;
645 
646 	do {
647 		if (progress.needupdate())
648 			progress.print_retrieving_article_list(lowest,highest,realnum,total,bytes,false);
649 		bytes+=getline(debug>=DEBUG_ALL);
650 		if (cbuf[0]=='.')break;
651 		an = strtoul(cbuf, &eptr, 10);
652 		if (*cbuf=='\0' || !(*eptr==' ' || *eptr=='\t')) {
653 			printf("error retrieving article number\n");
654 			continue;
655 		}
656 		r.insert(an);
657 		realnum++;
658 		if (progress.needupdate())
659 			progress.print_retrieving_article_list(lowest,highest,realnum,total,bytes,false);
660 
661 	}while (1);
662 	if(quiet<2 /*&& an*/){
663 		progress.print_retrieving_article_list(lowest,highest,realnum,total,bytes,true);
664 		printf("\n");
665 	}
666 }
667 
nntp_xgroup(const c_group_info::ptr & group,const t_xpat_list & patinfos,const nget_options & options)668 void c_prot_nntp::nntp_xgroup(const c_group_info::ptr &group, const t_xpat_list &patinfos, const nget_options &options) {
669 	c_server::ptr s;
670 	list<c_server::ptr> doservers;
671 	nntp_simple_prioritize(group->priogrouping, doservers);
672 
673 	int redone=0, succeeded=0, attempted=doservers.size();
674 	while (!doservers.empty() && redone<options.maxretry) {
675 		if (redone){
676 			printf("nntp_xgroup: trying again. %i\n",redone);
677 			if (options.retrydelay)
678 				sleep(options.retrydelay);
679 		}
680 		list<c_server::ptr>::iterator dsi = doservers.begin();
681 		list<c_server::ptr>::iterator ds_erase_i;
682 		while (dsi != doservers.end()){
683 			s=(*dsi);
684 			assert(s);
685 			PDEBUG(DEBUG_MED,"nntp_xgroup: serv(%s) %f>=%f",s->alias.c_str(),group->priogrouping->getserverpriority(s),group->priogrouping->defglevel);
686 			try {
687 				ConnectionHolder holder(&sockpool, &connection, s);
688 				nntp_doopen();
689 				ulong num,low,high;
690 				nntp_dogroup(group, num,low,high);
691 				c_nrange r;
692 				for (t_xpat_list::const_iterator i = patinfos.begin(); i != patinfos.end(); ++i)
693 					doxpat(r, *i, num, low, high);
694 				doxover(group, &r);
695 				succeeded++;
696 				connection->server_ok=true;
697 			} catch (baseCommEx &e) {
698 				printCommEx(e, s, redone, options);
699 				if (!e.isfatal()) {
700 					++dsi;
701 					continue;//don't remove server from list
702 				}
703 			}
704 			ds_erase_i = dsi;
705 			++dsi;
706 			doservers.erase(ds_erase_i);
707 		}
708 		redone++;
709 	}
710 	if (succeeded) {
711 		set_group_warn_status(attempted - succeeded);
712 		set_group_ok_status();
713 	}else {
714 		set_group_error_status();
715 		printf("no servers queried successfully\n");
716 	}
717 
718 	gcache_ismultiserver = gcache->ismultiserver();
719 }
720 
nntp_group(const c_group_info::ptr & ngroup,bool getheaders,const nget_options & options)721 void c_prot_nntp::nntp_group(const c_group_info::ptr &ngroup, bool getheaders, const nget_options &options){
722 	if (group == ngroup && gcache && !getheaders)
723 		return; // group is already selected, don't waste time reloading it
724 
725 	assert(ngroup);
726 	group=ngroup;
727 //	if (gcache) delete gcache;
728 	cleanupcache();
729 
730 	midinfo=new meta_mid_info(ngcachehome, ngroup);
731 	//gcache=new c_nntp_cache(nghome,group->group + ",cache");
732 	gcache=new c_nntp_cache(ngcachehome, group, midinfo);
733 	if (getheaders){
734 		c_server::ptr s;
735 		list<c_server::ptr> doservers;
736 		nntp_simple_prioritize(group->priogrouping, doservers);
737 
738 		int redone=0, succeeded=0, attempted=doservers.size();
739 		while (!doservers.empty() && redone<options.maxretry) {
740 			if (redone){
741 				printf("nntp_group: trying again. %i\n",redone);
742 				if (options.retrydelay)
743 					sleep(options.retrydelay);
744 			}
745 			list<c_server::ptr>::iterator dsi = doservers.begin();
746 			list<c_server::ptr>::iterator ds_erase_i;
747 			while (dsi != doservers.end()){
748 				s=(*dsi);
749 				assert(s);
750 				PDEBUG(DEBUG_MED,"nntp_group: serv(%s) %f>=%f",s->alias.c_str(),group->priogrouping->getserverpriority(s),group->priogrouping->defglevel);
751 				try {
752 					ConnectionHolder holder(&sockpool, &connection, s);
753 					nntp_doopen();
754 					nntp_dogroup(ngroup, getheaders, options.fullxover);
755 					succeeded++;
756 					connection->server_ok=true;
757 				} catch (baseCommEx &e) {
758 					printCommEx(e, s, redone, options);
759 					if (!e.isfatal()) {
760 						++dsi;
761 						continue;//don't remove server from list
762 					}
763 				}
764 				ds_erase_i = dsi;
765 				++dsi;
766 				doservers.erase(ds_erase_i);
767 			}
768 			redone++;
769 		}
770 		if (succeeded) {
771 			set_group_warn_status(attempted - succeeded);
772 			set_group_ok_status();
773 		}else {
774 			set_group_error_status();
775 			printf("no servers queried successfully\n");
776 		}
777 	}
778 
779 	gcache_ismultiserver = gcache->ismultiserver();
780 }
781 
print_retrieving_articles(time_t curtime,quinfo * tot)782 inline void arinfo::print_retrieving_articles(time_t curtime, quinfo*tot){
783 	dtime=curtime-starttime;
784 	Bps=(dtime>0)?bytesdone/dtime:0;
785 	if (!quiet) clear_line_and_return();
786 	if (tot && tot->doarticle_show_multi!=NO_SHOW_MULTI)
787 		printf("%s ",server_name);
788 	printf("%lu (%i/%i): %li/%liL %li/%liB %3li%% %liB/s %s",
789 			anum,partnum,partreq,linesdone,linestot,bytesdone,bytestot,
790 			(linestot!=0)?(linesdone*100/linestot):0,Bps,
791 			durationstr((linesdone>=linestot)?dtime:((Bps>0)?(bytestot-bytesdone)/(Bps):-1)).c_str());
792 	if (tot)
793 		printf(" %li/%li %s",tot->filesdone,tot->filestot,
794 				//(tot->bytesdone>=tot->bytestot)?curtime-tot->starttime:((Bps>0)?(tot->bytestot-tot->bytesdone)/(Bps):-1));
795 //				(linesdone>=linestot)?curtime-tot->starttime:((Bps>0)?(tot->bytestot-tot->bytesdone)/(Bps):-1));
796 				durationstr((linesdone>=linestot)?curtime-tot->starttime:((Bps>0)?(tot->bytesleft-bytesdone)/(Bps):-1)).c_str());
797 	fflush(stdout);
798 }
799 
800 //inline void c_prot_nntp::nntp_print_retrieving_articles(long nnn, long tot,long done,long btot,long bbb,unsigned long obtot,unsigned long obdone,long ototf,long odonef,time_t tstarttime){
801 //	time_t dtime=lasttime-starttime;
802 //	long Bps=(dtime>0)?bbb/dtime:0;
803 //	printf("\rRetrieving article %li: %li/%liL %li/%liB %3li%% %liB/s %lis %li/%li %lis",nnn,done,tot,bbb,btot,(tot!=0)?(done*100/tot):0,Bps,(done>=tot)?dtime:((Bps>0)?(btot-bbb)/(Bps):-1),odonef,ototf,(obdone>=obtot)?lasttime-tstarttime:((Bps>0)?(botot-obdone)/(Bps):-1));
804 //	fflush(stdout);//@@@@
805 //}
806 
nntp_doarticle_prioritize(c_nntp_part * part,t_nntp_server_articles_prioritized & sap,bool docurservmult)807 int c_prot_nntp::nntp_doarticle_prioritize(c_nntp_part *part,t_nntp_server_articles_prioritized &sap,bool docurservmult){
808 	t_nntp_server_articles::iterator sai;
809 	c_nntp_server_article *sa=NULL;
810 	float prio;
811 	for (sai = part->articles.begin(); sai != part->articles.end(); ++sai){
812 		sa=(*sai);
813 		assert(sa);
814 		for (t_server_list_range servers = nconfig.getservers(sa->serverid); servers.first!=servers.second; ++servers.first) {
815 			const c_server::ptr &s = servers.first->second;
816 			if (force_host && s!=force_host)
817 				continue;
818 			prio=sa->group->priogrouping->getserverpriority(s);
819 			if (docurservmult){
820 				if (sockpool.is_connected(s))
821 					prio*=nconfig.curservmult;
822 			}
823 			PDEBUG(DEBUG_MED,"prioritizing server %s(%lu) article %lu prio %f",s->alias.c_str(),sa->serverid,sa->articlenum,prio);
824 			sap.insert(t_nntp_server_articles_prioritized::value_type(prio,t_real_server_article(sa,s)));
825 		}
826 	}
827 
828 	if (docurservmult && !sap.empty()) {
829 		int connected=0, nonconnected=0;
830 		t_nntp_server_articles_prioritized::iterator i;
831 		pair<t_nntp_server_articles_prioritized::iterator,t_nntp_server_articles_prioritized::iterator> firstrange = sap.equal_range(sap.rend()->first);
832 		for (i=firstrange.first; i!=firstrange.second; ++i)
833 			if (sockpool.is_connected(i->second.second))
834 				++connected;
835 			else
836 				++nonconnected;
837 
838 		if (connected && nonconnected) { //if both connected and nonconnected servers have the (same) highest priority, reprioritize the connected ones a bit higher to avoid excessive reconnecting.
839 			t_nntp_server_articles_prioritized::iterator ci;
840 			for (i=firstrange.first; i!=firstrange.second;){
841 				ci = i;
842 				++i;
843 				if (sockpool.is_connected(ci->second.second)) {
844 					prio=(*ci).first;
845 					sa=(*ci).second.first;
846 					c_server::ptr s = (*ci).second.second;
847 					sap.erase(ci);
848 					prio*=1.001;
849 					sap.insert(t_nntp_server_articles_prioritized::value_type(prio,t_real_server_article(sa,s)));
850 					PDEBUG(DEBUG_MED,"server %s(%lu) article %lu reprioritized %f",s->alias.c_str(),sa->serverid,sa->articlenum,prio);
851 				}
852 			}
853 		}
854 
855 	}
856 	return 0;
857 }
858 
859 
nntp_dowritelite_article(c_file & fw,c_nntp_part * part,char * fn)860 int c_prot_nntp::nntp_dowritelite_article(c_file &fw,c_nntp_part *part,char *fn){
861 	fw.putf("0\n%s\n%s\n",group?group->group.c_str():"",fn);
862 
863 	c_server::ptr whost;
864 	c_nntp_server_article *sa=NULL;
865 	t_nntp_server_articles_prioritized sap;
866 	t_nntp_server_articles_prioritized::iterator sapi;
867 	nntp_doarticle_prioritize(part,sap,false);
868 	fw.putf("%lu\n",(ulong)sap.size());
869 	for (sapi = sap.begin(); sapi != sap.end(); ++sapi){
870 		sa=(*sapi).second.first;
871 		whost=(*sapi).second.second;
872 		fw.putf("%s\t%s\t%s\n",whost->addr.c_str(),whost->user.c_str(),whost->pass.c_str());
873 		if (group)
874 			fw.putf("%lu\n",sa->articlenum);
875 		else
876 			fw.putf("%s\n",part->messageid.c_str());
877 		fw.putf("%lu\n%lu\n",sa->bytes,sa->lines);
878 	}
879 	return 0;
880 }
881 
nntp_dogetarticle(arinfo * ari,quinfo * toti,list<string> & buf)882 void c_prot_nntp::nntp_dogetarticle(arinfo*ari,quinfo*toti,list<string> &buf){
883 	int header=1;
884 	time_t curt,lastt=0;
885 	char *lp;
886 	time(&ari->starttime);
887 	curt=starttime;
888 	long glr;
889 	do {
890 		glr=getline(debug>=DEBUG_ALL);
891 		if (cbuf[0]=='.'){
892 			if(cbuf[1]==0)
893 				break;
894 			lp=cbuf+1;
895 		}else
896 			lp=cbuf;
897 		ari->bytesdone+=glr;
898 		ari->linesdone++;
899 		if (header && lp[0]==0){
900 			//			printf("\ntoasted header statssssssss\n");
901 			header=0;
902 			ari->linesdone=0;
903 			time(&ari->starttime);//bytes=0;
904 		}
905 		time(&curt);
906 		if (!quiet && curt>lastt){
907 			//		nntp_print_retrieving_articles(num,ltotal,lines,btotal,bytes);
908 			ari->print_retrieving_articles(curt,toti);
909 			lastt=curt;
910 		}
911 		buf.push_back(lp);
912 	}while(1);
913 	if (quiet<2){
914 		//nntp_print_retrieving_articles(num,ltotal,lines,btotal,bytes);
915 		ari->print_retrieving_articles(curt,toti);
916 		printf("\n");
917 	}
918 
919 	//some servers report # of bytes a bit off of what we expect.
920 	if (!((ari->bytesdone <= ari->bytestot+3 && ari->bytesdone >= ari->bytestot-3) ||
921 			//some servers also report # of bytes counted with LF endings, then send with CRLF
922 			(ari->bytesdone <= ari->bytestot+3+ari->linesdone || ari->bytesdone >= ari->bytestot-3+ari->linesdone)) ||
923 			ari->linesdone!=ari->linestot){
924 		printf("doarticle %lu: %lu!=%lu || %lu!=%lu\n",ari->anum,ari->bytesdone,ari->bytestot,ari->linesdone,ari->linestot);
925 	}
926 	c_server::ptr host = connection->server;
927 	if (!(ari->linesdone>=ari->linestot+host->lineleniencelow && ari->linesdone<=ari->linestot+host->lineleniencehigh)){
928 		printf("unequal line count %lu should equal %lu",ari->linesdone,ari->linestot);
929 		if (host->lineleniencelow||host->lineleniencehigh){
930 			if (host->lineleniencelow==-host->lineleniencehigh)
931 				printf("(+/- %i)",host->lineleniencehigh);
932 			else
933 				printf("(%+i/%+i)",host->lineleniencelow,host->lineleniencehigh);
934 		}
935 		printf("\n");
936 		if (nconfig.unequal_line_error)
937 			throw TransportExFatal(Ex_INIT, "unequal line count and unequal_line_error is true");
938 		set_unequal_line_count_warn_status();
939 	}
940 }
941 
nntp_doarticle(c_nntp_part * part,arinfo * ari,quinfo * toti,char * fn,const nget_options & options)942 int c_prot_nntp::nntp_doarticle(c_nntp_part *part,arinfo*ari,quinfo*toti,char *fn, const nget_options &options){
943 	c_nntp_server_article *sa=NULL;
944 	t_nntp_server_articles_prioritized sap;
945 	t_nntp_server_articles_prioritized::iterator sapi;
946 	t_nntp_server_articles_prioritized::iterator sap_erase_i;
947 	nntp_doarticle_prioritize(part,sap,true);
948 	int redone=0, attempted=sap.size();
949 	while (!sap.empty() && redone<options.maxretry) {
950 		if (redone){
951 			printf("nntp_doarticle: trying again. %i\n",redone);
952 			if (options.retrydelay)
953 				sleep(options.retrydelay);
954 		}
955 		for (sapi = sap.begin(); sapi != sap.end();){
956 			sa=(*sapi).second.first;
957 			const c_server::ptr &s = (*sapi).second.second;
958 			assert(sa);
959 			ari->partnum=part->partnum;
960 			ari->anum=sa->articlenum;
961 			ari->bytestot=sa->bytes;
962 			ari->linestot=sa->lines;
963 			ari->linesdone=0;
964 			ari->bytesdone=0;
965 			PDEBUG(DEBUG_MED,"trying server %s(%lu) article %lu",s->alias.c_str(),sa->serverid,sa->articlenum);
966 			list<string> buf;//use a list of strings instead of char *.  Easier and it cleans up after itself too.
967 			try {
968 				ConnectionHolder holder(&sockpool, &connection, s);
969 				nntp_doopen();
970 				if (toti->doarticle_show_multi==SHOW_MULTI_SHORT)
971 					ari->server_name=connection->server->shortname.c_str();
972 				else if (toti->doarticle_show_multi==SHOW_MULTI_LONG)
973 					ari->server_name=connection->server->alias.c_str();
974 				nntp_dogroup(sa->group, false);
975 				chkreply_setok(stdputline(debug>=DEBUG_MED,"ARTICLE %lu",sa->articlenum));
976 				nntp_dogetarticle(ari,toti,buf);
977 				connection->server_ok=true;
978 			} catch (baseCommEx &e) {
979 				printCommEx(e, s, redone, options);
980 				if (e.isfatal()) {
981 					sap_erase_i = sapi;
982 					++sapi;
983 					sap.erase(sap_erase_i);
984 				}else{
985 					++sapi;
986 				}
987 				continue;
988 			}
989 			c_file_fd f(fn, O_CREAT|O_WRONLY|O_TRUNC, PRIVMODE);
990 			list<string>::iterator curb;
991 			try {
992 				for(curb = buf.begin();curb!=buf.end();++curb){
993 					f.putf("%s\n",(*curb).c_str());
994 				}
995 				f.close();
996 			}catch(FileEx &e){
997 				//if the drive is full or other error occurs, then the temp file will be cutoff and useless, so delete it.
998 				if (unlink(fn))
999 					perror("unlink:");
1000 				throw;
1001 			}
1002 			set_retrieve_warn_status(attempted - sap.size());
1003 			return 0; //article successfully retrieved, return.
1004 		}
1005 		redone++;
1006 	}
1007 	printf("couldn't get %s from anywhere\n",part->messageid.c_str());
1008 	set_retrieve_error_status();
1009 	return -1;
1010 }
1011 
print_nntp_file_info(c_nntp_file::ptr f,t_show_multiserver show_multi)1012 void print_nntp_file_info(c_nntp_file::ptr f, t_show_multiserver show_multi) {
1013 	char tconvbuf[TCONV_DEF_BUF_LEN];
1014 	c_nntp_part *p=(*f->parts.begin());
1015 	tconv(tconvbuf,TCONV_DEF_BUF_LEN,&p->date);
1016 	if (f->iscomplete())
1017 		printf("%i",f->have);
1018 	else
1019 		printf("%i/%i",f->have,f->req);
1020 	if (show_multi!=NO_SHOW_MULTI){
1021 		t_server_have_map have_map;
1022 		f->get_server_have_map(have_map);
1023 		if (show_multi==SHOW_MULTI_SHORT)
1024 			printf(" ");
1025 
1026 		for (t_server_have_map::iterator i=have_map.begin(); i!=have_map.end(); ++i){
1027 			for (t_server_list_range servers = nconfig.getservers(i->first); servers.first!=servers.second; ++servers.first) {
1028 				c_server::ptr s=servers.first->second;
1029 				if (show_multi==SHOW_MULTI_LONG){
1030 					printf(" %s", s->alias.c_str());
1031 					if (i->second<f->have)
1032 						printf(":%i", i->second);
1033 				}
1034 				else{
1035 					if (i->second<f->have){
1036 						for (string::size_type j=0; j<s->shortname.size(); j++)
1037 							printf("%c", toupper(s->shortname[j]));
1038 					}
1039 					else
1040 						printf("%s", s->shortname.c_str());
1041 				}
1042 			}
1043 		}
1044 	}
1045 	printf("\t%lil\t%s\t%s\t%s\t%s\n",f->lines(),tconvbuf,f->subject.c_str(),f->author.c_str(),p->messageid.c_str());
1046 }
1047 
nntp_retrieve(const vector<c_group_info::ptr> & rgroups,const t_nntp_getinfo_list & getinfos,const t_xpat_list & patinfos,const nget_options & options)1048 void c_prot_nntp::nntp_retrieve(const vector<c_group_info::ptr> &rgroups, const t_nntp_getinfo_list &getinfos, const t_xpat_list &patinfos, const nget_options &options) {
1049 	c_nntp_files_u filec;
1050 	ParHandler parhandler;
1051 	if (rgroups.size()!=1) {
1052 		cleanupcache();
1053 		group = NULL;
1054 		midinfo=new meta_mid_info(ngcachehome, rgroups);
1055 	} else {
1056 		if (rgroups.front() != group) {
1057 			cleanupcache();
1058 			group = rgroups.front();
1059 		}
1060 		if (!midinfo) {
1061 			midinfo=new meta_mid_info(ngcachehome, group);
1062 		}
1063 	}
1064 	gcache=new c_nntp_cache();
1065 
1066 	for (vector<c_group_info::ptr>::const_iterator gi=rgroups.begin(); gi!=rgroups.end(); gi++)
1067 		nntp_xgroup(*gi, patinfos, options);
1068 
1069 	gcache->getfiles(&filec, &parhandler, midinfo, getinfos);
1070 
1071 	gcache=NULL;
1072 
1073 	nntp_doretrieve(filec, parhandler, options);
1074 }
1075 
nntp_retrieve(const vector<c_group_info::ptr> & rgroups,const t_nntp_getinfo_list & getinfos,const nget_options & options)1076 void c_prot_nntp::nntp_retrieve(const vector<c_group_info::ptr> &rgroups, const t_nntp_getinfo_list &getinfos, const nget_options &options){
1077 	c_nntp_files_u filec;
1078 	ParHandler parhandler;
1079 	if (rgroups.size()!=1) {
1080 		cleanupcache();
1081 		group = NULL;
1082 		midinfo=new meta_mid_info(ngcachehome, rgroups);
1083 		nntp_cache_getfiles(&filec, &parhandler, &gcache_ismultiserver, ngcachehome, rgroups, midinfo, getinfos);
1084 	} else {
1085 		assert(rgroups.front());
1086 		if (gcache) {
1087 			assert(group == rgroups.front());
1088 			gcache->getfiles(&filec, &parhandler, midinfo, getinfos);
1089 			//attempt to free up some mem since all the data we need is now in filec, we don't need the whole cache.  Unfortunatly due to STL's memory allocators this doesn't really return the memory to the OS, but at least its available for any further STL allocations while retrieving.
1090 			gcache=NULL;
1091 		} else {
1092 			if (rgroups.front() != group) {
1093 				cleanupcache();
1094 				group = rgroups.front();
1095 			}
1096 			if (!midinfo) {
1097 				midinfo=new meta_mid_info(ngcachehome, rgroups);
1098 			}
1099 
1100 			nntp_cache_getfiles(&filec, &parhandler, &gcache_ismultiserver, ngcachehome, group, midinfo, getinfos);
1101 		}
1102 	}
1103 	nntp_doretrieve(filec, parhandler, options);
1104 }
1105 
1106 
nntp_doretrieve(c_nntp_files_u & filec,ParHandler & parhandler,const nget_options & options)1107 void c_prot_nntp::nntp_doretrieve(c_nntp_files_u &filec, ParHandler &parhandler, const nget_options &options) {
1108 	int optionflags = options.gflags;
1109 	if (!(optionflags&GETFILES_AUTOPAR_DISABLING_FLAGS)) {
1110 		parhandler.get_initial_pars(filec);
1111 	}
1112 
1113 	if (filec.files.empty())
1114 		return;
1115 
1116 	t_nntp_files_u::iterator curf;
1117 	//c_nntp_file *f;
1118 	c_nntp_file::ptr f;
1119 	c_nntp_file_retr::ptr fr;
1120 
1121 	if (optionflags & GETFILES_UNMARK) {
1122 		ulong nbytes=0;
1123 		unsigned int nfiles=0;
1124 		for(curf = filec.files.begin();curf!=filec.files.end();++curf){
1125 			fr=(*curf).second;
1126 			f=fr->file;
1127 			if (optionflags & GETFILES_TESTMODE) {
1128 				if (midinfo->check(f->bamid())){
1129 					print_nntp_file_info(f,options.test_multi);
1130 					nbytes+=f->bytes();
1131 					nfiles++;
1132 				}
1133 			} else
1134 				midinfo->remove(f->bamid());
1135 		}
1136 		if (optionflags & GETFILES_TESTMODE)
1137 			printf("Would unmark %lu bytes in %u files\n",nbytes,nfiles);
1138 	} else if (optionflags & GETFILES_TESTMODE){
1139 		for(curf = filec.files.begin();curf!=filec.files.end();++curf){
1140 			fr=(*curf).second;
1141 			print_nntp_file_info(fr->file,options.test_multi);
1142 		}
1143 		if (optionflags & GETFILES_MARK)
1144 			printf("Would mark ");
1145 		printf("%"PRIuFAST64" bytes in %lu files\n",filec.bytes,(ulong)filec.files.size());
1146 	} else if (optionflags & GETFILES_MARK) {
1147 		for(curf = filec.files.begin();curf!=filec.files.end();++curf){
1148 			fr=(*curf).second;
1149 			f=fr->file;
1150 			midinfo->insert(f);
1151 		}
1152 	} else {
1153 		quinfo qtotinfo;
1154 		arinfo ainfo;
1155 		time(&qtotinfo.starttime);
1156 		qtotinfo.filesdone=0;
1157 //		qtotinfo.linestot=filec.lines;
1158 		qtotinfo.filestot=filec.files.size();
1159 		qtotinfo.bytesleft=filec.bytes;
1160 		qtotinfo.doarticle_show_multi=gcache_ismultiserver?SHOW_MULTI_SHORT:NO_SHOW_MULTI;
1161 		c_nntp_part *p;
1162 //		s_part_u *bp;
1163 		c_nntp_file_parts::iterator curp;
1164 		char *fn;
1165 		if (!options.writelite.empty())
1166 			optionflags |= GETFILES_NODECODE;
1167 		curf=filec.files.end();
1168 		while (1){
1169 			if (curf!=filec.files.end()){
1170 //				delete (*lastf).second;//new cache implementation uses pointers to the same data
1171 				filec.files.erase(curf);
1172 				qtotinfo.filesdone++;
1173 				filec.bytes=qtotinfo.bytesleft;//update bytes in case we have an exception and need to restart.
1174 
1175 				if (!(optionflags&GETFILES_AUTOPAR_DISABLING_FLAGS)) {
1176 					//check if this was the last file to be downloaded to its path, and if so do autoparhandling
1177 					int path_files_left=0;
1178 					for (t_nntp_files_u::iterator dfi = filec.files.begin(); dfi!=filec.files.end(); ++dfi){
1179 						const c_nntp_file_retr::ptr &dfr = (*dfi).second;
1180 						if (dfr->path == fr->path)//fr will still be set to the just erased file_retr, here
1181 							path_files_left++;
1182 					}
1183 					if (path_files_left==0) {
1184 						long old_files_size = filec.files.size();
1185 						parhandler.maybe_get_pxxs(fr->path, filec);
1186 						//update status line information for any new pars that have been added
1187 						qtotinfo.bytesleft = filec.bytes;
1188 						qtotinfo.filestot += filec.files.size() - old_files_size;
1189 					}
1190 				}
1191 			}
1192 			if (filec.files.empty())
1193 				break;
1194 			curf = filec.files.begin();
1195 			fr=(*curf).second;
1196 			f=fr->file;
1197 			printf("Retrieving: ");
1198 			print_nntp_file_info(f, options.retr_show_multi);
1199 //			bp=f->parts.begin()->second;
1200 			int dlerr=0;
1201 			Decoder decoder;
1202 			for(curp = f->parts.begin();curp!=f->parts.end();++curp){
1203 				//asprintf(&fn,"%s/%s-%s-%li-%li-%li",nghome.c_str(),host.c_str(),group.c_str(),fgnum,part,num);
1204 				p=(*curp);
1205 				if (dlerr){
1206 					qtotinfo.bytesleft-=p->bytes();
1207 					continue;
1208 				}
1209 				{
1210 					const char *usepath;
1211 					if (!options.writelite.empty())
1212 						usepath="";
1213 					else usepath=fr->temppath.c_str();
1214 					if (optionflags & GETFILES_TEMPSHORTNAMES)
1215 						asprintf(&fn,"%s%lx.%03i",usepath,f->getfileid(),p->partnum);
1216 					else
1217 						asprintf(&fn,"%sngettemp-%lx.%03i",usepath,f->getfileid(),p->partnum);
1218 				}
1219 				if (!fexists(fn)){
1220 					ainfo.partreq = f->req;
1221 //					ainfo.anum=p->articlenum;//set in doarticle now.
1222 //					ainfo.linesdone=0;
1223 //					ainfo.bytesdone=0;
1224 //					ainfo.linestot=p->lines;
1225 //					ainfo.bytestot=p->bytes;
1226 					if (!options.writelite.empty()){
1227 						c_file_fd fw(options.writelite.c_str(), O_WRONLY|O_CREAT|O_APPEND, PRIVMODE);
1228 						nntp_dowritelite_article(fw,p,fn);
1229 						fw.close();
1230 						free(fn);
1231 						qtotinfo.bytesleft-=p->bytes();
1232 //						uustatus.derr=-1;//skip this file..
1233 						continue;
1234 					}
1235 					if ((optionflags & GETFILES_NOCONNECT) || nntp_doarticle(p,&ainfo,&qtotinfo,fn,options)){
1236 						free(fn);
1237 						fn=NULL;
1238 						if (!(optionflags & GETFILES_GETINCOMPLETE)) {
1239 							qtotinfo.bytesleft-=p->bytes();
1240 							dlerr=-1;//skip this file..
1241 							continue;
1242 						}
1243 					}
1244 				}else{
1245 //					qtotinfo.bytestot-=p->bytes;
1246 					if (quiet<2) printf("already have article %s (%s)\n",p->messageid.c_str(),fn);
1247 				}
1248 				qtotinfo.bytesleft-=p->bytes();
1249 				if (fn)
1250 					decoder.addpart(p->partnum,fn); //decoder will free fn when done.
1251 			}
1252 
1253 			if (dlerr)
1254 				printf("download error occured, keeping temp files.\n");
1255 			else if (optionflags&GETFILES_NODECODE) {
1256 				set_total_ok_status();
1257 				if (quiet<2)
1258 					printf("not decoding, keeping temp files.\n");
1259 			}
1260 			else {
1261 				dupe_file_checker flist;
1262 				if (!decoder.decode(options, fr, flist)) {
1263 					midinfo->insert(f);
1264 					if (!flist.empty()) {
1265 						//check all remaining files against what we just decoded, and remove any dupes.
1266 						t_nntp_files_u::iterator dfi = curf; ++dfi; //skip the current one
1267 						t_nntp_files_u::iterator del_fi;
1268 						c_nntp_file_retr::ptr dfr;
1269 						while(dfi!=filec.files.end()){
1270 							dfr = (*dfi).second;
1271 							//only check files that are being downloaded to the same path
1272 							if (dfr->dupecheck && dfr->path == fr->path) {
1273 								c_nntp_file::ptr df = dfr->file;
1274 								if (flist.checkhavefile(df->subject.c_str(), df->bamid(), df->bytes())) {
1275 									set_skipped_ok_status();
1276 									del_fi=dfi;
1277 									++dfi;
1278 									filec.files.erase(del_fi);
1279 									qtotinfo.filestot--;
1280 									qtotinfo.bytesleft-=df->bytes();
1281 									filec.bytes=qtotinfo.bytesleft;//update bytes in case we have an exception and need to restart.
1282 									continue;
1283 								}
1284 							}
1285 							++dfi;
1286 						}
1287 					}
1288 				}
1289 			}
1290 		}
1291 	}
1292 	//delete filec;filec=NULL;
1293 	cleanupcache();
1294 }
nntp_auth(void)1295 void c_prot_nntp::nntp_auth(void){
1296 	nntp_doauth(connection->server->user.c_str(),connection->server->pass.c_str());
1297 }
nntp_doauth(const char * user,const char * pass)1298 void c_prot_nntp::nntp_doauth(const char *user, const char *pass){
1299 	int i;
1300 
1301 	if(!user || !*user){
1302 		throw TransportExFatal(Ex_INIT,"nntp_doauth: no authorization info known");
1303 	}
1304 	putline(quiet<2,"AUTHINFO USER %s",user);
1305 	i=getreply(quiet<2);
1306 	if (i==350 || i==381){
1307 		if(!pass || !*pass){
1308 			throw TransportExFatal(Ex_INIT,"nntp_doauth: no password known");
1309 		}
1310 		if (quiet<2)
1311 			printf("%s << AUTHINFO PASS *\n", connection->server->shortname.c_str());
1312 		putline(0,"AUTHINFO PASS %s",pass);
1313 		i=getreply(quiet<2);
1314 	}
1315 	chkreply(i);
1316 }
1317 
nntp_open(c_server::ptr h)1318 void c_prot_nntp::nntp_open(c_server::ptr h){
1319 	if (h)
1320 		force_host=h;
1321 	else
1322 		force_host=NULL;
1323 }
1324 
nntp_doopen(void)1325 void c_prot_nntp::nntp_doopen(void){
1326 	assert(connection);
1327 	if (connection->freshconnect){
1328 		chkreply(getreply(quiet<2));
1329 		putline(debug>=DEBUG_MED,"MODE READER");
1330 		getline(debug>=DEBUG_MED);
1331 		connection->freshconnect=false;
1332 	}
1333 }
1334 
cleanupcache(void)1335 void c_prot_nntp::cleanupcache(void){
1336 //	if(gcache){gcache->dec_rcount();/*delete gcache;*/gcache=NULL;}
1337 	gcache=NULL;//ref counted.
1338 	if (midinfo){
1339 		meta_mid_info *mi = midinfo; //store midinfo in temp pointer and NULL out real pointer, to prevent a second deletion attempt if the destructor aborts and the atexit calls the cleanup again.
1340 		midinfo=NULL;
1341 		delete mi;
1342 	}
1343 }
cleanup(void)1344 void c_prot_nntp::cleanup(void){
1345 	cleanupcache();
1346 }
1347 
initready(void)1348 void c_prot_nntp::initready(void){
1349 //	midinfo=new c_mid_info((nghome + ".midinfo"));
1350 }
c_prot_nntp(void)1351 c_prot_nntp::c_prot_nntp(void){
1352 //	cbuf=new char[4096];
1353 //	cbuf_size=4096;
1354 	gcache=NULL;
1355 //	ch=-1;
1356 	connection=NULL;
1357 	midinfo=NULL;
1358 	force_host=NULL;
1359 }
~c_prot_nntp(void)1360 c_prot_nntp::~c_prot_nntp(void){
1361 //	printf("nntp destructing\n");
1362 //	if (midinfo)delete midinfo;
1363 	cleanup();
1364 }
1365