comparison src/post.c @ 88:1fcdced0246e noffle

[svn] Move posting code to post.c, add command line posting
author bears
date Thu, 18 May 2000 13:17:23 +0100
parents 7250be163ec4
children eb522db0d032
comparison
equal deleted inserted replaced
87:bf8c97460fd7 88:1fcdced0246e
1 /* 1 /*
2 post.c 2 post.c
3 3
4 $Id: post.c 70 2000-05-12 17:35:00Z enz $ 4 $Id: post.c 100 2000-05-18 12:17:23Z bears $
5 */ 5 */
6 6
7 #if HAVE_CONFIG_H 7 #if HAVE_CONFIG_H
8 #include <config.h> 8 #include <config.h>
9 #endif 9 #endif
10 10
11 #include <stdio.h> 11 #include <stdio.h>
12 #include "post.h" 12 #include "post.h"
13 #include <string.h> 13 #include <string.h>
14 #include "common.h" 14 #include "common.h"
15 #include "configfile.h"
15 #include "content.h" 16 #include "content.h"
17 #include "control.h"
16 #include "database.h" 18 #include "database.h"
17 #include "group.h" 19 #include "group.h"
20 #include "itemlist.h"
18 #include "log.h" 21 #include "log.h"
22 #include "outgoing.h"
19 #include "over.h" 23 #include "over.h"
20 #include "protocol.h" 24 #include "protocol.h"
21 #include "util.h" 25 #include "util.h"
22 #include "portable.h" 26 #include "portable.h"
23 27
32 size_t lines; 36 size_t lines;
33 }; 37 };
34 38
35 struct Article 39 struct Article
36 { 40 {
37 const char * text; 41 DynStr *text; /* Processed article text */
38 Bool posted; 42 ItemList *newsgroups; /* Newsgroups for dispatch */
43 ItemList *control; /* Control message? NULL if not */
44 Bool posted; /* Has it been put in the article database? */
45 const char *server; /* Server for external post */
39 struct OverInfo over; 46 struct OverInfo over;
40 }; 47 };
41 48
42 static struct Article article = { NULL, FALSE, { "", "", "", "", "", 0, 0 } }; 49 static struct Article article = { NULL, NULL, NULL, FALSE, NULL,
43 50 { "", "", "", "", "", 0, 0 } };
44 static void
45 getOverInfo( struct OverInfo * o )
46 {
47 const char *p = article.text;
48 Str line, field, value;
49
50 o->bytes = strlen( p );
51
52 while( p != NULL )
53 {
54 p = Utl_getHeaderLn( line, p );
55 if ( line[ 0 ] == '\0' )
56 break;
57
58 /* Look for headers we need to stash. */
59 if ( Prt_getField( field, value, line ) )
60 {
61 if ( strcmp( field, "subject" ) == 0 )
62 Utl_cpyStr( o->subject, value );
63 else if ( strcmp ( field, "from" ) == 0 )
64 Utl_cpyStr( o->from, value );
65 else if ( strcmp ( field, "date" ) == 0 )
66 Utl_cpyStr( o->date, value );
67 else if ( strcmp ( field, "references" ) == 0 )
68 Utl_cpyStr( o->ref, value );
69 else if ( strcmp ( field, "message-id" ) == 0 )
70 Utl_cpyStr( o->msgId, value );
71 }
72 }
73
74 /* Move to start of body and count lines. */
75 for ( p++, o->lines = 0; *p != '\0'; p++ )
76 if ( *p == '\n' )
77 o->lines++;
78 }
79
80 /* Register an article for posting. */
81 Bool
82 Post_open( const char * text )
83 {
84 if ( article.text != NULL )
85 {
86 Log_err( "Busy article in Post_open." );
87 return FALSE;
88 }
89
90 memset( &article.over, 0, sizeof( article.over ) );
91 article.text = text;
92 getOverInfo( &article.over );
93
94 if ( Db_contains( article.over.msgId ) )
95 {
96 Log_err( "Duplicate article %s.", article.over.msgId );
97 return FALSE;
98 }
99
100 return TRUE;
101 }
102
103 51
104 /* Add the article to a group. */ 52 /* Add the article to a group. */
105 Bool 53 static Bool
106 Post_add ( const char * grp ) 54 addToGroup( const char * grp )
107 { 55 {
108 Over * over; 56 Over * over;
109 const char *msgId; 57 const char *msgId;
110 58
111 over = new_Over( article.over.subject, 59 over = new_Over( article.over.subject,
112 article.over.from, 60 article.over.from,
113 article.over.date, 61 article.over.date,
114 article.over.msgId, 62 article.over.msgId,
115 article.over.ref, 63 article.over.ref,
124 72
125 if ( !article.posted ) 73 if ( !article.posted )
126 { 74 {
127 Log_inf( "Added '%s' to database.", msgId ); 75 Log_inf( "Added '%s' to database.", msgId );
128 if ( ! Db_prepareEntry( over, Cont_grp(), Cont_last() ) 76 if ( ! Db_prepareEntry( over, Cont_grp(), Cont_last() )
129 || ! Db_storeArt ( msgId, article.text ) ) 77 || ! Db_storeArt ( msgId, DynStr_str( article.text ) ) )
130 return FALSE; 78 return FALSE;
131 article.posted = TRUE; 79 article.posted = TRUE;
132 } 80 }
133 else 81 else
134 { 82 {
143 91
144 Cont_write(); 92 Cont_write();
145 Grp_setFirstLast( Cont_grp(), Cont_first(), Cont_last() ); 93 Grp_setFirstLast( Cont_grp(), Cont_first(), Cont_last() );
146 return TRUE; 94 return TRUE;
147 } 95 }
96
97 static Bool
98 checkPostableNewsgroup( Bool localOnly )
99 {
100 const char *grp;
101 Bool knownGrp = FALSE;
102 Bool postAllowedGrp = FALSE;
103 Bool local;
104
105 /*
106 Check at least one group is known. Look for external group and
107 set server.
108 */
109 article.server = NULL;
110 for( grp = Itl_first( article.newsgroups );
111 grp != NULL;
112 grp = Itl_next( article.newsgroups ) )
113 {
114 if ( Grp_exists( grp ) )
115 {
116 local = Grp_local( grp );
117 if ( localOnly && ! local )
118 continue;
119 knownGrp = TRUE;
120 switch( Grp_postAllow( grp ) )
121 {
122 case 'n':
123 if ( localOnly )
124 postAllowedGrp = TRUE;
125 break;
126 case 'y':
127 postAllowedGrp = TRUE;
128 break;
129 default:
130 if ( localOnly )
131 postAllowedGrp = TRUE;
132 else
133 /* Can't post to moderated local groups. */
134 postAllowedGrp = ! local;
135 break;
136 }
137 if ( postAllowedGrp && ! local && article.server == NULL )
138 article.server = Grp_server( grp );
139 if ( postAllowedGrp && article.server != NULL )
140 break;
141 }
142 }
143
144 if ( ! knownGrp )
145 {
146 Log_err( "No known group in Newsgroups header field" );
147 return FALSE;
148 }
149 else if ( ! postAllowedGrp )
150 {
151 Log_err( "No group permits posting" );
152 return FALSE;
153 }
154
155 return TRUE;
156 }
157
158 /* Get article text, check for validity & build overview. */
159 static Bool
160 getArticleText( const char *p )
161 {
162 DynStr * s;
163 Str line, field, value;
164 Bool replyToFound;
165
166 s = new_DynStr( 10000 );
167 article.text = s;
168
169 memset( &article.over, 0, sizeof( article.over ) );
170 replyToFound = FALSE;
171
172 /* Grab header lines first, getting overview info as we go. */
173 while ( ( p = Utl_getHeaderLn( line, p ) ) != NULL
174 && line[ 0 ] != '\0'
175 && Prt_getField( field, value, line ) )
176 {
177 /* Look for headers we need to stash. */
178 if ( strcmp( field, "subject" ) == 0 )
179 {
180 Utl_cpyStr( article.over.subject, value );
181 DynStr_appLn( s, line );
182 }
183 else if ( strcmp ( field, "from" ) == 0 )
184 {
185 Utl_cpyStr( article.over.from, value );
186 DynStr_appLn( s, line );
187 }
188 else if ( strcmp ( field, "date" ) == 0 )
189 {
190 Utl_cpyStr( article.over.date, value );
191 DynStr_appLn( s, line );
192 }
193 else if ( strcmp ( field, "references" ) == 0 )
194 {
195 Utl_cpyStr( article.over.ref, value );
196 DynStr_appLn( s, line );
197 }
198 else if ( strcmp ( field, "message-id" ) == 0 )
199 Utl_cpyStr( article.over.msgId, value );
200 else if ( strcmp ( field, "newsgroups" ) == 0 )
201 {
202 article.newsgroups = new_Itl( value, " ,\n\t" );
203 DynStr_appLn( s, line );
204 }
205 else if ( strcmp ( field, "control" ) == 0 )
206 {
207 article.control = new_Itl( value, " " );
208 DynStr_appLn( s, line );
209 }
210 else if ( strcmp ( field, "reply-to" ) == 0 )
211 {
212 replyToFound = TRUE;
213 DynStr_appLn( s, line );
214 }
215 else if ( strcmp ( field, "x-sender" ) == 0 )
216 {
217 DynStr_app( s, "X-NOFFLE-X-Sender: " );
218 DynStr_appLn( s, value );
219 }
220 else if ( strcmp ( field, "xref" ) == 0 )
221 Log_inf( "Xref header in post ignored" );
222 else
223 DynStr_appLn( s, line );
224 }
225
226 /* Now sort header-related issues */
227 if ( article.over.from[ 0 ] == '\0' )
228 {
229 Log_err( "Posted message has no From field" );
230 return FALSE;
231 }
232 if ( article.over.subject[ 0 ] == '\0' )
233 {
234 Log_err( "Posted message has no Subject field" );
235 return FALSE;
236 }
237 if ( article.newsgroups == NULL || Itl_count( article.newsgroups) == 0 )
238 {
239 Log_err( "Posted message has no valid Newsgroups field" );
240 return FALSE;
241 }
242 if ( Cfg_replaceMsgId() )
243 {
244 Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" );
245 Log_dbg( "Replacing Message-ID with '%s'", article.over.msgId );
246 }
247 else if ( article.over.msgId[ 0 ] == '\0' )
248 {
249 Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" );
250 Log_inf( "Adding missing Message-ID '%s'", article.over.msgId );
251 }
252 else if ( ! Prt_isValidMsgId( article.over.msgId ) )
253 {
254 Log_ntc( "Replacing invalid Message-ID '%s'", article.over.msgId );
255 Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" );
256 }
257 DynStr_app( s, "Message-ID: " );
258 DynStr_appLn( s, article.over.msgId );
259 if ( ! replyToFound )
260 {
261 Log_dbg( "Adding Reply-To field to posted message." );
262 DynStr_app( s, "Reply-To: " );
263 DynStr_appLn( s, article.over.from );
264 }
265 if ( article.over.date[ 0 ] == '\0' )
266 {
267 time_t t;
268
269 time( &t );
270 Utl_rfc822Date( t, article.over.date );
271 DynStr_app( s, "Date: " );
272 DynStr_appLn( s, article.over.date );
273 }
274 if ( p == NULL || p[ 0 ] == '\0' )
275 {
276 Log_err( "Posted message has no body" );
277 return FALSE;
278 }
279
280 /* Add the empty line separating header and body */
281 DynStr_appLn( s, "" );
282
283 /* Now pop on the rest of the body and count the lines & bytes */
284 DynStr_app( s, p );
285 for ( p++, article.over.lines = 0; *p != '\0'; p++ )
286 if ( *p == '\n' )
287 article.over.lines++;
288 article.over.bytes = DynStr_len( s );
289
290 return TRUE;
291 }
292
293 /* Add article to outgoing if needs be */
294 static Bool
295 postExternal( void )
296 {
297 if ( article.server == NULL )
298 return TRUE;
299
300 if ( ! Out_add( article.server, article.over.msgId, article.text ) )
301 {
302 Log_err( "Cannot add posted article to outgoing directory" );
303 return FALSE;
304 }
305
306 return TRUE;
307 }
308
309 /* Cancel and return TRUE if need to send cancel message on to server. */
310 static Bool
311 controlCancel( const char *cancelId )
312 {
313 return ( Ctrl_cancel( cancelId ) == CANCEL_NEEDS_MSG );
314 }
315
316 /*
317 It's a control message. Currently we only know about 'cancel'
318 messages; others are passed on for outside groups, and logged
319 as ignored for local groups.
320 */
321 static Bool
322 handleControl( void )
323 {
324 const char *grp;
325 const char *op;
326
327 op = Itl_first( article.control );
328 if ( op == NULL )
329 {
330 Log_err( "Malformed control line." );
331 return TRUE;
332 }
333 else if ( strcasecmp( op, "cancel" ) == 0 )
334 {
335 if ( ! controlCancel( Itl_next( article.control ) ) )
336 return TRUE; /* Handled entirely locally */
337 }
338 else
339 {
340 /* Log 'can't do' for internal groups. */
341 for( grp = Itl_first( article.newsgroups );
342 grp != NULL;
343 grp = Itl_next( article.newsgroups ) )
344 {
345 if ( Grp_exists( grp ) && Grp_local( grp ) )
346 Log_inf( "Ignoring control '%s' for '%s'.", op, grp );
347 }
348 }
349
350 return postExternal();
351 }
352
353 static Bool
354 postArticle( Bool localOnly )
355 {
356 const char *grp;
357 Bool err;
358 Bool postLocal;
359 Bool local;
360
361 err = FALSE;
362 postLocal = Cfg_postLocal();
363
364 /* Run round & post locally */
365 for( grp = Itl_first( article.newsgroups );
366 grp != NULL;
367 grp = Itl_next( article.newsgroups ) )
368 {
369 local = Grp_local( grp );
370 if ( localOnly && ! local )
371 continue;
372 if ( ( local || postLocal )
373 && ( Grp_postAllow( grp ) == 'y' || localOnly ) )
374 err = addToGroup( grp ) && err;
375 }
376
377 if ( localOnly )
378 return err;
379 else
380 return postExternal() && err;
381 }
382
383 /* Register an article for posting. */
384 Bool
385 Post_open( const char * text )
386 {
387 if ( article.text != NULL )
388 {
389 Log_err( "Busy article in Post_open." );
390 return FALSE;
391 }
392
393 if ( ! getArticleText( text ) )
394 return FALSE;
395
396 if ( Db_contains( article.over.msgId ) )
397 {
398 Log_err( "Duplicate article %s.", article.over.msgId );
399 return FALSE;
400 }
401
402 return TRUE;
403 }
404
405 /* Process the posting */
406 Bool
407 Post_post( Bool localOnly )
408 {
409 if ( ! checkPostableNewsgroup( localOnly ) )
410 return FALSE;
411
412 return ( article.control == NULL )
413 ? ! postArticle( localOnly )
414 : ! handleControl();
415 }
148 416
149 /* Done with article - tidy up. */ 417 /* Done with article - tidy up. */
150 void 418 void
151 Post_close( void ) 419 Post_close( void )
152 { 420 {
153 article.text = NULL; 421 if ( article.text != NULL )
422 {
423 del_DynStr( article.text );
424 article.text = NULL;
425 }
426 if ( article.newsgroups != NULL )
427 {
428 del_Itl( article.newsgroups );
429 article.newsgroups = NULL;
430 }
431 if ( article.control != NULL )
432 {
433 del_Itl( article.control );
434 article.control = NULL;
435 }
154 article.posted = FALSE; 436 article.posted = FALSE;
155 } 437 article.server = NULL;
156 438 }
439