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.
10 #include "wvdialbrain.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 /*********************************************/
31 saw_first_compuserve_prompt
= 0;
35 WvDialBrain::~WvDialBrain()
36 /*************************/
40 void WvDialBrain::reset()
41 /***********************/
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
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
-- )
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
);
78 guess_menu_guts( tok
); // may call set_prompt_response()
79 token_list_done( tok
);
83 return( cptr
+ 4 ); // return pointer directly AFTER "ppp".
88 const char * WvDialBrain::check_prompt( const char * buffer
)
89 /***********************************************************/
93 // If we've been here too many times, or too long ago, just give up and
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" );
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();
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();
119 saw_first_compuserve_prompt
++;
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();
135 } else if( is_password_prompt( buffer
) ) {
136 const char *passwd
= 0;
137 if (dialer
->options
.compuserve
&& saw_first_compuserve_prompt
== 1) {
139 dialer
->log( "Looks like a Compuserve classic password prompt.\nSending: classic\n" );
140 saw_first_compuserve_prompt
++;
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();
147 sent_login
= 1; // yes, we've sent a password:
148 // assume we've sent username too.
149 if (!passwd
) passwd
= dialer
->options
.password
;
152 } else if( is_welcome_msg( buffer
) ) {
153 dialer
->log( "Looks like a welcome message.\n" );
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",
167 dialer
->reset_offset();
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
= "";
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.
190 //**************************************************
191 // WvDialBrain Private Functions
192 //**************************************************
194 bool WvDialBrain::is_prompt( const char * c
,
195 const char * promptstring
,
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.
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
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
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
) )
232 c
= cptr
> c
? cptr
: c
;
233 while( isspace( *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
);
243 else while( strstr( c
+1, promptstring
) )
244 c
= strstr( c
+1, promptstring
);
245 } else { // dots are wild, like in regular expressions
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.
258 // Start over, since a letter did not match and was not a dot.
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
) )
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
) )
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
;
328 if( left
== NULL
|| right
== NULL
|| right
<= left
)
332 while( p
<= right
) {
333 // If *p is a null or a new-line, we are done.
334 if( *p
== '\0' || isnewline( *p
) )
337 // Skip whitespace in the string.
338 if( isspace( *p
) ) {
343 // If it's a letter, we've got the beginning of a word.
344 if( isalpha( *p
) ) {
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
;
352 prev_token
->next
= new_token
;
353 while( end
<= right
&& isalpha( *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
;
363 // If it's a digit, we've got the beginning of a number.
364 if( isdigit( *p
) ) {
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
;
372 prev_token
->next
= new_token
;
373 while( end
<= right
&& isdigit( *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
;
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
;
391 prev_token
->next
= new_token
;
392 new_token
->tok_char
= *p
;
394 prev_token
= new_token
;
398 // If it's anything else, ignore it.
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
;
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
;
437 char * prompt_resp
= NULL
;
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
446 for( tok
= token_list
; tok
!= NULL
; tok
= tok
->next
) {
448 if( tok
->type
== TOK_PUNCT
)
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
)
456 // Only looking at punctuation after the word we're investigating.
457 if( strchr( brackets
, tok2
->tok_char
) ) {
465 if( !tok2
|| !tok2
->next
)
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
);
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
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.
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 )
497 if( tok
== NULL
) // We did not find "ppp" after the rmarker
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
) {
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
);
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;
533 for( tok
= token_list
; tok
!= NULL
; tok
= tok
->next
) {
535 if( tok
->type
== TOK_PUNCT
) {
536 if( strchr( lbrackets
, tok
->tok_char
) ) {
538 ready_to_break
= true;
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
] ) {
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 )
572 if( tok
== NULL
) // We did not find "ppp" after the rmarker
575 if( prompt_resp
!= NULL
) { // Case Three was successful
576 set_prompt_response( prompt_resp
);
581 /////////////// FOURTH CASE
582 // This is a catch-all for punctuationless menus of the form:
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 )
597 set_prompt_response( lmarker
->tok_str
); // Case Four worked!
603 // Apparently this was not a valid menu option. Oh well.
607 void WvDialBrain::set_prompt_response( char * str
)
608 /*************************************************/
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
);