Server.C 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. //
  2. // Server.C -- News server class
  3. //
  4. // Copyright 2003-2013 Michael Sweet
  5. // Copyright 2002-2013 Greg Ercolano
  6. //
  7. // This program is free software; you can redistribute it and/or modify
  8. // it under the terms of the GNU General Public Licensse as published by
  9. // the Free Software Foundation; either version 2 of the License, or
  10. // (at your option) any later version.
  11. //
  12. // This program is distributed in the hope that it will be useful,
  13. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. // GNU General Public License for more details.
  16. //
  17. // You should have received a copy of the GNU General Public License
  18. // along with this program; if not, write to the Free Software
  19. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. //
  21. // 80 //////////////////////////////////////////////////////////////////////////
  22. #include "Server.H"
  23. #include <dirent.h>
  24. // Convenience macros...
  25. #define ISIT(x) if (!strcasecmp(cmd, x))
  26. #define ISHEAD(a) (strncasecmp(head, (a), strlen(a))==0)
  27. // REPLACE ALL INSTANCES OF 'FROM' -> 'TO' IN STRING 'S'
  28. static void ReplaceString(char *s, char from, char to)
  29. {
  30. for ( ; *s; s++ )
  31. if ( *s == from ) *s = to;
  32. }
  33. // SENDS CRLF TERMINATED MESSAGE TO REMOTE
  34. int Server::Send(const char *msg)
  35. {
  36. string out = msg; out.append("\r\n");
  37. write(msgsock, out.c_str(), out.length());
  38. G_conf.LogMessage(L_DEBUG, "SEND: %.4000s", msg);
  39. return(0);
  40. }
  41. // SEE IF GROUPNAME IS ON THIS SERVER
  42. int Server::ValidGroup(const char *groupname)
  43. {
  44. // DISALLOW UNIX PATHNAME LOOPHOLES
  45. if ( strstr(groupname, "..") )
  46. { errmsg = "illegal groupname"; return(-1); }
  47. // CHECK FOR INVALID CHARS IN GROUPNAME
  48. for ( int t=0; groupname[t]; t++ )
  49. if ( ( groupname[t] >= 'a' && groupname[t] <= 'z' ) || // alphalower
  50. ( groupname[t] >= 'A' && groupname[t] <= 'Z' ) || // alphaupper
  51. ( groupname[t] >= '0' && groupname[t] <= '9' ) || // numeric
  52. groupname[t] == '.' ) // dot
  53. { continue; }
  54. else
  55. { errmsg = "illegal chars in groupname"; return(-1); }
  56. string dirname;
  57. dirname = G_conf.SpoolDir();
  58. dirname.append("/");
  59. dirname.append(groupname);
  60. struct stat sbuf;
  61. if ( stat(dirname.c_str(), &sbuf) == -1 )
  62. { errmsg = "group does not exist"; return(-1); }
  63. return(0);
  64. }
  65. // FIND GROUP, UPDATE START/END/TOTAL INFO
  66. int Server::NewGroup(const char *the_group)
  67. {
  68. if ( ValidGroup(the_group) < 0 )
  69. { return(-1); }
  70. if ( group.Load(the_group) < 0 )
  71. { errmsg = group.Errmsg(); return(-1); }
  72. return(0);
  73. }
  74. // BREAK A LONG LINE INTO SMALLER PIECES
  75. // Useful for breaking eg. a big Bcc: list into separate lines.
  76. //
  77. void BreakLineToFP(FILE *fp,
  78. const char *prefix, // prefix, eg. "Bcc: "
  79. const char *line, // long line, eg. "a,b,c,d"
  80. const char *trail, // trailing chars, eg. "\n"
  81. const char *brk) // list of chars to break on
  82. // eg. ","
  83. {
  84. string addr;
  85. const char *ss = line;
  86. for (; 1; ++ss )
  87. {
  88. // break character?
  89. if ( *ss == 0 || strchr(brk, *ss) )
  90. {
  91. if ( addr != "" )
  92. { fprintf(fp, "%s%s%s", prefix, addr.c_str(), trail); }
  93. addr = "";
  94. if ( *ss == 0 ) return;
  95. }
  96. else
  97. { addr += *ss; }
  98. }
  99. }
  100. void AllGroups(vector<string>& groupnames, const char *subdir)
  101. {
  102. DIR *dir;
  103. struct dirent *dent;
  104. struct stat fileinfo;
  105. string dirname, filename, newsubdir;
  106. char groupname[LINE_LEN];
  107. dirname = G_conf.SpoolDir();
  108. if (subdir)
  109. dirname = dirname + "/" + subdir;
  110. if ((dir = opendir(dirname.c_str())) != NULL)
  111. {
  112. while ((dent = readdir(dir)) != NULL)
  113. {
  114. // Skip dot files...
  115. if (dent->d_name[0] == '.')
  116. continue;
  117. // See if the file is a directory...
  118. filename = dirname + "/" + dent->d_name;
  119. if (stat(filename.c_str(), &fileinfo)) continue;
  120. if (!S_ISDIR(fileinfo.st_mode)) continue; // not a dir, skip
  121. // It is a directory, see if it is a group...
  122. filename += "/.config";
  123. if (!stat(filename.c_str(), &fileinfo))
  124. {
  125. // Yes, add the group...
  126. if (subdir)
  127. {
  128. // Use subdirectory for group name, map '/' -> '.'
  129. snprintf(groupname, sizeof(groupname), "%s.%s",
  130. subdir, dent->d_name);
  131. ReplaceString(groupname, '/', '.');
  132. }
  133. else
  134. {
  135. // TODO: add strlcpy() and emulation as needed...
  136. strncpy(groupname, dent->d_name, sizeof(groupname) - 1);
  137. groupname[sizeof(groupname) - 1] = '\0';
  138. }
  139. groupnames.push_back(groupname);
  140. }
  141. // Now recurse into the subdirectory...
  142. if (subdir)
  143. newsubdir = string(subdir) + "/" + dent->d_name;
  144. else
  145. newsubdir = dent->d_name;
  146. if ( G_conf.NoRecurseMsgDir() )
  147. {
  148. // if ( !isgroup ) // optimization: avoid recursing into potentially huge group dir
  149. AllGroups(groupnames, newsubdir.c_str());
  150. }
  151. else
  152. { AllGroups(groupnames, newsubdir.c_str()); }
  153. }
  154. closedir(dir);
  155. }
  156. }
  157. // HANDLE SIGALRM
  158. // alarm() used to timeout inactive child servers.
  159. //
  160. static void sigalrm_handler(int)
  161. {
  162. // cerr << "ALARM -- connection timed out. (child exiting)" << endl;
  163. exit(1);
  164. }
  165. // SEE IF AN OPERATION IS ALLOWED TO BE DONE
  166. // op can be:
  167. // AUTH_READ -- allowed to read?
  168. // AUTH_POST -- allowed to post?
  169. // Returns: 1 if ok, 0 if not sends a proper error
  170. //
  171. int Server::IsAllowed(int op)
  172. {
  173. if ( ! G_conf.IsAuthAllowed(op) )
  174. {
  175. Send("480 Authentication required"); // RFC 2980 3.1.1
  176. return(0);
  177. }
  178. return(1);
  179. }
  180. // HANDLE COMMANDS FROM REMOTE
  181. int Server::CommandLoop(const char *overview[])
  182. {
  183. const char *remoteip_str = GetRemoteIPStr();
  184. char s[LINE_LEN+1],
  185. cmd[LINE_LEN+1],
  186. arg1[LINE_LEN+1],
  187. arg2[LINE_LEN+1],
  188. reply[LINE_LEN];
  189. int auth_simple = 0;
  190. string auth_user_save;
  191. string auth_pass_save;
  192. Send("200 newsd news server ready - posting ok");
  193. // HANDLE ALARM -- timeout the connection if no data transacted
  194. signal(SIGALRM, sigalrm_handler);
  195. while ( 1 )
  196. {
  197. int total = 0;
  198. char *ss = s;
  199. int found = 0;
  200. // RESET TIMEOUT ALARM
  201. if ( G_conf.Timeout() )
  202. { alarm(G_conf.Timeout()); }
  203. // READ A CRLF-TERMINATED (OR LF-TERMINATED) LINE
  204. for ( ; read(msgsock, ss, 1) == 1; ss++, total++ )
  205. {
  206. if ( *ss == '\n' || total >= (LINE_LEN-2) )
  207. { *ss = 0; found = 1; break; }
  208. if ( *ss == '\r' )
  209. {
  210. read(msgsock, ss, 1); // skip \n
  211. *ss = 0;
  212. found = 1;
  213. break;
  214. }
  215. }
  216. if ( ! found )
  217. { break; }
  218. G_conf.LogMessage(L_INFO, "GOT: %s", s);
  219. arg1[0] = arg2[0] = 0;
  220. if ( sscanf(s, "%s%s%s", cmd, arg1, arg2) < 1 )
  221. { continue; }
  222. // AUTHINFO SIMPLE -- username/password
  223. // This is a continuation of an 'AUTHINFO SIMPLE' command
  224. // where we parse the user/password on an empty line.
  225. //
  226. if ( auth_simple )
  227. {
  228. auth_simple = 0;
  229. // EXPECT 'user' AND 'pass'
  230. if ( !cmd[0] || !arg1[0] )
  231. { Send("501 Bad or unknown argument"); continue; }
  232. auth_user_save = cmd;
  233. auth_pass_save = arg1;
  234. if ( G_conf.AuthLogin(auth_user_save, auth_pass_save) < 0 )
  235. Send("452 Authorization rejected"); // RFC 2980 3.1.2.1
  236. else
  237. Send("250 Authenticated OK"); // RFC 2980 3.1.2.1
  238. auth_user_save = "";
  239. auth_pass_save = "";
  240. continue;
  241. }
  242. ISIT("AUTHINFO") // AUTHENTICATION -- RFC 2980 3.1
  243. {
  244. // "AUTHINFO SIMPLE"
  245. if ( strcasecmp(arg1, "SIMPLE") == 0 ) // RFC 2980 3.1.2
  246. {
  247. if ( ! G_conf.IsAuthNeeded() )
  248. { Send("281 No authentication needed"); continue; }
  249. Send("350 Go ahead with username and password"); // 3.1.2.1
  250. auth_simple = 1;
  251. continue;
  252. }
  253. else if ( strcasecmp(arg1, "USER") == 0 ) // RFC 2980 3.1.1
  254. {
  255. if ( ! G_conf.IsAuthNeeded() )
  256. { Send("281 No authentication needed"); continue; }
  257. if ( ! arg2[0] )
  258. { Send("501 Bad or unknown argument"); continue; }
  259. auth_user_save = arg2;
  260. Send("381 Now supply your password"); // 3.1.1.1
  261. continue;
  262. }
  263. else if ( strcasecmp(arg1, "PASS") == 0 ) // RFC 2980 3.1.1
  264. {
  265. if ( ! G_conf.IsAuthNeeded() )
  266. { Send("281 No authentication needed"); continue; }
  267. if ( ! arg2[0] )
  268. { Send("501 Bad or unknown argument"); continue; }
  269. if ( auth_user_save == "" )
  270. { Send("482 User must be specified first"); continue; }
  271. auth_pass_save = arg2;
  272. if ( G_conf.AuthLogin(auth_user_save, auth_pass_save) < 0 )
  273. Send("482 Authentication failed"); // 3.1.1.1
  274. else
  275. Send("281 Authenticated OK"); // 3.1.1.1
  276. auth_user_save = "";
  277. auth_pass_save = "";
  278. continue;
  279. }
  280. else if ( strcasecmp(arg1, "GENERIC") == 0 ) // RFC 2980 3.1.3
  281. {
  282. Send("501 'AUTHINFO GENERIC' not supported"); // 3.1.3.1
  283. continue;
  284. }
  285. Send("501 Bad or unknown argument");
  286. continue;
  287. }
  288. ISIT("CHECK") // TRANSPORT EXTENSION -- RFC 2980
  289. {
  290. Send("400 not accepting articles - we are not a news feed");
  291. continue;
  292. }
  293. ISIT("TAKETHIS") // TRANSPORT EXTENSION -- RFC 2980
  294. {
  295. Send("400 not accepting articles - we are not a news feed");
  296. continue;
  297. }
  298. ISIT("MODE") // TRANSPORT EXTENSION -- RFC 2980
  299. {
  300. if ( strcasecmp(arg1, "stream") == 0 )
  301. {
  302. Send("500 Streaming not implemented on this server");
  303. continue;
  304. }
  305. // NEWS READER EXTENSION -- RFC 2980
  306. if ( strcasecmp(arg1, "reader") == 0 )
  307. {
  308. Send("200 erco's newsd server ready (posting ok)");
  309. continue;
  310. }
  311. Send("500 What?"); // inn/nnrp/commands.c:CMDmode() - erco
  312. continue;
  313. }
  314. ISIT("LIST")
  315. {
  316. if ( ! IsAllowed(AUTH_READ) ) continue;
  317. if ( strcasecmp(arg1, "EXTENSIONS") == 0 ) // INTERNET DRAFT (S.Barber)
  318. {
  319. Send("202 Extensions supported:\r\n"
  320. "LISTGROUP\r\n"
  321. "MODE\r\n"
  322. "XREPLIC\r\n"
  323. "XOVER\r\n"
  324. "DATE\r\n"
  325. ".");
  326. continue;
  327. }
  328. if ( strcasecmp(arg1, "ACTIVE") == 0 || // NEWS READER EXTENSION -- RFC 2980
  329. arg1[0] == 0 ) // RFC 977
  330. {
  331. // "LIST ACTIVE rush.*"
  332. if ( strcasecmp(cmd, "LIST") == 0 &&
  333. strcasecmp(arg1, "ACTIVE") == 0 &&
  334. arg2[0] != 0 )
  335. {
  336. Send("501 LIST ACTIVE <wildmat>: wildmats not supported"); // TBD
  337. continue;
  338. }
  339. // "LIST" or "LIST ACTIVE"
  340. Send("215 list of newsgroups follows");
  341. vector<string> groupnames;
  342. AllGroups(groupnames, NULL);
  343. for ( unsigned t=0; t<groupnames.size(); t++ )
  344. {
  345. Group tgroup;
  346. if ( tgroup.Load(groupnames[t].c_str()) < 0 )
  347. { continue; }
  348. snprintf(reply, sizeof(reply), "%s %d %d %c",
  349. (const char*)tgroup.Name(),
  350. (int)tgroup.Total(),
  351. (int)tgroup.Start(),
  352. (char)(tgroup.PostOK() ? 'y' : 'n'));
  353. Send(reply);
  354. }
  355. Send(".");
  356. continue;
  357. }
  358. if ( strcasecmp(arg1, "ACTIVE.TIMES")==0 ) // NEWS READER EXTENSION -- RFC 2980
  359. {
  360. Send("215 information follows");
  361. vector<string> groupnames;
  362. AllGroups(groupnames, NULL);
  363. for ( unsigned t=0; t<groupnames.size(); t++ )
  364. {
  365. Group tgroup;
  366. if ( tgroup.Load(groupnames[t].c_str()) < 0 )
  367. { continue; }
  368. snprintf(reply, sizeof(reply), "%s %ld %s",
  369. (const char*)tgroup.Name(),
  370. (long)tgroup.Ctime(),
  371. (const char*)tgroup.Creator());
  372. Send(reply);
  373. }
  374. Send(".");
  375. continue;
  376. }
  377. if ( strcasecmp(arg1, "DISTRIBUTIONS")==0 ) // NEWS READER EXTENSION -- RFC 2980
  378. {
  379. // TODO
  380. Send("503 Not implemented on this server");
  381. continue;
  382. }
  383. if ( strcasecmp(arg1, "DISTRIB.PATS")==0 ) // NEWS READER EXTENSION -- RFC 2980
  384. {
  385. // TODO
  386. Send("503 Not implemented on this server");
  387. continue;
  388. }
  389. if ( strcasecmp(arg1, "NEWSGROUPS")==0 ) // NEWS READER EXTENSION -- RFC 2980
  390. {
  391. Send("215 information follows");
  392. vector<string> groupnames;
  393. AllGroups(groupnames, NULL);
  394. for ( unsigned t=0; t<groupnames.size(); t++ )
  395. {
  396. Group tgroup;
  397. if ( tgroup.Load(groupnames[t].c_str()) < 0 )
  398. { continue; }
  399. snprintf(reply, sizeof(reply), "%s %s",
  400. (const char*)tgroup.Name(),
  401. (const char*)tgroup.Description());
  402. Send(reply);
  403. }
  404. Send(".");
  405. continue;
  406. }
  407. if ( strcasecmp(arg1, "OVERVIEW.FMT")==0 ) // NEWS READER EXTENSION -- RFC 2980
  408. {
  409. Send("215 information follows");
  410. for ( int t=0; overview[t]; t++ )
  411. { Send(overview[t]); }
  412. Send(".");
  413. continue;
  414. }
  415. if ( strcasecmp(arg1, "SUBSCRIPTIONS")==0 ) // NEWS READER EXTENSION -- RFC 2980
  416. {
  417. Send("215 information follows");
  418. Send("rush.general"); // HACK: TBD
  419. Send(".");
  420. continue;
  421. }
  422. Send("501 Syntax error");
  423. continue;
  424. }
  425. ISIT("LISTGROUP") // TRANSPORT EXTENSION -- RFC 2980
  426. {
  427. if ( ! IsAllowed(AUTH_READ) ) continue;
  428. Group restore = group;
  429. if ( arg1[0] )
  430. {
  431. if ( group.Load(arg1) < 0 )
  432. {
  433. snprintf(reply, sizeof(reply), "411 No such newsgroup: %s",
  434. (const char*)group.Errmsg());
  435. Send(reply);
  436. group = restore;
  437. continue;
  438. }
  439. }
  440. if ( arg1[0] == 0 && ! group.IsValid() )
  441. {
  442. Send("412 Not currently in newsgroup");
  443. continue;
  444. }
  445. // RFC 2980: SET CURRENT ARTICLE TO FIRST
  446. article.Load(group.Start());
  447. Send("211 list of article numbers follow");
  448. for ( unsigned long t = group.Start(); t <= group.End(); t++ )
  449. { snprintf(reply, sizeof(reply), "%lu", t); Send(reply); }
  450. Send(".");
  451. continue;
  452. }
  453. ISIT("XREPLIC") // TRANSPORT EXTENSION -- RFC 2980
  454. {
  455. Send("437 'xreplic' not implemented on this server");
  456. continue;
  457. }
  458. ISIT("XOVER") // NEWS READER EXTENSION -- RFC 2980
  459. {
  460. // From RFC2970 for XOVER:
  461. //
  462. // Each line of output will be formatted with the article number,
  463. // followed by each of the headers in the overview database or the
  464. // article itself (when the data is not available in the overview
  465. // database) for that article separated by a tab character. The
  466. // sequence of fields must be in this order: subject, author, date,
  467. // message-id, references, byte count, and line count. Other optional
  468. // fields may follow line count. Other optional fields may follow line
  469. // count. These fields are specified by examining the response to the
  470. // LIST OVERVIEW.FMT command. Where no data exists, a null field must
  471. // be provided (i.e. the output will have two tab characters adjacent to
  472. // each other). Servers should not output fields for articles that have
  473. // been removed since the XOVER database was created.
  474. //
  475. if ( ! IsAllowed(AUTH_READ) ) continue;
  476. if ( ! group.IsValid() )
  477. {
  478. Send("412 Not in a newsgroup");
  479. continue;
  480. }
  481. unsigned long sarticle = group.Start(),
  482. earticle = group.End();
  483. // HANDLE OPTIONAL RANGE
  484. if ( arg1[0] )
  485. {
  486. if ( sscanf(arg1, "%lu-%lu", &sarticle, &earticle) == 2 )
  487. { }
  488. else if ( sscanf(arg1, "%lu-", &sarticle) == 1 )
  489. { earticle = group.End(); }
  490. else
  491. { earticle = sarticle; }
  492. }
  493. // SANITIZE ARTICLE NUMBERS
  494. if ( sarticle < group.Start() ) { sarticle = group.Start(); }
  495. if ( sarticle > group.End() ) { sarticle = group.End(); }
  496. if ( earticle < group.Start() ) { earticle = group.Start(); }
  497. if ( earticle > group.End() ) { earticle = group.End(); }
  498. if ( sarticle > earticle ) { sarticle = earticle; }
  499. Send("224 overview follows");
  500. for ( unsigned long t=sarticle; t<=earticle; t++ )
  501. {
  502. // LOAD EACH ARTICLE
  503. Article a;
  504. if ( a.Load(group.Name(), t) < 0 )
  505. {
  506. // cerr << " DEBUG: ERROR: " << a.Errmsg() << endl;
  507. continue;
  508. }
  509. string replystr = a.Overview(overview);
  510. Send(replystr.c_str());
  511. }
  512. Send(".");
  513. continue;
  514. }
  515. ISIT("GROUP") // RFC 977
  516. {
  517. if ( ! IsAllowed(AUTH_READ) ) continue;
  518. if ( arg1[0] == 0 )
  519. {
  520. Send("501 syntax error; expected 'GROUP <group-name>'");
  521. continue;
  522. }
  523. Group restore = group;
  524. if ( group.Load(arg1) < 0 )
  525. {
  526. snprintf(reply, sizeof(reply), "411 No such newsgroup: %s",
  527. (const char*)group.Errmsg());
  528. Send(reply);
  529. group = restore;
  530. continue;
  531. }
  532. // UPDATE CURRENT ARTICLE
  533. article.Load(group.Name(), group.Start());
  534. // 211 n f l s group selected
  535. // (n = estimated number of articles in group,
  536. // f = first article number in the group,
  537. // l = last article number in the group,
  538. // s = name of the group.)
  539. //
  540. snprintf(reply, sizeof(reply), "211 %lu %lu %lu %s group selected",
  541. (unsigned long)group.Total(),
  542. (unsigned long)group.Start(),
  543. (unsigned long)group.End(),
  544. (const char*)group.Name());
  545. Send(reply);
  546. continue;
  547. }
  548. ISIT("HELP") // RFC 977
  549. {
  550. Send("100 help text follows");
  551. Send("CHECK\r\n"
  552. "TAKETHIS\r\n"
  553. "MODE [stream|reader]\r\n"
  554. "LIST [active|active.times|distributions|distrib.pats|"
  555. "newsgroups|overview.fmt|subscriptions]\r\n"
  556. "LISTGROUP [newsgroup]\r\n"
  557. "XREPLIC\r\n"
  558. "XOVER [msg#|msg#-|msg#-msg#]\r\n"
  559. "GROUP newsgroup\r\n"
  560. "HELP\r\n"
  561. "NEWGROUPS [YY]yymmdd hhmmss [GMT|UTC] [distributions]\r\n"
  562. "NEWNEWS\r\n"
  563. "NEXT\r\n"
  564. "HEAD [msg#|<msgid>]\r\n"
  565. "BODY [msg#|<msgid>]\r\n"
  566. "ARTICLE [msg#|<msgid>]\r\n"
  567. "AUTHINFO [user|pass] <value>\r\n"
  568. "AUTHINFO simple\r\n"
  569. "STAT [msg#|<msgid>]\r\n"
  570. "POST\r\n"
  571. "DATE\r\n"
  572. "QUIT\r\n"
  573. ".");
  574. continue;
  575. }
  576. ISIT("NEWGROUPS") // RFC 977
  577. {
  578. if ( ! IsAllowed(AUTH_READ) ) continue;
  579. // NEWGROUPS <YYMMDD> <HHMMSS> [GMT] [<distributions>]
  580. if ( strlen(arg1) != 6 || strlen(arg2) != 6 )
  581. {
  582. Send("501 Bad or missing date/time arguments");
  583. continue;
  584. }
  585. int year, mon, day, hour, min, sec;
  586. if ( sscanf(arg1, "%2d%2d%2d", &year, &mon, &day) != 3 ||
  587. sscanf(arg2, "%2d%2d%2d", &hour, &min, &sec) != 3 )
  588. {
  589. Send("501 Bad date/time argument");
  590. continue;
  591. }
  592. // TBD
  593. // 1) TRANSLATE YY -> YYYY
  594. // 2) TRANSLATE TIMES INTO A time() VALUE
  595. // 3) COMPARE TIME TO GROUP'S CTIME
  596. //
  597. vector<string> groupnames;
  598. AllGroups(groupnames, NULL);
  599. Send("231 list of new newsgroups follows");
  600. for ( unsigned t=0; t<groupnames.size(); t++ )
  601. {
  602. Group tgroup;
  603. if ( tgroup.Load(groupnames[t].c_str()) < 0 )
  604. { continue; }
  605. Send(tgroup.Name());
  606. }
  607. Send("."); // for now, nothing matches
  608. continue;
  609. /**** TBD
  610. if ( tgroup.Ctime() > checktime )
  611. { Send(tgroup.Name()); }
  612. }
  613. Send(".");
  614. continue;
  615. ********/
  616. }
  617. ISIT("NEWNEWS") // RFC 977
  618. {
  619. Send("501 Command not implemented on server"); // TBD
  620. continue;
  621. }
  622. ISIT("NEXT") // RFC 977
  623. {
  624. if ( ! IsAllowed(AUTH_READ) ) continue;
  625. Article restore = article;
  626. if ( ! group.IsValid() )
  627. { Send("412 no newsgroup selected"); continue; }
  628. if ( ! article.IsValid() )
  629. { Send("420 no article has been selected"); continue; }
  630. unsigned long next = article.Number() + 1;
  631. if ( next < group.Start() || next > group.End() )
  632. { Send("421 no next article in this group"); continue; }
  633. if ( article.Load(group.Name(), next) < 0 )
  634. {
  635. snprintf(reply, sizeof(reply),
  636. "421 error retrieving article %lu: %s",
  637. (unsigned long)next,
  638. (const char*)article.Errmsg());
  639. Send(reply);
  640. article = restore;
  641. continue;
  642. }
  643. snprintf(reply, sizeof(reply),
  644. "223 %lu %s article retrieved - request text separately",
  645. (unsigned long)next,
  646. (const char*)article.MessageID());
  647. Send(reply);
  648. continue;
  649. }
  650. if ( strcasecmp(cmd, "HEAD") == 0 || // RFC 977
  651. strcasecmp(cmd, "BODY") == 0 || // RFC 977
  652. strcasecmp(cmd, "ARTICLE") == 0 || // RFC 977
  653. strcasecmp(cmd, "STAT") == 0 ) // RFC 977
  654. {
  655. if ( ! IsAllowed(AUTH_READ) ) continue;
  656. Article restore = article;
  657. unsigned long the_article;
  658. char restoreflag = 0;
  659. if ( ! group.IsValid() )
  660. { Send("412 Not currently in newsgroup"); continue; }
  661. if ( arg1[0] == '<' ) // "ARTICLE <252-rush.general@news.3dsite.com>"
  662. {
  663. if ( group.FindArticleByMessageID(arg1, the_article) < 0 )
  664. {
  665. Send("430 no such article found");
  666. continue;
  667. }
  668. restoreflag = 1; // RFC 977: do not affect current article
  669. }
  670. else if ( isdigit(arg1[0]) ) // "HEAD 12"
  671. {
  672. if ( sscanf(arg1, "%lu", &the_article) != 1 )
  673. { Send("501 bad article number"); continue; }
  674. restoreflag = 0; // RFC 977: affect current article if valid
  675. }
  676. else if ( arg1[0] == 0 ) // "HEAD"
  677. {
  678. the_article = article.Number();
  679. restoreflag = 1;
  680. }
  681. else // all else is junk
  682. { Send("501 bad argument"); continue; }
  683. // Range check
  684. if ( the_article < group.Start() || the_article > group.End() )
  685. {
  686. snprintf(reply, sizeof(reply),
  687. "423 no such article in group (range %lu-%lu)",
  688. (unsigned long)group.Start(),
  689. (unsigned long)group.End());
  690. Send(reply);
  691. continue;
  692. }
  693. if ( article.Load(group.Name(), the_article) < 0 )
  694. {
  695. snprintf(reply, sizeof(reply), "430 no such article: %s",
  696. (const char*)article.Errmsg());
  697. Send(reply);
  698. continue;
  699. }
  700. // HANDLE VARIATIONS OF COMMAND
  701. if ( strcasecmp(cmd, "ARTICLE") == 0 )
  702. {
  703. snprintf(reply, sizeof(reply),
  704. "220 %lu %s article retrieved - head and body follow",
  705. (unsigned long)the_article,
  706. (const char*)article.MessageID());
  707. Send(reply);
  708. article.SendArticle(msgsock);
  709. Send(".");
  710. }
  711. else if ( strcasecmp(cmd, "HEAD") == 0 )
  712. {
  713. snprintf(reply, sizeof(reply),
  714. "221 %lu %s article retrieved - head follows",
  715. (unsigned long)the_article,
  716. (const char*)article.MessageID());
  717. Send(reply);
  718. article.SendHead(msgsock);
  719. Send(".");
  720. }
  721. else if ( strcasecmp(cmd, "BODY") == 0 )
  722. {
  723. snprintf(reply, sizeof(reply),
  724. "222 %lu %s article retrieved - body follows",
  725. (unsigned long)the_article,
  726. (const char*)article.MessageID());
  727. Send(reply);
  728. article.SendBody(msgsock);
  729. Send(".");
  730. }
  731. else if ( strcasecmp(cmd, "STAT") == 0 )
  732. {
  733. snprintf(reply, sizeof(reply),
  734. "223 %lu %s article retrieved - request text separately",
  735. (unsigned long)the_article,
  736. (const char*)article.MessageID());
  737. Send(reply);
  738. }
  739. if ( restoreflag )
  740. { article = restore; }
  741. continue;
  742. }
  743. ISIT("POST") // RFC 977
  744. {
  745. if ( ! IsAllowed(AUTH_POST) ) continue;
  746. Send("340 Continue posting; Period on a line by itself to end");
  747. // KEEP TRACK OF POSTING LENGTH
  748. // If too long, stop loading posting.
  749. //
  750. int linechars = 0,
  751. linecount = 0,
  752. toolong = 0;
  753. // COLLECT POSTING FROM CLIENT
  754. int eom = 0;
  755. char c;
  756. string msg;
  757. while (read(msgsock, &c, 1) == 1 )
  758. {
  759. // KEEP TRACK OF #LINES
  760. // Lines longer than 80 chars count as multiple lines.
  761. // If posting too long, stop accumulating message in ram,
  762. // but keep reading until they've sent the terminating "."
  763. //
  764. ++linechars;
  765. if ( linechars > 80 || c == '\n' )
  766. { linechars = 0; linecount++; }
  767. if ( group.PostLimit() > 0 && linecount > group.PostLimit() )
  768. { toolong = 1; continue; }
  769. msg += c;
  770. // PARSE FOR END OF MESSAGE
  771. // Cute little state machine to maintain end of message
  772. // parse over buffer boundaries. The states:
  773. //
  774. // 0 - not yet at eom
  775. // 1 - \n before the dot
  776. // 2 - the dot
  777. // 3 - \r (or \n) after the dot
  778. //
  779. if (c == '\n' || c == '\r')
  780. {
  781. if (eom == 0)
  782. eom = 1;
  783. else if (eom == 2)
  784. {
  785. eom = 3;
  786. break;
  787. }
  788. }
  789. else if ( eom == 1 && ( c == '.' ) )
  790. eom = 2;
  791. else if ( eom != 0 )
  792. eom = 0;
  793. }
  794. // POSTING TOO LONG? FAIL
  795. if ( toolong )
  796. {
  797. snprintf(reply, sizeof(reply),
  798. "411 Not Posted: article exceeds sanity line limit of %d.",
  799. (int)group.PostLimit());
  800. Send(reply);
  801. continue;
  802. }
  803. // PARSE ARTICLE -- SEPARATE HEADER AND BODY
  804. vector<string> header;
  805. vector<string> body;
  806. if ( group.ParseArticle(msg, header, body) < 0 )
  807. {
  808. snprintf(reply, sizeof(reply), "441 %s",
  809. (const char*)group.Errmsg());
  810. Send(reply);
  811. continue;
  812. }
  813. // UPDATE 'Path:'
  814. group.UpdatePath(header);
  815. // POST ARTICLE
  816. // Don't affect 'current group' or 'current article'.
  817. //
  818. Group tgroup;
  819. if ( tgroup.Post(overview, header, body, remoteip_str) < 0 )
  820. {
  821. snprintf(reply, sizeof(reply), "441 %s",
  822. (const char*)tgroup.Errmsg());
  823. Send(reply);
  824. continue;
  825. }
  826. Send("240 Article posted successfully.");
  827. // CC MESSAGE TO MAIL ADDRESS?
  828. if ( tgroup.IsCCPost() )
  829. {
  830. string from = "Anonymous",
  831. subject = "-";
  832. // PRESERVE THESE FIELDS FROM NNTP POSTING -> SMTP
  833. // From: -- must
  834. // Subject: -- must
  835. // Xref: -- ?
  836. // Path: -- ?
  837. // References: -- needed to preserve threading
  838. // Message-ID: -- needed to preserve threading
  839. // Content-Type: -- mime related
  840. // MIME-Version: -- mime related
  841. //
  842. int pflag = 0;
  843. string preserve;
  844. for ( unsigned t=0; t<header.size(); t++ )
  845. {
  846. const char *head = header[t].c_str();
  847. // CONTINUATION OF HEADER LINE?
  848. if ( head[0] == ' ' || head[0] == 9 )
  849. {
  850. // CONTINUATION OF PREVIOUS PRESERVED HEADER LINE?
  851. if ( pflag )
  852. { preserve += header[t]; preserve += "\n"; }
  853. continue;
  854. }
  855. // ZERO OUT PRESERVE -- NO MORE CONTINUATIONS
  856. pflag = 0;
  857. // CHECK FOR PRESERVE FIELDS
  858. if ( ISHEAD("From: ") ||
  859. ISHEAD("Subject: ") ||
  860. ISHEAD("References: ") ||
  861. ISHEAD("Xref: ") ||
  862. ISHEAD("Path: ") ||
  863. ISHEAD("Content-Type: ") ||
  864. ISHEAD("MIME-Version: ") ||
  865. ISHEAD("Message-ID: ") )
  866. {
  867. pflag = 1;
  868. preserve += header[t];
  869. preserve += "\n";
  870. }
  871. }
  872. FILE *fp = popen(G_conf.SendMail(), "w");
  873. if ( fp == NULL )
  874. {
  875. G_conf.LogMessage(L_ERROR, "ccpost: popen('%s','w') failed - %s",
  876. G_conf.SendMail(),
  877. strerror(errno));
  878. continue;
  879. }
  880. fprintf(fp, "To: %s\n", (const char*)tgroup.VoidEmail());
  881. // Bcc list can be long; break it up into one line per address
  882. BreakLineToFP(fp, "Bcc: ", tgroup.CCPost(), "\n", ",");
  883. fprintf(fp, "%s", preserve.c_str());
  884. // Reply-To: Needed for mail gateway
  885. if ( tgroup.IsReplyTo() )
  886. fprintf(fp, "Reply-To: %s\n", (const char*)tgroup.ReplyTo());
  887. // Errors-To: advised so admin hears about problems, in addition
  888. // to the real person who sent the message.
  889. //
  890. fprintf(fp, "Errors-To: %s", (const char*)tgroup.Creator());
  891. fprintf(fp, "\n");
  892. fprintf(fp, "[posted to %s]\n\n", (const char*)tgroup.Name());
  893. for ( unsigned t=0; t<body.size(); t++ )
  894. fprintf(fp, "%s\n", (const char*)body[t].c_str());
  895. if ( pclose(fp) < 0 )
  896. G_conf.LogMessage(L_ERROR, "ccpost pclose failed - %s",
  897. strerror(errno));
  898. }
  899. continue;
  900. }
  901. ISIT("DATE") // COMMON EXTENSIONS - RFC 2980
  902. {
  903. if ( ! IsAllowed(AUTH_READ) ) continue;
  904. // "111 YYYYMMDDhhmmss"
  905. time_t lt = time(NULL);
  906. struct tm *tm = gmtime(&lt); // RFC 2980 -- time is GMT format, not local
  907. snprintf(reply, sizeof(reply),
  908. "111 %d%02d%02d%02d%02d%02d",
  909. (int)tm->tm_year + 1900,
  910. (int)tm->tm_mon + 1, // 0-11 -> 1-12
  911. (int)tm->tm_mday, // 1-31
  912. (int)tm->tm_hour, // 0-23
  913. (int)tm->tm_min, // 0-59
  914. (int)tm->tm_sec); // 0-59
  915. Send(reply);
  916. continue;
  917. }
  918. ISIT("QUIT") // RFC 977
  919. {
  920. Send("205 goodbye.");
  921. break;
  922. }
  923. Send("500 Command not understood");
  924. continue;
  925. }
  926. close(msgsock);
  927. G_conf.LogMessage(L_INFO, "Connection from %s closed", GetRemoteIPStr());
  928. return(0);
  929. }
  930. // OPEN A TCP LISTENER ON THE CONFIGURED ADDRESS AND PORT
  931. int Server::Listen()
  932. {
  933. if ((sock = socket (AF_INET,SOCK_STREAM,0)) < 0)
  934. { errmsg = "socket(): "; errmsg += strerror(errno); return(-1); }
  935. // Allow reuse of address to avoid "bind(): address already in use"
  936. {
  937. int on = 1;
  938. if ( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0 )
  939. { errmsg = "setsockopt(SO_REUSEADDR): "; errmsg += strerror(errno); return(-1); }
  940. }
  941. while (bind(sock, (struct sockaddr*)G_conf.Listen(),
  942. sizeof(struct sockaddr_in)) < 0)
  943. { perror("binding stream socket"); sleep(5); continue; }
  944. if ( listen(sock,5) < 0 )
  945. { errmsg = "listen(sock,5): "; errmsg += strerror(errno); return(-1); }
  946. return(0);
  947. }
  948. // ACCEPT CONNECTIONS FROM REMOTE
  949. int Server::Accept()
  950. {
  951. // fprintf(stderr, "Listening for connect requests on port %d\n",
  952. // (int)port);
  953. #if defined(DARWIN) | defined(BSD)
  954. int length = sizeof(sin);
  955. #else
  956. socklen_t length = sizeof(sin);
  957. #endif
  958. msgsock = accept(sock, (struct sockaddr*)&sin, &length);
  959. if (msgsock < 0)
  960. {
  961. errmsg = "accept(): ";
  962. errmsg += strerror(errno);
  963. return(-1);
  964. }
  965. G_conf.LogMessage(L_INFO, "Connection from host %s, port %u",
  966. (const char*)inet_ntoa(sin.sin_addr),
  967. (int)ntohs(sin.sin_port));
  968. return (0);
  969. }