Mercurial > noffle
comparison src/server.c @ 43:2842f50feb55 noffle
[svn] * client.c, client.h, common.h, config.c, config.h, content.c, content.h,
control.c, control.h, database.c, database.h, dynamicstring.c,
dynamicstring.h, fetch.c, fetch.h, fetchlist.c, fetchlist.h, group.c,
group.h, itemlist.c, itemlist.h, lock.c, lock.h, log.c, log.h, noffle.c,
online.c, online.h, outgoing.c, outgoing.h, over.c, over.h, post.c, post.h,
protocol.c, protocol.h, pseudo.c, pseudo.h, request.c, request.h, server.c,
server.h, util.c, util.h, wildmat.c, wildmat.h: Moved files to the
subdirectory src/
* Makefile.am, acconfig.h, configure.in, docs/Makefile.am, src/Makefile.am,
Makefile.in, aclocal.m4, config.h.in, configure, install-sh, missing,
mkinstalldirs, stamp-h.in, docs/Makefile.in, src/Makefile.in: Added files.
They are used by aclocal, autoheader, autoconf and automake.
* src/config.c, src/config.h: Renamed to configfile.c and configfile.h,
because configure will generate a config.h file itself.
* src/client.c, src/content.c, src/database.c, src/fetch.c, src/fetchlist.c,
src/group.c, src/lock.c, src/noffle.c, src/online.c, src/outgoing.c,
src/over.c, src/pseudo.c, src/request.c, src/server.c, src/util.c:
Changed '#include "config.h"' to '#include "configfile.h"'.
* src/client.c, src/content.c, src/database.c, src/fetch.c, src/fetchlist.c,
src/group.c, src/lock.c, src/online.c, src/outgoing.c, src/post.c,
src/protocol.c, src/request.c, src/server.c: Files now #include <config.h>.
Added missing <stdio.h>. This removes the warnings about snprintf() not
being declared.
* Makefile: Removed. This is now generated by configure.
| author | uh1763 |
|---|---|
| date | Fri, 05 May 2000 22:45:56 +0100 |
| parents | |
| children | 32ba1198c6fa |
comparison
equal
deleted
inserted
replaced
| 42:2467ff423c15 | 43:2842f50feb55 |
|---|---|
| 1 /* | |
| 2 server.c | |
| 3 | |
| 4 $Id: server.c 49 2000-05-05 21:45:56Z uh1763 $ | |
| 5 */ | |
| 6 | |
| 7 #if HAVE_CONFIG_H | |
| 8 #include <config.h> | |
| 9 #endif | |
| 10 | |
| 11 #include <stdio.h> | |
| 12 #include "server.h" | |
| 13 #include <ctype.h> | |
| 14 #include <signal.h> | |
| 15 #include <stdarg.h> | |
| 16 #include <sys/time.h> | |
| 17 #include <sys/types.h> | |
| 18 #include <time.h> | |
| 19 #include <unistd.h> | |
| 20 #include "client.h" | |
| 21 #include "common.h" | |
| 22 #include "configfile.h" | |
| 23 #include "content.h" | |
| 24 #include "control.h" | |
| 25 #include "database.h" | |
| 26 #include "dynamicstring.h" | |
| 27 #include "fetch.h" | |
| 28 #include "fetchlist.h" | |
| 29 #include "group.h" | |
| 30 #include "itemlist.h" | |
| 31 #include "lock.h" | |
| 32 #include "log.h" | |
| 33 #include "online.h" | |
| 34 #include "outgoing.h" | |
| 35 #include "post.h" | |
| 36 #include "protocol.h" | |
| 37 #include "pseudo.h" | |
| 38 #include "request.h" | |
| 39 #include "util.h" | |
| 40 #include "wildmat.h" | |
| 41 | |
| 42 struct | |
| 43 { | |
| 44 Bool running; | |
| 45 int artPtr; | |
| 46 Str grp; /* selected group, "" if none */ | |
| 47 } serv = { FALSE, 0, "" }; | |
| 48 | |
| 49 typedef struct Cmd | |
| 50 { | |
| 51 const char *name; | |
| 52 const char *syntax; | |
| 53 /* Returns false, if quit cmd */ | |
| 54 Bool (*cmdProc)( char *arg, const struct Cmd *cmd ); | |
| 55 } | |
| 56 Cmd; | |
| 57 | |
| 58 static Bool doArt( char *arg, const Cmd *cmd ); | |
| 59 static Bool doBody( char *arg, const Cmd *cmd ); | |
| 60 static Bool doGrp( char *arg, const Cmd *cmd ); | |
| 61 static Bool doHead( char *arg, const Cmd *cmd ); | |
| 62 static Bool doHelp( char *arg, const Cmd *cmd ); | |
| 63 static Bool doIhave( char *arg, const Cmd *cmd ); | |
| 64 static Bool doLast( char *arg, const Cmd *cmd ); | |
| 65 static Bool doList( char *arg, const Cmd *cmd ); | |
| 66 static Bool doListgrp( char *arg, const Cmd *cmd ); | |
| 67 static Bool doMode( char *arg, const Cmd *cmd ); | |
| 68 static Bool doNewgrps( char *arg, const Cmd *cmd ); | |
| 69 static Bool doNext( char *arg, const Cmd *cmd ); | |
| 70 static Bool doPost( char *arg, const Cmd *cmd ); | |
| 71 static Bool doSlave( char *arg, const Cmd *cmd ); | |
| 72 static Bool doStat( char *arg, const Cmd *cmd ); | |
| 73 static Bool doQuit( char *arg, const Cmd *cmd ); | |
| 74 static Bool doXhdr( char *arg, const Cmd *cmd ); | |
| 75 static Bool doXpat( char *arg, const Cmd *cmd ); | |
| 76 static Bool doXOver( char *arg, const Cmd *cmd ); | |
| 77 static Bool notImplemented( char *arg, const Cmd *cmd ); | |
| 78 static void putStat( unsigned int stat, const char *fmt, ... ); | |
| 79 | |
| 80 Cmd commands[] = | |
| 81 { | |
| 82 { "article", "ARTICLE [msg-id|n]", &doArt }, | |
| 83 { "body", "BODY [msg-id|n]", &doBody }, | |
| 84 { "head", "HEAD [msg-id|n]", &doHead }, | |
| 85 { "group", "GROUP grp", &doGrp }, | |
| 86 { "help", "HELP", &doHelp }, | |
| 87 { "ihave", "IHAVE (ignored)", &doIhave }, | |
| 88 { "last", "LAST", &doLast }, | |
| 89 { "list", "LIST [ACTIVE [pat]]|ACTIVE.TIMES [pat]|" | |
| 90 "EXTENSIONS|NEWSGROUPS [pat]|OVERVIEW.FMT", &doList }, | |
| 91 { "listgroup", "LISTGROUP grp", &doListgrp }, | |
| 92 { "mode", "MODE (ignored)", &doMode }, | |
| 93 { "newgroups", "NEWGROUPS [xx]yymmdd hhmmss [GMT]", &doNewgrps }, | |
| 94 { "newnews", "NEWNEWS (not implemented)", ¬Implemented }, | |
| 95 { "next", "NEXT", &doNext }, | |
| 96 { "post", "POST", &doPost }, | |
| 97 { "quit", "QUIT", &doQuit }, | |
| 98 { "slave", "SLAVE (ignored)", &doSlave }, | |
| 99 { "stat", "STAT [msg-id|n]", &doStat }, | |
| 100 { "xhdr", "XHDR over-field [m[-[n]]]", &doXhdr }, | |
| 101 { "xpat", "XPAT over-field m[-[n]] pat", &doXpat }, | |
| 102 { "xover", "XOVER [m[-[n]]]", &doXOver } | |
| 103 }; | |
| 104 | |
| 105 /* | |
| 106 Notice interest in reading this group. | |
| 107 Automatically subscribe if option set in config file. | |
| 108 */ | |
| 109 static void | |
| 110 noteInterest( void ) | |
| 111 { | |
| 112 FetchMode mode; | |
| 113 | |
| 114 Grp_setLastAccess( serv.grp, time( NULL ) ); | |
| 115 if ( ! Grp_local ( serv.grp ) && Cfg_autoSubscribe() && ! Online_true() ) | |
| 116 { | |
| 117 Fetchlist_read(); | |
| 118 if ( ! Fetchlist_contains( serv.grp ) ) | |
| 119 { | |
| 120 if ( strcmp( Cfg_autoSubscribeMode(), "full" ) == 0 ) | |
| 121 mode = FULL; | |
| 122 else if ( strcmp( Cfg_autoSubscribeMode(), "thread" ) == 0 ) | |
| 123 mode = THREAD; | |
| 124 else | |
| 125 mode = OVER; | |
| 126 Fetchlist_add( serv.grp, mode ); | |
| 127 Fetchlist_write(); | |
| 128 Pseudo_autoSubscribed(); | |
| 129 } | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 static void | |
| 134 putStat( unsigned int stat, const char *fmt, ... ) | |
| 135 { | |
| 136 Str s, line; | |
| 137 va_list ap; | |
| 138 | |
| 139 ASSERT( stat <= 999 ); | |
| 140 va_start( ap, fmt ); | |
| 141 vsnprintf( s, MAXCHAR, fmt, ap ); | |
| 142 va_end( ap ); | |
| 143 snprintf( line, MAXCHAR, "%u %s", stat, s ); | |
| 144 Log_dbg( "[S] %s", line ); | |
| 145 printf( "%s\r\n", line ); | |
| 146 } | |
| 147 | |
| 148 static void | |
| 149 putTxtLn( const char *fmt, ... ) | |
| 150 { | |
| 151 Str line; | |
| 152 va_list ap; | |
| 153 | |
| 154 va_start( ap, fmt ); | |
| 155 vsnprintf( line, MAXCHAR, fmt, ap ); | |
| 156 va_end( ap ); | |
| 157 Prt_putTxtLn( line, stdout ); | |
| 158 } | |
| 159 | |
| 160 static void | |
| 161 putTxtBuf( const char *buf ) | |
| 162 { | |
| 163 if ( buf ) | |
| 164 Prt_putTxtBuf( buf, stdout ); | |
| 165 } | |
| 166 | |
| 167 static void | |
| 168 putEndOfTxt( void ) | |
| 169 { | |
| 170 Prt_putEndOfTxt( stdout ); | |
| 171 } | |
| 172 | |
| 173 static void | |
| 174 putSyntax( const Cmd *cmd ) | |
| 175 { | |
| 176 putStat( STAT_SYNTAX_ERR, "Syntax error. Usage: %s", cmd->syntax ); | |
| 177 } | |
| 178 | |
| 179 static Bool | |
| 180 getLn( Str line ) | |
| 181 { | |
| 182 return Prt_getLn( line, stdin ); | |
| 183 } | |
| 184 | |
| 185 static Bool | |
| 186 getTxtLn( Str line, Bool *err ) | |
| 187 { | |
| 188 return Prt_getTxtLn( line, err, stdin ); | |
| 189 } | |
| 190 | |
| 191 static Bool | |
| 192 notImplemented( char *arg, const Cmd *cmd ) | |
| 193 { | |
| 194 putStat( STAT_NO_PERMISSION, "Command not implemented" ); | |
| 195 return TRUE; | |
| 196 } | |
| 197 | |
| 198 static void | |
| 199 checkNewArts( const char *grp ) | |
| 200 { | |
| 201 if ( ! Online_true() | |
| 202 || strcmp( grp, serv.grp ) == 0 | |
| 203 || Grp_local( grp ) | |
| 204 || time( NULL ) - Grp_lastAccess( serv.grp ) < 1800 ) | |
| 205 return; | |
| 206 if ( Fetch_init( Grp_serv( grp ) ) ) | |
| 207 { | |
| 208 Fetch_getNewArts( grp, OVER ); | |
| 209 Fetch_close(); | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 static void | |
| 214 postArts() | |
| 215 { | |
| 216 Str serv; | |
| 217 | |
| 218 Cfg_beginServEnum(); | |
| 219 while ( Cfg_nextServ( serv ) ) | |
| 220 if ( Fetch_init( serv ) ) | |
| 221 { | |
| 222 Fetch_postArts(); | |
| 223 Fetch_close(); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 static void | |
| 228 readCont( const char *name ) | |
| 229 { | |
| 230 Fetchlist_read(); | |
| 231 Cont_read( name ); | |
| 232 if ( ! Grp_local ( name ) | |
| 233 && ! Fetchlist_contains( name ) | |
| 234 && ! Online_true() ) | |
| 235 { | |
| 236 Pseudo_appGeneralInfo(); | |
| 237 Grp_setFirstLast( name, Cont_first(), Cont_last() ); | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 static void | |
| 242 changeToGrp( const char *grp ) | |
| 243 { | |
| 244 checkNewArts( grp ); | |
| 245 Utl_cpyStr( serv.grp, grp ); | |
| 246 readCont( grp ); | |
| 247 serv.artPtr = Cont_first(); | |
| 248 } | |
| 249 | |
| 250 static Bool | |
| 251 doGrp( char *arg, const Cmd *cmd ) | |
| 252 { | |
| 253 int first, last, numb; | |
| 254 | |
| 255 if ( arg[ 0 ] == '\0' ) | |
| 256 putSyntax( cmd ); | |
| 257 else if ( ! Grp_exists( arg ) ) | |
| 258 putStat( STAT_NO_SUCH_GRP, "No such group" ); | |
| 259 else | |
| 260 { | |
| 261 changeToGrp( arg ); | |
| 262 first = Cont_first(); | |
| 263 last = Cont_last(); | |
| 264 if ( ( first == 0 && last == 0 ) | |
| 265 || first > last ) | |
| 266 first = last = numb = 0; | |
| 267 else | |
| 268 numb = last - first + 1; | |
| 269 putStat( STAT_GRP_SELECTED, "%lu %lu %lu %s selected", | |
| 270 numb, first, last, arg ); | |
| 271 } | |
| 272 return TRUE; | |
| 273 } | |
| 274 | |
| 275 static Bool | |
| 276 testGrpSelected( void ) | |
| 277 { | |
| 278 if ( *serv.grp == '\0' ) | |
| 279 { | |
| 280 putStat( STAT_NO_GRP_SELECTED, "No group selected" ); | |
| 281 return FALSE; | |
| 282 } | |
| 283 return TRUE; | |
| 284 } | |
| 285 | |
| 286 static void | |
| 287 findServ( const char *msgId, Str result ) | |
| 288 { | |
| 289 const char *p, *pColon, *serv; | |
| 290 Str s, grp; | |
| 291 | |
| 292 Utl_cpyStr( result, "(unknown)" ); | |
| 293 if ( Db_contains( msgId ) ) | |
| 294 { | |
| 295 Utl_cpyStr( s, Db_xref( msgId ) ); | |
| 296 p = strtok( s, " \t" ); | |
| 297 if ( p ) | |
| 298 do | |
| 299 { | |
| 300 pColon = strstr( p, ":" ); | |
| 301 if ( pColon ) | |
| 302 { | |
| 303 Utl_cpyStrN( grp, p, pColon - p ); | |
| 304 serv = Grp_serv( grp ); | |
| 305 if ( Cfg_servIsPreferential( serv, result ) ) | |
| 306 Utl_cpyStr( result, serv ); | |
| 307 } | |
| 308 } | |
| 309 while ( ( p = strtok( NULL, " \t" ) ) ); | |
| 310 } | |
| 311 } | |
| 312 | |
| 313 static Bool | |
| 314 retrieveArt( const char *msgId ) | |
| 315 { | |
| 316 Str serv; | |
| 317 | |
| 318 findServ( msgId, serv ); | |
| 319 if ( strcmp( serv, "(unknown)" ) == 0 | |
| 320 || strcmp( serv, GRP_LOCAL_SERVER_NAME ) == 0 ) | |
| 321 return FALSE; | |
| 322 if ( ! Client_connect( serv ) ) | |
| 323 return FALSE; | |
| 324 Client_retrieveArt( msgId ); | |
| 325 Client_disconnect(); | |
| 326 return TRUE; | |
| 327 } | |
| 328 | |
| 329 static Bool | |
| 330 checkNumb( int numb ) | |
| 331 { | |
| 332 if ( ! testGrpSelected() ) | |
| 333 return FALSE; | |
| 334 if ( ! Cont_validNumb( numb ) ) | |
| 335 { | |
| 336 putStat( STAT_NO_SUCH_NUMB, "No such article" ); | |
| 337 return FALSE; | |
| 338 } | |
| 339 return TRUE; | |
| 340 } | |
| 341 | |
| 342 /* | |
| 343 Parse arguments for ARTICLE, BODY, HEAD, STAT commands. | |
| 344 Return message-ID and article number (0 if unknown). | |
| 345 */ | |
| 346 static Bool | |
| 347 whichId( const char **msgId, int *numb, char *arg ) | |
| 348 { | |
| 349 const Over *ov; | |
| 350 int n; | |
| 351 | |
| 352 if ( sscanf( arg, "%d", &n ) == 1 ) | |
| 353 { | |
| 354 if ( ! checkNumb( n ) ) | |
| 355 return FALSE; | |
| 356 serv.artPtr = n; | |
| 357 ov = Cont_get( n ); | |
| 358 *msgId = Ov_msgId( ov ); | |
| 359 *numb = n; | |
| 360 } | |
| 361 else if ( strcmp( arg, "" ) == 0 ) | |
| 362 { | |
| 363 if ( ! checkNumb( serv.artPtr ) ) | |
| 364 return FALSE; | |
| 365 ov = Cont_get( serv.artPtr ); | |
| 366 *msgId = Ov_msgId( ov ); | |
| 367 *numb = serv.artPtr; | |
| 368 } | |
| 369 else | |
| 370 { | |
| 371 *msgId = arg; | |
| 372 *numb = 0; | |
| 373 } | |
| 374 if ( ! Pseudo_isGeneralInfo( *msgId ) && ! Db_contains( *msgId ) ) | |
| 375 { | |
| 376 putStat( STAT_NO_SUCH_NUMB, "No such article" ); | |
| 377 return FALSE; | |
| 378 } | |
| 379 return TRUE; | |
| 380 } | |
| 381 | |
| 382 void | |
| 383 touchArticle( const char *msgId ) | |
| 384 { | |
| 385 int stat = Db_stat( msgId ); | |
| 386 stat |= DB_INTERESTING; | |
| 387 Db_setStat( msgId, stat ); | |
| 388 Db_updateLastAccess( msgId ); | |
| 389 } | |
| 390 | |
| 391 static void | |
| 392 touchReferences( const char *msgId ) | |
| 393 { | |
| 394 Str s; | |
| 395 int len; | |
| 396 char *p; | |
| 397 const char *ref = Db_ref( msgId ); | |
| 398 | |
| 399 while ( TRUE ) | |
| 400 { | |
| 401 p = s; | |
| 402 while ( *ref != '<' ) | |
| 403 if ( *(ref++) == '\0' ) | |
| 404 return; | |
| 405 len = 0; | |
| 406 while ( *ref != '>' ) | |
| 407 { | |
| 408 if ( *ref == '\0' || ++len >= MAXCHAR - 1 ) | |
| 409 return; | |
| 410 *(p++) = *(ref++); | |
| 411 } | |
| 412 *(p++) = '>'; | |
| 413 *p = '\0'; | |
| 414 if ( Db_contains( s ) ) | |
| 415 touchArticle( s ); | |
| 416 } | |
| 417 } | |
| 418 | |
| 419 static void | |
| 420 doBodyInDb( const char *msgId ) | |
| 421 { | |
| 422 int stat; | |
| 423 Str serv; | |
| 424 | |
| 425 touchArticle( msgId ); | |
| 426 touchReferences( msgId ); | |
| 427 stat = Db_stat( msgId ); | |
| 428 if ( Online_true() && ( stat & DB_NOT_DOWNLOADED ) ) | |
| 429 { | |
| 430 retrieveArt( msgId ); | |
| 431 stat = Db_stat( msgId ); | |
| 432 } | |
| 433 if ( stat & DB_RETRIEVING_FAILED ) | |
| 434 { | |
| 435 Db_setStat( msgId, stat & ~DB_RETRIEVING_FAILED ); | |
| 436 putTxtBuf( Db_body( msgId ) ); | |
| 437 } | |
| 438 else if ( stat & DB_NOT_DOWNLOADED ) | |
| 439 { | |
| 440 findServ( msgId, serv ); | |
| 441 if ( Req_contains( serv, msgId ) ) | |
| 442 putTxtBuf( Pseudo_alreadyMarkedBody() ); | |
| 443 else if ( strcmp( serv, "(unknown)" ) != 0 && | |
| 444 strcmp( serv, GRP_LOCAL_SERVER_NAME ) != 0 && | |
| 445 Req_add( serv, msgId ) ) | |
| 446 putTxtBuf( Pseudo_markedBody() ); | |
| 447 else | |
| 448 putTxtBuf( Pseudo_markingFailedBody() ); | |
| 449 } | |
| 450 else | |
| 451 putTxtBuf( Db_body( msgId ) ); | |
| 452 } | |
| 453 | |
| 454 static Bool | |
| 455 doBody( char *arg, const Cmd *cmd ) | |
| 456 { | |
| 457 const char *msgId; | |
| 458 int numb; | |
| 459 | |
| 460 if ( ! whichId( &msgId, &numb, arg ) ) | |
| 461 return TRUE; | |
| 462 putStat( STAT_BODY_FOLLOWS, "%ld %s Body", numb, msgId ); | |
| 463 if ( Pseudo_isGeneralInfo( msgId ) ) | |
| 464 putTxtBuf( Pseudo_generalInfoBody() ); | |
| 465 else | |
| 466 doBodyInDb( msgId ); | |
| 467 putEndOfTxt(); | |
| 468 noteInterest(); | |
| 469 return TRUE; | |
| 470 } | |
| 471 | |
| 472 static void | |
| 473 doHeadInDb( const char *msgId ) | |
| 474 { | |
| 475 putTxtBuf( Db_header( msgId ) ); | |
| 476 } | |
| 477 | |
| 478 static Bool | |
| 479 doHead( char *arg, const Cmd *cmd ) | |
| 480 { | |
| 481 const char *msgId; | |
| 482 int numb; | |
| 483 | |
| 484 if ( ! whichId( &msgId, &numb, arg ) ) | |
| 485 return TRUE; | |
| 486 putStat( STAT_HEAD_FOLLOWS, "%ld %s Head", numb, msgId ); | |
| 487 if ( Pseudo_isGeneralInfo( msgId ) ) | |
| 488 putTxtBuf( Pseudo_generalInfoHead() ); | |
| 489 else | |
| 490 doHeadInDb( msgId ); | |
| 491 putEndOfTxt(); | |
| 492 return TRUE; | |
| 493 } | |
| 494 | |
| 495 static void | |
| 496 doArtInDb( const char *msgId ) | |
| 497 { | |
| 498 doHeadInDb( msgId ); | |
| 499 putTxtLn( "" ); | |
| 500 doBodyInDb( msgId ); | |
| 501 } | |
| 502 | |
| 503 static Bool | |
| 504 doArt( char *arg, const Cmd *cmd ) | |
| 505 { | |
| 506 const char *msgId; | |
| 507 int numb; | |
| 508 | |
| 509 if ( ! whichId( &msgId, &numb, arg ) ) | |
| 510 return TRUE; | |
| 511 putStat( STAT_ART_FOLLOWS, "%ld %s Article", numb, msgId ); | |
| 512 if ( Pseudo_isGeneralInfo( msgId ) ) | |
| 513 { | |
| 514 putTxtBuf( Pseudo_generalInfoHead() ); | |
| 515 putTxtLn( "" ); | |
| 516 putTxtBuf( Pseudo_generalInfoBody() ); | |
| 517 } | |
| 518 else | |
| 519 doArtInDb( msgId ); | |
| 520 putEndOfTxt(); | |
| 521 noteInterest(); | |
| 522 return TRUE; | |
| 523 } | |
| 524 | |
| 525 static Bool | |
| 526 doHelp( char *arg, const Cmd *cmd ) | |
| 527 { | |
| 528 unsigned int i; | |
| 529 | |
| 530 putStat( STAT_HELP_FOLLOWS, "Help" ); | |
| 531 putTxtBuf( "\nCommands:\n\n" ); | |
| 532 for ( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); ++i ) | |
| 533 putTxtLn( "%s", commands[ i ].syntax ); | |
| 534 putEndOfTxt(); | |
| 535 return TRUE; | |
| 536 } | |
| 537 | |
| 538 static Bool | |
| 539 doIhave( char *arg, const Cmd *cmd ) | |
| 540 { | |
| 541 putStat( STAT_ART_REJECTED, "Command not used" ); | |
| 542 return TRUE; | |
| 543 } | |
| 544 | |
| 545 static Bool | |
| 546 doLast( char *arg, const Cmd *cmd ) | |
| 547 { | |
| 548 int n; | |
| 549 | |
| 550 if ( testGrpSelected() ) | |
| 551 { | |
| 552 n = serv.artPtr; | |
| 553 if ( ! Cont_validNumb( n ) ) | |
| 554 putStat( STAT_NO_ART_SELECTED, "No article selected" ); | |
| 555 else | |
| 556 { | |
| 557 while ( ! Cont_validNumb( --n ) && n >= Cont_first() ); | |
| 558 if ( ! Cont_validNumb( n ) ) | |
| 559 putStat( STAT_NO_PREV_ART, "No previous article" ); | |
| 560 else | |
| 561 { | |
| 562 putStat( STAT_ART_RETRIEVED, "%ld %s selected", | |
| 563 n, Ov_msgId( Cont_get( n ) ) ); | |
| 564 serv.artPtr = n; | |
| 565 } | |
| 566 } | |
| 567 } | |
| 568 return TRUE; | |
| 569 } | |
| 570 | |
| 571 static void | |
| 572 printGroups( const char *pat, void (*printProc)( Str, const char* ) ) | |
| 573 { | |
| 574 Str line; | |
| 575 const char *g; | |
| 576 FILE *f; | |
| 577 sig_t lastHandler; | |
| 578 int ret; | |
| 579 | |
| 580 putStat( STAT_GRPS_FOLLOW, "Groups" ); | |
| 581 fflush( stdout ); | |
| 582 Log_dbg( "[S FLUSH]" ); | |
| 583 if ( Grp_exists( pat ) ) | |
| 584 { | |
| 585 (*printProc)( line, pat ); | |
| 586 if ( ! Prt_putTxtLn( line, stdout ) ) | |
| 587 Log_err( "Writing to stdout failed." ); | |
| 588 } | |
| 589 else | |
| 590 { | |
| 591 lastHandler = signal( SIGPIPE, SIG_IGN ); | |
| 592 f = popen( "sort", "w" ); | |
| 593 if ( f == NULL ) | |
| 594 { | |
| 595 Log_err( "Cannot open pipe to 'sort'" ); | |
| 596 if ( Grp_firstGrp( &g ) ) | |
| 597 do | |
| 598 if ( Wld_match( g, pat ) ) | |
| 599 { | |
| 600 (*printProc)( line, g ); | |
| 601 if ( ! Prt_putTxtLn( line, stdout ) ) | |
| 602 Log_err( "Writing to stdout failed." ); | |
| 603 } | |
| 604 while ( Grp_nextGrp( &g ) ); | |
| 605 } | |
| 606 else | |
| 607 { | |
| 608 if ( Grp_firstGrp( &g ) ) | |
| 609 do | |
| 610 if ( Wld_match( g, pat ) ) | |
| 611 { | |
| 612 (*printProc)( line, g ); | |
| 613 if ( ! Prt_putTxtLn( line, f ) ) | |
| 614 { | |
| 615 Log_err( "Writing to 'sort' pipe failed." ); | |
| 616 break; | |
| 617 } | |
| 618 } | |
| 619 while ( Grp_nextGrp( &g ) ); | |
| 620 ret = pclose( f ); | |
| 621 if ( ret != EXIT_SUCCESS ) | |
| 622 Log_err( "sort command returned %d", ret ); | |
| 623 fflush( stdout ); | |
| 624 Log_dbg( "[S FLUSH]" ); | |
| 625 signal( SIGPIPE, lastHandler ); | |
| 626 } | |
| 627 } | |
| 628 putEndOfTxt(); | |
| 629 } | |
| 630 | |
| 631 static void | |
| 632 printActiveTimes( Str result, const char *grp ) | |
| 633 { | |
| 634 snprintf( result, MAXCHAR, "%s %ld", grp, Grp_created( grp ) ); | |
| 635 } | |
| 636 | |
| 637 static void | |
| 638 doListActiveTimes( const char *pat ) | |
| 639 { | |
| 640 printGroups( pat, &printActiveTimes ); | |
| 641 } | |
| 642 | |
| 643 static void | |
| 644 printActive( Str result, const char *grp ) | |
| 645 { | |
| 646 snprintf( result, MAXCHAR, "%s %d %d %c", | |
| 647 grp, Grp_last( grp ), Grp_first( grp ), Grp_postAllow( grp ) ); | |
| 648 } | |
| 649 | |
| 650 static void | |
| 651 doListActive( const char *pat ) | |
| 652 { | |
| 653 printGroups( pat, &printActive ); | |
| 654 } | |
| 655 | |
| 656 static void | |
| 657 printNewsgrp( Str result, const char *grp ) | |
| 658 { | |
| 659 snprintf( result, MAXCHAR, "%s %s", grp, Grp_dsc( grp ) ); | |
| 660 } | |
| 661 | |
| 662 static void | |
| 663 doListNewsgrps( const char *pat ) | |
| 664 { | |
| 665 printGroups( pat, &printNewsgrp ); | |
| 666 } | |
| 667 | |
| 668 static void | |
| 669 putGrp( const char *name ) | |
| 670 { | |
| 671 putTxtLn( "%s %lu %lu y", name, Grp_last( name ), Grp_first( name ) ); | |
| 672 } | |
| 673 | |
| 674 static void | |
| 675 doListOverFmt( void ) | |
| 676 { | |
| 677 putStat( STAT_GRPS_FOLLOW, "Overview format" ); | |
| 678 putTxtBuf( "Subject:\n" | |
| 679 "From:\n" | |
| 680 "Date:\n" | |
| 681 "Message-ID:\n" | |
| 682 "References:\n" | |
| 683 "Bytes:\n" | |
| 684 "Lines:\n" ); | |
| 685 putEndOfTxt(); | |
| 686 } | |
| 687 | |
| 688 static void | |
| 689 doListExtensions( void ) | |
| 690 { | |
| 691 putStat( STAT_CMD_OK, "Extensions" ); | |
| 692 putTxtBuf( " LISTGROUP\n" | |
| 693 " XOVER\n" ); | |
| 694 putEndOfTxt(); | |
| 695 } | |
| 696 | |
| 697 static Bool | |
| 698 doList( char *line, const Cmd *cmd ) | |
| 699 { | |
| 700 Str s, arg; | |
| 701 const char *pat; | |
| 702 | |
| 703 if ( sscanf( line, "%s", s ) != 1 ) | |
| 704 doListActive( "*" ); | |
| 705 else | |
| 706 { | |
| 707 Utl_toLower( s ); | |
| 708 strcpy( arg, Utl_restOfLn( line, 1 ) ); | |
| 709 pat = Utl_stripWhiteSpace( arg ); | |
| 710 if ( pat[ 0 ] == '\0' ) | |
| 711 pat = "*"; | |
| 712 if ( strcmp( "active", s ) == 0 ) | |
| 713 doListActive( pat ); | |
| 714 else if ( strcmp( "overview.fmt", s ) == 0 ) | |
| 715 doListOverFmt(); | |
| 716 else if ( strcmp( "newsgroups", s ) == 0 ) | |
| 717 doListNewsgrps( pat ); | |
| 718 else if ( strcmp( "active.times", s ) == 0 ) | |
| 719 doListActiveTimes( pat ); | |
| 720 else if ( strcmp( "extensions", s ) == 0 ) | |
| 721 doListExtensions(); | |
| 722 else | |
| 723 putSyntax( cmd ); | |
| 724 } | |
| 725 return TRUE; | |
| 726 } | |
| 727 | |
| 728 static Bool | |
| 729 doListgrp( char *arg, const Cmd *cmd ) | |
| 730 { | |
| 731 const Over *ov; | |
| 732 int first, last, i; | |
| 733 | |
| 734 if ( ! Grp_exists( arg ) ) | |
| 735 putStat( STAT_NO_SUCH_GRP, "No such group" ); | |
| 736 else | |
| 737 { | |
| 738 changeToGrp( arg ); | |
| 739 first = Cont_first(); | |
| 740 last = Cont_last(); | |
| 741 putStat( STAT_GRP_SELECTED, "Article list" ); | |
| 742 for ( i = first; i <= last; ++i ) | |
| 743 if ( ( ov = Cont_get( i ) ) ) | |
| 744 putTxtLn( "%lu", i ); | |
| 745 putEndOfTxt(); | |
| 746 } | |
| 747 return TRUE; | |
| 748 } | |
| 749 | |
| 750 static Bool | |
| 751 doMode( char *arg, const Cmd *cmd ) | |
| 752 { | |
| 753 putStat( STAT_READY_POST_ALLOW, "Ok" ); | |
| 754 return TRUE; | |
| 755 } | |
| 756 | |
| 757 static unsigned long | |
| 758 getTimeInSeconds( unsigned int year, unsigned int mon, unsigned int day, | |
| 759 unsigned int hour, unsigned int min, unsigned int sec ) | |
| 760 { | |
| 761 struct tm t = { 0 }; | |
| 762 | |
| 763 t.tm_year = year - 1900; | |
| 764 t.tm_mon = mon - 1; | |
| 765 t.tm_mday = day; | |
| 766 t.tm_hour = hour; | |
| 767 t.tm_min = min; | |
| 768 t.tm_sec = sec; | |
| 769 return mktime( &t ); | |
| 770 } | |
| 771 | |
| 772 | |
| 773 static Bool | |
| 774 doNewgrps( char *arg, const Cmd *cmd ) | |
| 775 { | |
| 776 time_t t, now, lastUpdate; | |
| 777 unsigned int year, mon, day, hour, min, sec, cent, len; | |
| 778 const char *g; | |
| 779 Str date, timeofday, file; | |
| 780 | |
| 781 if ( sscanf( arg, "%s %s", date, timeofday ) != 2 ) | |
| 782 { | |
| 783 putSyntax( cmd ); | |
| 784 return TRUE; | |
| 785 } | |
| 786 len = strlen( date ); | |
| 787 switch ( len ) | |
| 788 { | |
| 789 case 6: | |
| 790 if ( sscanf( date, "%2u%2u%2u", &year, &mon, &day ) != 3 ) | |
| 791 { | |
| 792 putSyntax( cmd ); | |
| 793 return TRUE; | |
| 794 } | |
| 795 now = time( NULL ); | |
| 796 cent = 1900; | |
| 797 while ( now > getTimeInSeconds( cent + 100, 1, 1, 0, 0, 0 ) ) | |
| 798 cent += 100; | |
| 799 year += cent; | |
| 800 break; | |
| 801 case 8: | |
| 802 if ( sscanf( date, "%4u%2u%2u", &year, &mon, &day ) != 3 ) | |
| 803 { | |
| 804 putSyntax( cmd ); | |
| 805 return TRUE; | |
| 806 } | |
| 807 break; | |
| 808 default: | |
| 809 putSyntax( cmd ); | |
| 810 return TRUE; | |
| 811 } | |
| 812 if ( sscanf( timeofday, "%2u%2u%2u", &hour, &min, &sec ) != 3 ) | |
| 813 { | |
| 814 putSyntax( cmd ); | |
| 815 return TRUE; | |
| 816 } | |
| 817 if ( year < 1970 || mon == 0 || mon > 12 || day == 0 || day > 31 | |
| 818 || hour > 23 || min > 59 || sec > 60 ) | |
| 819 { | |
| 820 putSyntax( cmd ); | |
| 821 return TRUE; | |
| 822 } | |
| 823 snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); | |
| 824 t = getTimeInSeconds( year, mon, day, hour, min, sec ); | |
| 825 putStat( STAT_NEW_GRP_FOLLOW, "New groups since %s", arg ); | |
| 826 | |
| 827 if ( ! Utl_getStamp( &lastUpdate, file ) || t <= lastUpdate ) | |
| 828 { | |
| 829 if ( Grp_firstGrp( &g ) ) | |
| 830 do | |
| 831 if ( Grp_created( g ) > t ) | |
| 832 putGrp( g ); | |
| 833 while ( Grp_nextGrp( &g ) ); | |
| 834 } | |
| 835 putEndOfTxt(); | |
| 836 return TRUE; | |
| 837 } | |
| 838 | |
| 839 static Bool | |
| 840 doNext( char *arg, const Cmd *cmd ) | |
| 841 { | |
| 842 int n; | |
| 843 | |
| 844 if ( testGrpSelected() ) | |
| 845 { | |
| 846 n = serv.artPtr; | |
| 847 if ( ! Cont_validNumb( n ) ) | |
| 848 putStat( STAT_NO_ART_SELECTED, "No article selected" ); | |
| 849 else | |
| 850 { | |
| 851 while ( ! Cont_validNumb( ++n ) && n <= Cont_last() ); | |
| 852 if ( ! Cont_validNumb( n ) ) | |
| 853 putStat( STAT_NO_NEXT_ART, "No next article" ); | |
| 854 else | |
| 855 { | |
| 856 putStat( STAT_ART_RETRIEVED, "%ld %s selected", | |
| 857 n, Ov_msgId( Cont_get( n ) ) ); | |
| 858 serv.artPtr = n; | |
| 859 } | |
| 860 } | |
| 861 } | |
| 862 return TRUE; | |
| 863 } | |
| 864 | |
| 865 /* Cancel and return TRUE if need to send cancel message on to server. */ | |
| 866 static Bool | |
| 867 controlCancel( const char *cancelId ) | |
| 868 { | |
| 869 return ( Ctrl_cancel( cancelId ) == CANCEL_NEEDS_MSG ); | |
| 870 } | |
| 871 | |
| 872 /* | |
| 873 It's a control message. Currently we only know about 'cancel' | |
| 874 messages; others are passed on for outside groups, and logged | |
| 875 as ignored for local groups. | |
| 876 */ | |
| 877 static Bool | |
| 878 handleControl( ItemList *control, ItemList *newsgroups, | |
| 879 const char *msgId, const DynStr *art ) | |
| 880 { | |
| 881 const char *grp; | |
| 882 const char *op; | |
| 883 Bool err = FALSE; | |
| 884 Bool localDone = FALSE; | |
| 885 | |
| 886 op = Itl_first( control ); | |
| 887 if ( op == NULL ) | |
| 888 { | |
| 889 Log_err( "Malformed control line." ); | |
| 890 return TRUE; | |
| 891 } | |
| 892 else if ( strcasecmp( op, "cancel" ) == 0 ) | |
| 893 { | |
| 894 if ( controlCancel( Itl_next( control ) ) ) | |
| 895 localDone = TRUE; | |
| 896 else | |
| 897 return err; | |
| 898 } | |
| 899 | |
| 900 /* Pass on for outside groups. */ | |
| 901 for( grp = Itl_first( newsgroups ); | |
| 902 grp != NULL; | |
| 903 grp = Itl_next( newsgroups ) ) | |
| 904 { | |
| 905 if ( Grp_exists( grp ) && ! Grp_local( grp ) ) | |
| 906 { | |
| 907 if ( ! Out_add( Grp_serv( grp ), msgId, art ) ) | |
| 908 { | |
| 909 Log_err( "Cannot add posted article to outgoing directory" ); | |
| 910 err = TRUE; | |
| 911 } | |
| 912 break; | |
| 913 } | |
| 914 } | |
| 915 | |
| 916 if ( localDone ) | |
| 917 return err; | |
| 918 | |
| 919 /* Log 'can't do' for internal groups. */ | |
| 920 for( grp = Itl_first( newsgroups ); | |
| 921 grp != NULL; | |
| 922 grp = Itl_next( newsgroups ) ) | |
| 923 { | |
| 924 if ( Grp_exists( grp ) && Grp_local( grp ) ) | |
| 925 Log_inf( "Ignoring control '%s' for '%s'.", op, grp ); | |
| 926 } | |
| 927 | |
| 928 return err; | |
| 929 } | |
| 930 | |
| 931 static Bool | |
| 932 postArticle( ItemList *newsgroups, const char *msgId, const DynStr *art ) | |
| 933 { | |
| 934 const char *grp; | |
| 935 Bool err; | |
| 936 Bool oneLocal; | |
| 937 | |
| 938 err = oneLocal = FALSE; | |
| 939 | |
| 940 /* Run round first doing all local groups. */ | |
| 941 for( grp = Itl_first( newsgroups ); | |
| 942 grp != NULL; | |
| 943 grp = Itl_next( newsgroups ) ) | |
| 944 { | |
| 945 if ( Grp_local( grp ) ) | |
| 946 { | |
| 947 if ( ! oneLocal ) | |
| 948 { | |
| 949 if ( ! Post_open( DynStr_str( art ) ) ) | |
| 950 { | |
| 951 err = TRUE; | |
| 952 break; | |
| 953 } | |
| 954 else | |
| 955 oneLocal = TRUE; | |
| 956 } | |
| 957 | |
| 958 if ( ! Post_add( grp ) ) | |
| 959 err = TRUE; | |
| 960 } | |
| 961 } | |
| 962 if ( oneLocal ) | |
| 963 Post_close(); | |
| 964 | |
| 965 /* Now look for a valid external group. */ | |
| 966 for( grp = Itl_first( newsgroups ); | |
| 967 grp != NULL; | |
| 968 grp = Itl_next( newsgroups ) ) | |
| 969 { | |
| 970 if ( Grp_exists( grp ) && ! Grp_local( grp ) ) | |
| 971 { | |
| 972 if ( ! Out_add( Grp_serv( grp ), msgId, art ) ) | |
| 973 { | |
| 974 Log_err( "Cannot add posted article to outgoing directory" ); | |
| 975 err = TRUE; | |
| 976 } | |
| 977 break; | |
| 978 } | |
| 979 } | |
| 980 | |
| 981 return err; | |
| 982 } | |
| 983 | |
| 984 static Bool | |
| 985 doPost( char *arg, const Cmd *cmd ) | |
| 986 { | |
| 987 Bool err, replyToFound, dateFound, inHeader; | |
| 988 DynStr *s; | |
| 989 Str line, field, val, msgId, from; | |
| 990 const char* p; | |
| 991 ItemList * newsgroups, *control; | |
| 992 | |
| 993 /* | |
| 994 Get article and make following changes to the header: | |
| 995 - add/replace/cut Message-ID depending on config options | |
| 996 - add Reply-To with content of From, if missing | |
| 997 (some providers overwrite From field) | |
| 998 - rename X-Sender header to X-NOFFLE-X-Sender | |
| 999 (some providers want to insert their own X-Sender) | |
| 1000 | |
| 1001 For doing this, it is not necessary to parse multiple-line | |
| 1002 headers. | |
| 1003 */ | |
| 1004 putStat( STAT_SEND_ART, "Continue (end with period)" ); | |
| 1005 fflush( stdout ); | |
| 1006 Log_dbg( "[S FLUSH]" ); | |
| 1007 s = new_DynStr( 10000 ); | |
| 1008 msgId[ 0 ] = '\0'; | |
| 1009 from[ 0 ] = '\0'; | |
| 1010 newsgroups = control = NULL; | |
| 1011 replyToFound = dateFound = FALSE; | |
| 1012 inHeader = TRUE; | |
| 1013 while ( getTxtLn( line, &err ) ) | |
| 1014 { | |
| 1015 if ( inHeader ) | |
| 1016 { | |
| 1017 p = Utl_stripWhiteSpace( line ); | |
| 1018 if ( *p == '\0' ) | |
| 1019 { | |
| 1020 inHeader = FALSE; | |
| 1021 if ( from[ 0 ] == '\0' ) | |
| 1022 Log_err( "Posted message has no From field" ); | |
| 1023 if ( ! Cfg_removeMsgId() ) | |
| 1024 { | |
| 1025 if ( Cfg_replaceMsgId() ) | |
| 1026 { | |
| 1027 Prt_genMsgId( msgId, from, "NOFFLE" ); | |
| 1028 Log_dbg( "Replacing Message-ID with '%s'", msgId ); | |
| 1029 } | |
| 1030 else if ( msgId[ 0 ] == '\0' ) | |
| 1031 { | |
| 1032 Prt_genMsgId( msgId, from, "NOFFLE" ); | |
| 1033 | |
| 1034 Log_inf( "Adding missing Message-ID '%s'", msgId ); | |
| 1035 } | |
| 1036 else if ( ! Prt_isValidMsgId( msgId ) ) | |
| 1037 { | |
| 1038 Log_ntc( "Replacing invalid Message-ID with '%s'", | |
| 1039 msgId ); | |
| 1040 Prt_genMsgId( msgId, from, "NOFFLE" ); | |
| 1041 } | |
| 1042 DynStr_app( s, "Message-ID: " ); | |
| 1043 DynStr_appLn( s, msgId ); | |
| 1044 } | |
| 1045 if ( ! replyToFound && from[ 0 ] != '\0' ) | |
| 1046 { | |
| 1047 Log_dbg( "Adding Reply-To field to posted message." ); | |
| 1048 DynStr_app( s, "Reply-To: " ); | |
| 1049 DynStr_appLn( s, from ); | |
| 1050 } | |
| 1051 if ( ! dateFound ) | |
| 1052 { | |
| 1053 time_t t; | |
| 1054 | |
| 1055 time( &t ); | |
| 1056 Utl_rfc822Date( t, val ); | |
| 1057 DynStr_app( s, "Date: " ); | |
| 1058 DynStr_appLn( s, val ); | |
| 1059 } | |
| 1060 DynStr_appLn( s, p ); | |
| 1061 } | |
| 1062 else if ( Prt_getField( field, val, p ) ) | |
| 1063 { | |
| 1064 if ( strcmp( field, "message-id" ) == 0 ) | |
| 1065 strcpy( msgId, val ); | |
| 1066 else if ( strcmp( field, "from" ) == 0 ) | |
| 1067 { | |
| 1068 strcpy( from, val ); | |
| 1069 DynStr_appLn( s, p ); | |
| 1070 } | |
| 1071 else if ( strcmp( field, "newsgroups" ) == 0 ) | |
| 1072 { | |
| 1073 Utl_toLower( val ); | |
| 1074 newsgroups = new_Itl ( val, " ," ); | |
| 1075 DynStr_appLn( s, p ); | |
| 1076 } | |
| 1077 else if ( strcmp( field, "control" ) == 0 ) | |
| 1078 { | |
| 1079 control = new_Itl ( val, " " ); | |
| 1080 DynStr_appLn( s, p ); | |
| 1081 } | |
| 1082 else if ( strcmp( field, "reply-to" ) == 0 ) | |
| 1083 { | |
| 1084 replyToFound = TRUE; | |
| 1085 DynStr_appLn( s, p ); | |
| 1086 } | |
| 1087 else if ( strcmp( field, "date" ) == 0 ) | |
| 1088 { | |
| 1089 dateFound = TRUE; | |
| 1090 DynStr_appLn( s, p ); | |
| 1091 } | |
| 1092 else if ( strcmp( field, "x-sender" ) == 0 ) | |
| 1093 { | |
| 1094 DynStr_app( s, "X-NOFFLE-X-Sender: " ); | |
| 1095 DynStr_appLn( s, val ); | |
| 1096 } | |
| 1097 else | |
| 1098 DynStr_appLn( s, p ); | |
| 1099 } | |
| 1100 else | |
| 1101 DynStr_appLn( s, line ); | |
| 1102 } | |
| 1103 else | |
| 1104 DynStr_appLn( s, line ); | |
| 1105 } | |
| 1106 if ( inHeader ) | |
| 1107 Log_err( "Posted message has no body" ); | |
| 1108 if ( ! err ) | |
| 1109 { | |
| 1110 if ( newsgroups == NULL || Itl_count( newsgroups ) == 0 ) | |
| 1111 { | |
| 1112 Log_err( "Posted message has no valid Newsgroups header field" ); | |
| 1113 err = TRUE; | |
| 1114 } | |
| 1115 else | |
| 1116 { | |
| 1117 const char *grp; | |
| 1118 Bool knownGrp = FALSE; | |
| 1119 Bool postAllowedGrp = FALSE; | |
| 1120 | |
| 1121 /* Check at least one group is known. */ | |
| 1122 for( grp = Itl_first( newsgroups ); | |
| 1123 grp != NULL; | |
| 1124 grp = Itl_next( newsgroups ) ) | |
| 1125 { | |
| 1126 if ( Grp_exists( grp ) ) | |
| 1127 { | |
| 1128 knownGrp = TRUE; | |
| 1129 switch( Grp_postAllow( grp ) ) | |
| 1130 { | |
| 1131 case 'n': | |
| 1132 break; | |
| 1133 case 'm': | |
| 1134 /* Can't post to moderated local groups. */ | |
| 1135 postAllowedGrp = ! Grp_local( grp ); | |
| 1136 break; | |
| 1137 default: | |
| 1138 postAllowedGrp = TRUE; | |
| 1139 } | |
| 1140 if ( postAllowedGrp ) | |
| 1141 break; | |
| 1142 } | |
| 1143 } | |
| 1144 | |
| 1145 if ( ! knownGrp ) | |
| 1146 { | |
| 1147 | |
| 1148 Log_err( "No known group in Newsgroups header field" ); | |
| 1149 err = TRUE; | |
| 1150 } | |
| 1151 else if ( ! postAllowedGrp ) | |
| 1152 { | |
| 1153 | |
| 1154 Log_err( "No group permits posting" ); | |
| 1155 err = TRUE; | |
| 1156 } | |
| 1157 else | |
| 1158 { | |
| 1159 err = ( control == NULL ) | |
| 1160 ? postArticle( newsgroups, msgId, s ) | |
| 1161 : handleControl( control, newsgroups, msgId, s ); | |
| 1162 } | |
| 1163 } | |
| 1164 } | |
| 1165 if ( err ) | |
| 1166 putStat( STAT_POST_FAILED, "Posting failed" ); | |
| 1167 else | |
| 1168 { | |
| 1169 putStat( STAT_POST_OK, "Message posted" ); | |
| 1170 if ( Online_true() ) | |
| 1171 postArts(); | |
| 1172 } | |
| 1173 del_Itl( newsgroups ); | |
| 1174 del_Itl( control ); | |
| 1175 del_DynStr( s ); | |
| 1176 return TRUE; | |
| 1177 } | |
| 1178 | |
| 1179 static void | |
| 1180 parseRange( const char *s, int *first, int *last, int *numb ) | |
| 1181 { | |
| 1182 int r, i; | |
| 1183 char* p; | |
| 1184 Str t; | |
| 1185 | |
| 1186 Utl_cpyStr( t, s ); | |
| 1187 p = Utl_stripWhiteSpace( t ); | |
| 1188 r = sscanf( p, "%d-%d", first, last ); | |
| 1189 if ( r < 1 ) | |
| 1190 { | |
| 1191 *first = serv.artPtr; | |
| 1192 *last = serv.artPtr; | |
| 1193 } | |
| 1194 else if ( r == 1 ) | |
| 1195 { | |
| 1196 if ( p[ strlen( p ) - 1 ] == '-' ) | |
| 1197 *last = Cont_last(); | |
| 1198 else | |
| 1199 *last = *first; | |
| 1200 } | |
| 1201 if ( *first < Cont_first() ) | |
| 1202 *first = Cont_first(); | |
| 1203 if ( *last > Cont_last() ) | |
| 1204 *last = Cont_last(); | |
| 1205 if ( *first > Cont_last() || *last < Cont_first() ) | |
| 1206 *last = *first - 1; | |
| 1207 *numb = 0; | |
| 1208 for ( i = *first; i <= *last; ++i ) | |
| 1209 if ( Cont_validNumb( i ) ) | |
| 1210 ++(*numb); | |
| 1211 } | |
| 1212 | |
| 1213 static Bool | |
| 1214 doXhdr( char *arg, const Cmd *cmd ) | |
| 1215 { | |
| 1216 int first, last, i, n, numb; | |
| 1217 enum { SUBJ, FROM, DATE, MSG_ID, REF, BYTES, LINES } what; | |
| 1218 const char *p; | |
| 1219 const Over *ov; | |
| 1220 Str whatStr; | |
| 1221 | |
| 1222 if ( ! testGrpSelected() ) | |
| 1223 return TRUE; | |
| 1224 if ( sscanf( arg, "%s", whatStr ) != 1 ) | |
| 1225 { | |
| 1226 putSyntax( cmd ); | |
| 1227 return TRUE; | |
| 1228 } | |
| 1229 Utl_toLower( whatStr ); | |
| 1230 if ( strcmp( whatStr, "subject" ) == 0 ) | |
| 1231 what = SUBJ; | |
| 1232 else if ( strcmp( whatStr, "from" ) == 0 ) | |
| 1233 what = FROM; | |
| 1234 else if ( strcmp( whatStr, "date" ) == 0 ) | |
| 1235 what = DATE; | |
| 1236 else if ( strcmp( whatStr, "message-id" ) == 0 ) | |
| 1237 what = MSG_ID; | |
| 1238 else if ( strcmp( whatStr, "references" ) == 0 ) | |
| 1239 what = REF; | |
| 1240 else if ( strcmp( whatStr, "bytes" ) == 0 ) | |
| 1241 what = BYTES; | |
| 1242 else if ( strcmp( whatStr, "lines" ) == 0 ) | |
| 1243 what = LINES; | |
| 1244 else | |
| 1245 { | |
| 1246 putStat( STAT_HEAD_FOLLOWS, "Unknown header (empty list follows)" ); | |
| 1247 putEndOfTxt(); | |
| 1248 return TRUE; | |
| 1249 } | |
| 1250 p = Utl_restOfLn( arg, 1 ); | |
| 1251 parseRange( p, &first, &last, &numb ); | |
| 1252 if ( numb == 0 ) | |
| 1253 putStat( STAT_NO_ART_SELECTED, "No articles selected" ); | |
| 1254 else | |
| 1255 { | |
| 1256 putStat( STAT_HEAD_FOLLOWS, "%s header %lu-%lu", | |
| 1257 whatStr, first, last ) ; | |
| 1258 for ( i = first; i <= last; ++i ) | |
| 1259 if ( ( ov = Cont_get( i ) ) ) | |
| 1260 { | |
| 1261 n = Ov_numb( ov ); | |
| 1262 switch ( what ) | |
| 1263 { | |
| 1264 case SUBJ: | |
| 1265 putTxtLn( "%lu %s", n, Ov_subj( ov ) ); | |
| 1266 break; | |
| 1267 case FROM: | |
| 1268 putTxtLn( "%lu %s", n, Ov_from( ov ) ); | |
| 1269 break; | |
| 1270 case DATE: | |
| 1271 putTxtLn( "%lu %s", n, Ov_date( ov ) ); | |
| 1272 break; | |
| 1273 case MSG_ID: | |
| 1274 putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); | |
| 1275 break; | |
| 1276 case REF: | |
| 1277 putTxtLn( "%lu %s", n, Ov_ref( ov ) ); | |
| 1278 break; | |
| 1279 case BYTES: | |
| 1280 putTxtLn( "%lu %d", n, Ov_bytes( ov ) ); | |
| 1281 break; | |
| 1282 case LINES: | |
| 1283 putTxtLn( "%lu %d", n, Ov_lines( ov ) ); | |
| 1284 break; | |
| 1285 default: | |
| 1286 ASSERT( FALSE ); | |
| 1287 } | |
| 1288 } | |
| 1289 putEndOfTxt(); | |
| 1290 } | |
| 1291 return TRUE; | |
| 1292 } | |
| 1293 | |
| 1294 static Bool | |
| 1295 doXpat( char *arg, const Cmd *cmd ) | |
| 1296 { | |
| 1297 int first, last, i, n; | |
| 1298 enum { SUBJ, FROM, DATE, MSG_ID, REF } what; | |
| 1299 const Over *ov; | |
| 1300 Str whatStr, pat; | |
| 1301 | |
| 1302 if ( ! testGrpSelected() ) | |
| 1303 return TRUE; | |
| 1304 if ( sscanf( arg, "%s %d-%d %s", whatStr, &first, &last, pat ) != 4 ) | |
| 1305 { | |
| 1306 if ( sscanf( arg, "%s %d- %s", whatStr, &first, pat ) == 3 ) | |
| 1307 last = Cont_last(); | |
| 1308 else if ( sscanf( arg, "%s %d %s", whatStr, &first, pat ) == 3 ) | |
| 1309 last = first; | |
| 1310 else | |
| 1311 { | |
| 1312 putSyntax( cmd ); | |
| 1313 return TRUE; | |
| 1314 } | |
| 1315 } | |
| 1316 Utl_toLower( whatStr ); | |
| 1317 if ( strcmp( whatStr, "subject" ) == 0 ) | |
| 1318 what = SUBJ; | |
| 1319 else if ( strcmp( whatStr, "from" ) == 0 ) | |
| 1320 what = FROM; | |
| 1321 else if ( strcmp( whatStr, "date" ) == 0 ) | |
| 1322 what = DATE; | |
| 1323 else if ( strcmp( whatStr, "message-id" ) == 0 ) | |
| 1324 what = MSG_ID; | |
| 1325 else if ( strcmp( whatStr, "references" ) == 0 ) | |
| 1326 what = REF; | |
| 1327 else | |
| 1328 { | |
| 1329 putStat( STAT_HEAD_FOLLOWS, "invalid header (empty list follows)" ); | |
| 1330 putEndOfTxt(); | |
| 1331 return TRUE; | |
| 1332 } | |
| 1333 putStat( STAT_HEAD_FOLLOWS, "header" ) ; | |
| 1334 for ( i = first; i <= last; ++i ) | |
| 1335 if ( ( ov = Cont_get( i ) ) ) | |
| 1336 { | |
| 1337 n = Ov_numb( ov ); | |
| 1338 switch ( what ) | |
| 1339 { | |
| 1340 case SUBJ: | |
| 1341 if ( Wld_match( Ov_subj( ov ), pat ) ) | |
| 1342 putTxtLn( "%lu %s", n, Ov_subj( ov ) ); | |
| 1343 break; | |
| 1344 case FROM: | |
| 1345 if ( Wld_match( Ov_from( ov ), pat ) ) | |
| 1346 putTxtLn( "%lu %s", n, Ov_from( ov ) ); | |
| 1347 break; | |
| 1348 case DATE: | |
| 1349 if ( Wld_match( Ov_date( ov ), pat ) ) | |
| 1350 putTxtLn( "%lu %s", n, Ov_date( ov ) ); | |
| 1351 break; | |
| 1352 case MSG_ID: | |
| 1353 if ( Wld_match( Ov_msgId( ov ), pat ) ) | |
| 1354 putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); | |
| 1355 break; | |
| 1356 case REF: | |
| 1357 if ( Wld_match( Ov_ref( ov ), pat ) ) | |
| 1358 putTxtLn( "%lu %s", n, Ov_ref( ov ) ); | |
| 1359 break; | |
| 1360 default: | |
| 1361 ASSERT( FALSE ); | |
| 1362 } | |
| 1363 } | |
| 1364 putEndOfTxt(); | |
| 1365 return TRUE; | |
| 1366 } | |
| 1367 | |
| 1368 static Bool | |
| 1369 doSlave( char *arg, const Cmd *cmd ) | |
| 1370 { | |
| 1371 putStat( STAT_CMD_OK, "Ok" ); | |
| 1372 return TRUE; | |
| 1373 } | |
| 1374 | |
| 1375 static Bool | |
| 1376 doStat( char *arg, const Cmd *cmd ) | |
| 1377 { | |
| 1378 const char *msgId; | |
| 1379 int numb; | |
| 1380 | |
| 1381 if ( ! whichId( &msgId, &numb, arg ) ) | |
| 1382 return TRUE; | |
| 1383 if ( numb > 0 ) | |
| 1384 putStat( STAT_ART_RETRIEVED, "%ld %s selected", | |
| 1385 numb, msgId ); | |
| 1386 else | |
| 1387 putStat( STAT_ART_RETRIEVED, "0 %s selected", msgId ); | |
| 1388 return TRUE; | |
| 1389 } | |
| 1390 | |
| 1391 static Bool | |
| 1392 doQuit( char *arg, const Cmd *cmd ) | |
| 1393 { | |
| 1394 putStat( STAT_GOODBYE, "Goodbye" ); | |
| 1395 return FALSE; | |
| 1396 } | |
| 1397 | |
| 1398 static Bool | |
| 1399 doXOver( char *arg, const Cmd *cmd ) | |
| 1400 { | |
| 1401 int first, last, i, n; | |
| 1402 const Over *ov; | |
| 1403 | |
| 1404 if ( ! testGrpSelected() ) | |
| 1405 return TRUE; | |
| 1406 parseRange( arg, &first, &last, &n ); | |
| 1407 if ( n == 0 ) | |
| 1408 putStat( STAT_NO_ART_SELECTED, "No articles selected" ); | |
| 1409 else | |
| 1410 { | |
| 1411 putStat( STAT_OVERS_FOLLOW, "Overview %ld-%ld", first, last ); | |
| 1412 for ( i = first; i <= last; ++i ) | |
| 1413 if ( ( ov = Cont_get( i ) ) ) | |
| 1414 putTxtLn( "%lu\t%s\t%s\t%s\t%s\t%s\t%d\t%d\t", | |
| 1415 Ov_numb( ov ), Ov_subj( ov ), Ov_from( ov ), | |
| 1416 Ov_date( ov ), Ov_msgId( ov ), Ov_ref( ov ), | |
| 1417 Ov_bytes( ov ), Ov_lines( ov ) ); | |
| 1418 putEndOfTxt(); | |
| 1419 } | |
| 1420 return TRUE; | |
| 1421 } | |
| 1422 | |
| 1423 static void | |
| 1424 putFatal( const char *fmt, ... ) | |
| 1425 { | |
| 1426 va_list ap; | |
| 1427 Str s; | |
| 1428 | |
| 1429 va_start( ap, fmt ); | |
| 1430 vsnprintf( s, MAXCHAR, fmt, ap ); | |
| 1431 va_end( ap ); | |
| 1432 Log_err( s ); | |
| 1433 putStat( STAT_PROGRAM_FAULT, "%s", s ); | |
| 1434 fflush( stdout ); | |
| 1435 Log_dbg( "[S FLUSH]" ); | |
| 1436 } | |
| 1437 | |
| 1438 /* Parse line, execute command and return FALSE, if it was the quit command. */ | |
| 1439 static Bool | |
| 1440 parseAndExecute( Str line ) | |
| 1441 { | |
| 1442 unsigned int i, n; | |
| 1443 Cmd *c; | |
| 1444 Str s, arg; | |
| 1445 Bool ret; | |
| 1446 | |
| 1447 if ( sscanf( line, "%s", s ) == 1 ) | |
| 1448 { | |
| 1449 Utl_toLower( s ); | |
| 1450 strcpy( arg, Utl_restOfLn( line, 1 ) ); | |
| 1451 n = sizeof( commands ) / sizeof( commands[ 0 ] ); | |
| 1452 for ( i = 0, c = commands; i < n; ++i, ++c ) | |
| 1453 if ( strcmp( c->name, s ) == 0 ) | |
| 1454 { | |
| 1455 ret = c->cmdProc( Utl_stripWhiteSpace( arg ), c ); | |
| 1456 fflush( stdout ); | |
| 1457 Log_dbg( "[S FLUSH]" ); | |
| 1458 return ret; | |
| 1459 } | |
| 1460 } | |
| 1461 putStat( STAT_NO_SUCH_CMD, "Command not recognized" ); | |
| 1462 fflush( stdout ); | |
| 1463 Log_dbg( "[S FLUSH]" ); | |
| 1464 return TRUE; | |
| 1465 } | |
| 1466 | |
| 1467 static void | |
| 1468 putWelcome( void ) | |
| 1469 { | |
| 1470 putStat( STAT_READY_POST_ALLOW, "NNTP server NOFFLE %s", | |
| 1471 Cfg_version() ); | |
| 1472 fflush( stdout ); | |
| 1473 Log_dbg( "[S FLUSH]" ); | |
| 1474 } | |
| 1475 | |
| 1476 static Bool | |
| 1477 initServ( void ) | |
| 1478 { | |
| 1479 ASSERT( ! serv.running ); | |
| 1480 if ( ! Lock_openDatabases() ) | |
| 1481 return FALSE; | |
| 1482 serv.running = TRUE; | |
| 1483 return TRUE; | |
| 1484 } | |
| 1485 | |
| 1486 static void | |
| 1487 closeServ( void ) | |
| 1488 { | |
| 1489 ASSERT( serv.running ); | |
| 1490 serv.running = FALSE; | |
| 1491 Lock_closeDatabases(); | |
| 1492 } | |
| 1493 | |
| 1494 void | |
| 1495 Serv_run( void ) | |
| 1496 { | |
| 1497 Bool done; | |
| 1498 int r; | |
| 1499 Str line; | |
| 1500 struct timeval timeOut; | |
| 1501 fd_set readSet; | |
| 1502 | |
| 1503 putWelcome(); | |
| 1504 done = FALSE; | |
| 1505 while ( ! done ) | |
| 1506 { | |
| 1507 FD_ZERO( &readSet ); | |
| 1508 FD_SET( STDIN_FILENO, &readSet ); | |
| 1509 /* Never hold lock more than 5 seconds (empirically good value, | |
| 1510 avoids to close/open databases, if clients sends several | |
| 1511 commands, but releases the lock often enough, for allowing | |
| 1512 multiple persons to read news at the same time) */ | |
| 1513 timeOut.tv_sec = 5; | |
| 1514 timeOut.tv_usec = 0; | |
| 1515 r = select( STDIN_FILENO + 1, &readSet, NULL, NULL, &timeOut ); | |
| 1516 if ( r < 0 ) | |
| 1517 done = TRUE; | |
| 1518 else if ( r == 0 ) | |
| 1519 { | |
| 1520 if ( serv.running ) | |
| 1521 closeServ(); | |
| 1522 } | |
| 1523 else /* ( r > 0 ) */ | |
| 1524 { | |
| 1525 if ( ! serv.running ) | |
| 1526 { | |
| 1527 if ( ! initServ() ) | |
| 1528 { | |
| 1529 putFatal( "Cannot init server" ); | |
| 1530 done = TRUE; | |
| 1531 } | |
| 1532 } | |
| 1533 if ( ! getLn( line ) ) | |
| 1534 { | |
| 1535 Log_inf( "Client disconnected. Terminating." ); | |
| 1536 done = TRUE; | |
| 1537 } | |
| 1538 else if ( ! parseAndExecute( line ) ) | |
| 1539 done = TRUE; | |
| 1540 } | |
| 1541 } | |
| 1542 if ( serv.running ) | |
| 1543 closeServ(); | |
| 1544 } |
