nitlog: oops, didn't deal properly with posts in months that haven't
[wvapps.git] / wvdial / wvdialbrain.cc
blobc96ab64522b4f28e6f90c7d07b4ca993d9b11940
1 /*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2003 Net Integration Technologies, Inc.
5 * The brains behind the WvDialer class. This handles prompt-detection,
6 * menu-guessing, and related tasks.
8 */
10 #include "wvdialbrain.h"
11 #include "wvdialer.h"
13 #include <ctype.h>
14 #include <string.h>
15 #include <time.h>
17 static char rbrackets[] = ".-:'\">)]}";
18 static char lbrackets[] = ".-:'\"<([{";
19 static char brackets[] = ".-:'\"<>()[]{}";
20 static char menu_bad_words[] = "press push hit enter type key";
23 //**************************************************
24 // WvDialBrain Public Functions
25 //**************************************************
27 WvDialBrain::WvDialBrain( WvDialer * a_dialer )
28 /*********************************************/
29 : dialer( a_dialer )
31 saw_first_compuserve_prompt = 0;
32 reset();
35 WvDialBrain::~WvDialBrain()
36 /*************************/
40 void WvDialBrain::reset()
41 /***********************/
43 sent_login = 0;
44 prompt_tries = 0;
45 prompt_response = "";
48 const char * WvDialBrain::guess_menu( char * buf )
49 /************************************************/
50 // Searches buf for signs of intelligence, and tries to guess how to
51 // start PPP on the remote side. Good for terminal servers with menus,
52 // prompts, and (hopefully) whatever else.
53 // Assumes buf is lowercase already, and has all its nulls turned into
54 // whitespace.
56 char * cptr = NULL;
57 char * line;
58 char * nextline;
59 BrainToken * tok;
61 while( strstr( buf, "ppp" ) != NULL ) {
62 cptr = strstr( buf, "ppp" );
63 // pick out the beginning of the line containing "ppp"
64 for( line=cptr; line >= buf && !isnewline( *line ); line-- )
66 line++; // too far!
68 // find the beginning of the next line in the buffer, or the
69 // end of the buffer... so we know where to stop our searches.
70 for( nextline = line; *nextline && !isnewline( *nextline ); nextline++ )
73 buf = nextline; // now 'continue' will check the next line
75 // Now tokenize the line and perform an IntelliSearch (tm)
76 tok = tokenize( line, nextline );
77 if( tok ) {
78 guess_menu_guts( tok ); // may call set_prompt_response()
79 token_list_done( tok );
82 if( cptr )
83 return( cptr + 4 ); // return pointer directly AFTER "ppp".
84 else
85 return( NULL );
88 const char * WvDialBrain::check_prompt( const char * buffer )
89 /***********************************************************/
91 WvString tprompt;
93 // If we've been here too many times, or too long ago, just give up and
94 // start pppd.
95 if( prompt_tries >= 5 || time( NULL ) - dialer->last_rx >= 10 ) {
96 dialer->log( WvLog::Notice, "Don't know what to do! "
97 "Starting pppd and hoping for the best.\n" );
98 dialer->start_ppp();
100 } else if( dialer->options.compuserve && is_compuserve_prompt( buffer )) {
101 // We have a login prompt, so send a suitable response.
102 const char * send_this = "CIS";
103 dialer->log( "Looks like a Compuserve host name prompt.\n"
104 "Sending: %s\n", send_this );
105 saw_first_compuserve_prompt++;
106 saw_first_compuserve_prompt++;
107 dialer->reset_offset();
108 prompt_tries++;
109 return( send_this );
111 } else if( dialer->options.compuserve
112 && !saw_first_compuserve_prompt && is_login_prompt ( buffer )) {
113 // We have a login prompt, so send a suitable response.
114 const char * send_this = "cisv1";
115 dialer->log( "Looks like a Compuserve New login prompt.\n"
116 "Sending: %s\n", send_this );
117 dialer->reset_offset();
118 prompt_tries++;
119 saw_first_compuserve_prompt++;
120 return( send_this );
122 } else if( is_login_prompt( buffer ) ) {
123 // We have a login prompt, so send a suitable response.
124 WvString login = dialer->options.login;
125 if (dialer->options.compuserve &&
126 strstr (dialer->options.login, "/noint") == 0) {
127 login = WvString("%s%s", login, "/noint/go:pppconnect");
129 dialer->log( "Looks like a login prompt.\n"
130 "Sending: %s\n", login );
131 dialer->reset_offset();
132 prompt_tries++;
133 return( login );
135 } else if( is_password_prompt( buffer ) ) {
136 const char *passwd = 0;
137 if (dialer->options.compuserve && saw_first_compuserve_prompt == 1) {
138 passwd = "classic";
139 dialer->log( "Looks like a Compuserve classic password prompt.\nSending: classic\n" );
140 saw_first_compuserve_prompt++;
141 } else {
142 // We have a password prompt, so send a suitable resonse.
143 dialer->log( "Looks like a password prompt.\nSending: (password)\n" );
145 dialer->reset_offset();
146 prompt_tries++;
147 sent_login = 1; // yes, we've sent a password:
148 // assume we've sent username too.
149 if (!passwd) passwd = dialer->options.password;
150 return( passwd );
152 } else if( is_welcome_msg( buffer ) ) {
153 dialer->log( "Looks like a welcome message.\n" );
154 dialer->start_ppp();
156 } else if( is_prompt( buffer ) ) {
157 // We have some other prompt.
158 if( dialer->is_pending() ) {
159 return( NULL ); // figure it out next time
162 if( !prompt_response[0] )
163 prompt_response = dialer->options.default_reply; // wild guess
165 dialer->log( "Hmm... a prompt. Sending \"%s\".\n",
166 prompt_response );
167 dialer->reset_offset();
168 prompt_tries++;
170 // only use our prompt guess the first time.
171 // if it fails, go back to the default reply.
172 tprompt = prompt_response;
173 prompt_response = "";
175 return( tprompt );
177 } else {
178 // not a prompt at all!
179 if( dialer->is_pending() )
180 return( NULL ); // figure it out next time
183 // If we get here, then we aren't at a prompt.
184 return( NULL );
190 //**************************************************
191 // WvDialBrain Private Functions
192 //**************************************************
194 bool WvDialBrain::is_prompt( const char * c,
195 const char * promptstring,
196 bool dots_wild )
197 /***************************************************/
198 // Searches the buffer 'c' for a prompt. If no promptstring is given, we
199 // return true for ANY prompt. If a prompt string is given, then we are
200 // looking for a SPECIFIC prompt that contains that string.
202 const char * cptr;
203 static const char * prompt_punct = ")>}]:.|-?$%=\x11";
205 // if no promptstring was given, the search is simple: it is a
206 // prompt if the last line ends in punctuation and no newline.
207 if( !promptstring ) {
208 for( cptr = c + strlen( c ) - 1; cptr >= c; cptr-- ) {
209 if( isnewline( *cptr ) ) {
210 if ( !prompt_response[0] )
211 return( false ); // last line was empty: not a prompt
212 else
213 continue; // we have a guess, so use it anyway, IF the
214 // last non-blank line was a prompt.
216 if( strchr( prompt_punct, *cptr ) )
217 return( true ); // first non-whitespace was punctuation! good.
218 if ( !isspace( *cptr ) )
219 return( false ); // not punctuation or whitespace at end
221 return( false );
224 // seek backwards from the end of the buffer to the beginning of
225 // the last line, or the beginning of the buffer, whichever comes first.
226 // Then skip leading whitespace so that when we are done, (c) points at
227 // the first non-white character on the line.
228 for( cptr = c + strlen( c ) - 1; cptr >= c; cptr-- ) {
229 if( isnewline( *cptr ) )
230 break;
232 c = cptr > c ? cptr : c;
233 while( isspace( *c ) )
234 c++;
236 // find the promptstring in the buffer.
237 if( dots_wild == false ) {
238 // a bit of an obscure case, but: find the _last_ occurrence on the
239 // prompt line, not the first.
240 c = strstr( c, promptstring );
241 if( !c )
242 return( false );
243 else while( strstr( c+1, promptstring ) )
244 c = strstr( c+1, promptstring );
245 } else { // dots are wild, like in regular expressions
246 const char * p = c;
247 unsigned int char_count = 0;
248 while( *p != '\0' && char_count != strlen( promptstring ) ) {
249 char tmp = promptstring[ char_count ];
250 if( tmp == '.' || *p == tmp ) {
251 if( char_count == 0 ) {
252 // If we match the beginning of the promptstring,
253 // set c to point at it.
254 c = p;
256 char_count++;
257 } else {
258 // Start over, since a letter did not match and was not a dot.
259 char_count = 0;
261 p++;
264 // If we hit the end of the promptstring, and char_count does not
265 // equal its length, then it was not there.
266 if( char_count != strlen( promptstring ) )
267 return( false );
270 // now make sure everything after "promptstring" is whitespace
271 // and there is at least one punctuation mark.
272 bool foundpunct = false;
273 for( c += strlen( promptstring ); *c; c++ ) {
274 if( strchr( prompt_punct, *c ) )
275 foundpunct = true;
276 else if( !isspace( *c ) )
277 return false; // non-whitespace or punctuation: not a prompt
279 return( foundpunct ); // found a prompt if the string was followed by punct
282 bool WvDialBrain::is_login_prompt( const char * buf )
283 /***************************************************/
285 return( is_prompt( buf, "login" ) ||
286 is_prompt( buf, "name" ) ||
287 is_prompt( buf, "user" ) ||
288 is_prompt( buf, "id" ) ||
289 is_prompt( buf, "userid" ) ||
290 is_prompt( buf, "user.id", true ) ||
291 is_prompt( buf, "signon" ) ||
292 is_prompt( buf, "sign.on", true ) ||
293 is_prompt( buf, "usuario", false ) ||
294 ( dialer->options.login_prompt[0] &&
295 is_prompt( buf, dialer->options.login_prompt ) ) );
298 bool WvDialBrain::is_compuserve_prompt( const char * buf )
299 /***************************************************/
301 return( is_prompt( buf, "host name" ));
304 bool WvDialBrain::is_password_prompt( const char * buf )
305 /******************************************************/
307 return( is_prompt( buf, "password" ) ||
308 ( dialer->options.pass_prompt[0] &&
309 is_prompt( buf, dialer->options.pass_prompt ) ) );
312 bool WvDialBrain::is_welcome_msg( const char * buf )
313 /**************************************************/
314 // Thanks to dsb for this one, 3/10/98.
316 return( sent_login && ( strstr( buf, "mtu" ) ||
317 strstr( buf, "ip address is" ) ) );
320 BrainToken * WvDialBrain::tokenize( char * left, char * right )
321 /*************************************************************/
323 BrainToken * token_list = NULL;
324 BrainToken * new_token = NULL;
325 BrainToken * prev_token = NULL;
326 char * p;
328 if( left == NULL || right == NULL || right <= left )
329 return( NULL );
331 p = left;
332 while( p <= right ) {
333 // If *p is a null or a new-line, we are done.
334 if( *p == '\0' || isnewline( *p ) )
335 break;
337 // Skip whitespace in the string.
338 if( isspace( *p ) ) {
339 p++;
340 continue;
343 // If it's a letter, we've got the beginning of a word.
344 if( isalpha( *p ) ) {
345 char * end = p+1;
346 new_token = new BrainToken;
347 new_token->type = TOK_WORD;
348 new_token->next = NULL;
349 if( token_list == NULL )
350 token_list = new_token;
351 else
352 prev_token->next = new_token;
353 while( end <= right && isalpha( *end ) )
354 end++;
355 new_token->tok_str = new char[ end - p + 1 ];
356 strncpy( new_token->tok_str, p, end - p );
357 new_token->tok_str[ end-p ] = '\0';
358 p = end; // skip to the end of the word for next time
359 prev_token = new_token;
360 continue;
363 // If it's a digit, we've got the beginning of a number.
364 if( isdigit( *p ) ) {
365 char * end = p+1;
366 new_token = new BrainToken;
367 new_token->type = TOK_NUMBER;
368 new_token->next = NULL;
369 if( token_list == NULL )
370 token_list = new_token;
371 else
372 prev_token->next = new_token;
373 while( end <= right && isdigit( *end ) )
374 end++;
375 new_token->tok_str = new char[ end - p + 1 ];
376 strncpy( new_token->tok_str, p, end - p );
377 new_token->tok_str[ end-p ] = '\0';
378 p = end; // skip to the end of the number for next time
379 prev_token = new_token;
380 continue;
383 // If it's useful punctuation (brackets and such), grab it.
384 if( strchr( brackets, *p ) ) {
385 new_token = new BrainToken;
386 new_token->type = TOK_PUNCT;
387 new_token->next = NULL;
388 if( token_list == NULL )
389 token_list = new_token;
390 else
391 prev_token->next = new_token;
392 new_token->tok_char = *p;
393 p++;
394 prev_token = new_token;
395 continue;
398 // If it's anything else, ignore it.
399 p++;
402 return( token_list );
405 void WvDialBrain::token_list_done( BrainToken * token_list )
406 /**********************************************************/
408 BrainToken * next_token;
410 while( token_list != NULL ) {
411 next_token = token_list->next;
412 if( token_list->type == TOK_WORD || token_list->type == TOK_NUMBER )
413 delete token_list->tok_str;
414 delete token_list;
415 token_list = next_token;
419 void WvDialBrain::guess_menu_guts( BrainToken * token_list )
420 /**********************************************************/
421 // There are some cases which may occur in a valid menu line.
422 // Number 1 is of the form "P for PPP"
423 // Number 2 is of the form "1 - start PPP"
424 // Number 3 is of the form "(1) start PPP"
425 // Number 4 has the form "1 blah blah PPP". The first non-whitespace character
426 // is a number, followed by more whitespace. We have to be paranoid here
427 // to avoid seeing phone numbers and such.
428 // We check for these cases in order.
430 // Okay, fine. This function now uses goto for no good reason. So shoot me.
431 // At least it doesn't randomly return from the middle of the function before
432 // trying all the cases...
434 BrainToken * lmarker = NULL;
435 BrainToken * rmarker = NULL;
436 BrainToken * tok;
437 char * prompt_resp = NULL;
438 int index;
440 /////////////// FIRST CASE
441 // This should be generalized later, but for now we'll look for "FOR PPP",
442 // and then examine the thing in front of THAT. If it's not punctuation,
443 // we'll use it as a prompt response. If it IS punctuation, but is NOT
444 // a bracket, we'll take the thing before THAT even, and use it as a prompt
445 // response.
446 for( tok = token_list; tok != NULL; tok = tok->next ) {
447 bool failed = false;
448 if( tok->type == TOK_PUNCT )
449 continue;
451 // Only looking at words and numbers now.
452 BrainToken * tok2 = tok->next;
453 for( ; tok2 != NULL; tok2 = tok2->next ) {
454 if( tok2->type != TOK_PUNCT )
455 break;
456 // Only looking at punctuation after the word we're investigating.
457 if( strchr( brackets, tok2->tok_char ) ) {
458 failed = true;
459 break;
462 if( failed )
463 continue;
465 if( !tok2 || !tok2->next )
466 break;
468 // Now tok is the potential response, and tok2 is the next word or
469 // number, as long as there were no brackets in between.
470 // So now we can look for "for ppp".
471 if( !strcmp( tok2->tok_str, "for" ) &&
472 !strcmp( tok2->next->tok_str, "ppp" ) )
474 set_prompt_response( tok->tok_str );
475 return;
479 /////////////// SECOND CASE
480 // Find the first right-bracket on the line, and evaluate everything
481 // before it. Things that are allowed are numbers, and words other
482 // than "press" etc.
483 for( tok = token_list; tok != NULL; tok = tok->next )
484 if( tok->type == TOK_PUNCT )
485 if( strchr( rbrackets, tok->tok_char ) ) {
486 rmarker = tok; // leftmost right-bracket on this line.
487 break;
489 if( rmarker == NULL )
490 goto three; // no right-bracket on this line.
492 // Make sure "ppp" comes _AFTER_ the rmarker... So that we don't respond
493 // to "I like food (ppp is fun too)" or similar things.
494 for( tok = rmarker->next; tok != NULL; tok = tok->next )
495 if( tok->type == TOK_WORD && strcmp( tok->tok_str, "ppp" ) == 0 )
496 break;
497 if( tok == NULL ) // We did not find "ppp" after the rmarker
498 goto three;
500 for( tok = token_list; tok != rmarker; tok = tok->next ) {
501 // If we find punctuation in here, then Case Two is WRONG.
502 // Also, handles things like "Press 5" or "Type ppp" correctly.
503 // If there's more than one valid "thing", use the last one.
504 if( tok->type == TOK_PUNCT ) {
505 prompt_resp = NULL;
506 break;
508 if( tok->type == TOK_NUMBER )
509 prompt_resp = tok->tok_str;
510 if( tok->type == TOK_WORD )
511 if( strstr( menu_bad_words, tok->tok_str ) == NULL )
512 prompt_resp = tok->tok_str;
515 if( prompt_resp != NULL ) { // Case Two was successful!
516 set_prompt_response( prompt_resp );
517 return;
520 three:
521 /////////////// THIRD CASE
522 // Find the first (and recursively innermost) matching pair of brackets.
523 // For example: "This ('a', by the way) is what you type to start ppp."
524 // will parse out simply the letter a.
526 // Start by finding the RIGHTmost right-bracket which immediately
527 // follows the LEFTmost right-bracket (ie - no words in between).
528 // In the above example, it is the first apostrophe.
530 // Nov 6/98: Ummmmm, does the above paragraph make sense?
531 bool ready_to_break = false;
532 rmarker = NULL;
533 for( tok = token_list; tok != NULL; tok = tok->next ) {
535 if( tok->type == TOK_PUNCT ) {
536 if( strchr( lbrackets, tok->tok_char ) ) {
537 lmarker = tok;
538 ready_to_break = true;
540 } else {
541 if( ready_to_break )
542 break;
545 if( lmarker == NULL )
546 goto four; // no left-bracket on this line.
548 // Now find the matching bit of punctuation in the remainder.
549 // Watch for useful words as we do it...
550 index = strchr( lbrackets, lmarker->tok_char ) - lbrackets;
551 for( tok = lmarker->next; tok != NULL; tok = tok->next ) {
552 if( tok->type == TOK_PUNCT ) {
553 if( tok->tok_char == rbrackets[ index ] ) {
554 rmarker = tok;
555 break;
557 } else if( tok->type == TOK_WORD ) {
558 if( strstr( menu_bad_words, tok->tok_str ) == NULL )
559 prompt_resp = tok->tok_str;
560 } else // tok->type == TOK_NUMBER
561 prompt_resp = tok->tok_str;
564 if( rmarker == NULL )
565 goto four; // no corresponding right-bracket on this line.
567 // Make sure "ppp" comes _AFTER_ the rmarker... So that we don't respond
568 // to "I like food (ppp is fun too)" or similar things.
569 for( tok = rmarker->next; tok != NULL; tok = tok->next )
570 if( tok->type == TOK_WORD && strcmp( tok->tok_str, "ppp" ) == 0 )
571 break;
572 if( tok == NULL ) // We did not find "ppp" after the rmarker
573 goto four;
575 if( prompt_resp != NULL ) { // Case Three was successful
576 set_prompt_response( prompt_resp );
577 return;
580 four:
581 /////////////// FOURTH CASE
582 // This is a catch-all for punctuationless menus of the form:
583 // 1 PPP
584 // 2 Quit
585 // Let's just assume the command is a single-digit number. This should
586 // avoid accidentally parsing phone numbers or IP addresses.
587 lmarker = token_list;
588 if ( lmarker->type == TOK_NUMBER
589 && lmarker->tok_str[0] && !lmarker->tok_str[1] )
591 for ( tok = token_list; tok != NULL; tok = tok->next )
592 if (tok->type == TOK_WORD && strcmp( tok->tok_str, "ppp" ) == 0 )
593 break;
595 if ( tok )
597 set_prompt_response( lmarker->tok_str ); // Case Four worked!
598 return;
603 // Apparently this was not a valid menu option. Oh well.
604 return;
607 void WvDialBrain::set_prompt_response( char * str )
608 /*************************************************/
610 WvString n;
612 if( strcmp( str, prompt_response ) ) {
613 n.setsize( strlen( str ) + 1 );
614 strcpy( n.edit(), str );
615 n.edit()[ strlen( str ) ] = '\0';
617 dialer->log( "Found a good menu option: \"%s\".\n", n );
618 prompt_response = n;