Fossil SCM

Clean up handling of passwords for SSH. Further separate prompting for password from saving of password.

andybradford 2013-11-08 06:01 UTC url-password-fixes
Commit ad34c07c451b6dd825e5983a2e4273bd94bac0a5
+4 -8
--- src/clone.c
+++ src/clone.c
@@ -117,14 +117,14 @@
117117
void clone_cmd(void){
118118
char *zPassword;
119119
const char *zDefaultUser; /* Optional name of the default user */
120120
int nErr = 0;
121121
int bPrivate = 0; /* Also clone private branches */
122
- int urlFlags = URL_PROMPT_PW;
122
+ int urlFlags = URL_PROMPT_PW | URL_REMEMBER;
123123
124124
if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE;
125
- if( find_option("once",0,0)==0) urlFlags |= URL_ASK_REMEMBER_PW;
125
+ if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER;
126126
clone_ssh_find_options();
127127
url_proxy_options();
128128
if( g.argc < 4 ){
129129
usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
130130
}
@@ -140,13 +140,11 @@
140140
if( g.urlIsFile ){
141141
file_copy(g.urlName, g.argv[3]);
142142
db_close(1);
143143
db_open_repository(g.argv[3]);
144144
db_record_repository_filename(g.argv[3]);
145
- if( urlFlags&URL_ASK_REMEMBER_PW ){
146
- url_remember();
147
- }
145
+ url_remember();
148146
if( !bPrivate ) delete_private_content();
149147
shun_artifacts();
150148
db_create_default_users(1, zDefaultUser);
151149
if( zDefaultUser ){
152150
g.zLogin = zDefaultUser;
@@ -161,13 +159,11 @@
161159
db_record_repository_filename(g.argv[3]);
162160
db_initial_setup(0, 0, zDefaultUser, 0);
163161
user_select();
164162
db_set("content-schema", CONTENT_SCHEMA, 0);
165163
db_set("aux-schema", AUX_SCHEMA, 0);
166
- if( urlFlags&URL_ASK_REMEMBER_PW ){
167
- url_remember();
168
- }
164
+ url_remember();
169165
if( g.zSSLIdentity!=0 ){
170166
/* If the --ssl-identity option was specified, store it as a setting */
171167
Blob fn;
172168
blob_zero(&fn);
173169
file_canonical_name(g.zSSLIdentity, &fn, 0);
174170
--- src/clone.c
+++ src/clone.c
@@ -117,14 +117,14 @@
117 void clone_cmd(void){
118 char *zPassword;
119 const char *zDefaultUser; /* Optional name of the default user */
120 int nErr = 0;
121 int bPrivate = 0; /* Also clone private branches */
122 int urlFlags = URL_PROMPT_PW;
123
124 if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE;
125 if( find_option("once",0,0)==0) urlFlags |= URL_ASK_REMEMBER_PW;
126 clone_ssh_find_options();
127 url_proxy_options();
128 if( g.argc < 4 ){
129 usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
130 }
@@ -140,13 +140,11 @@
140 if( g.urlIsFile ){
141 file_copy(g.urlName, g.argv[3]);
142 db_close(1);
143 db_open_repository(g.argv[3]);
144 db_record_repository_filename(g.argv[3]);
145 if( urlFlags&URL_ASK_REMEMBER_PW ){
146 url_remember();
147 }
148 if( !bPrivate ) delete_private_content();
149 shun_artifacts();
150 db_create_default_users(1, zDefaultUser);
151 if( zDefaultUser ){
152 g.zLogin = zDefaultUser;
@@ -161,13 +159,11 @@
161 db_record_repository_filename(g.argv[3]);
162 db_initial_setup(0, 0, zDefaultUser, 0);
163 user_select();
164 db_set("content-schema", CONTENT_SCHEMA, 0);
165 db_set("aux-schema", AUX_SCHEMA, 0);
166 if( urlFlags&URL_ASK_REMEMBER_PW ){
167 url_remember();
168 }
169 if( g.zSSLIdentity!=0 ){
170 /* If the --ssl-identity option was specified, store it as a setting */
171 Blob fn;
172 blob_zero(&fn);
173 file_canonical_name(g.zSSLIdentity, &fn, 0);
174
--- src/clone.c
+++ src/clone.c
@@ -117,14 +117,14 @@
117 void clone_cmd(void){
118 char *zPassword;
119 const char *zDefaultUser; /* Optional name of the default user */
120 int nErr = 0;
121 int bPrivate = 0; /* Also clone private branches */
122 int urlFlags = URL_PROMPT_PW | URL_REMEMBER;
123
124 if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE;
125 if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER;
126 clone_ssh_find_options();
127 url_proxy_options();
128 if( g.argc < 4 ){
129 usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
130 }
@@ -140,13 +140,11 @@
140 if( g.urlIsFile ){
141 file_copy(g.urlName, g.argv[3]);
142 db_close(1);
143 db_open_repository(g.argv[3]);
144 db_record_repository_filename(g.argv[3]);
145 url_remember();
 
 
146 if( !bPrivate ) delete_private_content();
147 shun_artifacts();
148 db_create_default_users(1, zDefaultUser);
149 if( zDefaultUser ){
150 g.zLogin = zDefaultUser;
@@ -161,13 +159,11 @@
159 db_record_repository_filename(g.argv[3]);
160 db_initial_setup(0, 0, zDefaultUser, 0);
161 user_select();
162 db_set("content-schema", CONTENT_SCHEMA, 0);
163 db_set("aux-schema", AUX_SCHEMA, 0);
164 url_remember();
 
 
165 if( g.zSSLIdentity!=0 ){
166 /* If the --ssl-identity option was specified, store it as a setting */
167 Blob fn;
168 blob_zero(&fn);
169 file_canonical_name(g.zSSLIdentity, &fn, 0);
170
+4 -1
--- src/sync.c
+++ src/sync.c
@@ -54,10 +54,11 @@
5454
if( g.urlUser!=0 && g.urlPasswd==0 ){
5555
g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
5656
g.urlFlags |= URL_PROMPT_PW;
5757
url_prompt_for_password();
5858
}
59
+ url_remember();
5960
#if 0 /* Disabled for now */
6061
if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){
6162
/* When doing an automatic pull, also automatically pull shuns from
6263
** the server if pull_shuns is enabled.
6364
**
@@ -115,10 +116,11 @@
115116
}
116117
if( urlFlags & URL_REMEMBER ){
117118
clone_ssh_db_set_options();
118119
}
119120
url_parse(zUrl, urlFlags);
121
+ url_remember();
120122
if( g.urlProtocol==0 ){
121123
if( urlOptional ) fossil_exit(0);
122124
usage("URL");
123125
}
124126
urlFlags = g.urlFlags;
@@ -264,16 +266,17 @@
264266
}
265267
if( g.argc==3 ){
266268
db_unset("last-sync-url", 0);
267269
db_unset("last-sync-pw", 0);
268270
if( is_false(g.argv[2]) ) return;
269
- url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW);
271
+ url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW|URL_ASK_REMEMBER_PW);
270272
}
273
+ url_remember();
271274
zUrl = db_get("last-sync-url", 0);
272275
if( zUrl==0 ){
273276
fossil_print("off\n");
274277
return;
275278
}else{
276279
url_parse(zUrl, 0);
277280
fossil_print("%s\n", g.urlCanonical);
278281
}
279282
}
280283
--- src/sync.c
+++ src/sync.c
@@ -54,10 +54,11 @@
54 if( g.urlUser!=0 && g.urlPasswd==0 ){
55 g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
56 g.urlFlags |= URL_PROMPT_PW;
57 url_prompt_for_password();
58 }
 
59 #if 0 /* Disabled for now */
60 if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){
61 /* When doing an automatic pull, also automatically pull shuns from
62 ** the server if pull_shuns is enabled.
63 **
@@ -115,10 +116,11 @@
115 }
116 if( urlFlags & URL_REMEMBER ){
117 clone_ssh_db_set_options();
118 }
119 url_parse(zUrl, urlFlags);
 
120 if( g.urlProtocol==0 ){
121 if( urlOptional ) fossil_exit(0);
122 usage("URL");
123 }
124 urlFlags = g.urlFlags;
@@ -264,16 +266,17 @@
264 }
265 if( g.argc==3 ){
266 db_unset("last-sync-url", 0);
267 db_unset("last-sync-pw", 0);
268 if( is_false(g.argv[2]) ) return;
269 url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW);
270 }
 
271 zUrl = db_get("last-sync-url", 0);
272 if( zUrl==0 ){
273 fossil_print("off\n");
274 return;
275 }else{
276 url_parse(zUrl, 0);
277 fossil_print("%s\n", g.urlCanonical);
278 }
279 }
280
--- src/sync.c
+++ src/sync.c
@@ -54,10 +54,11 @@
54 if( g.urlUser!=0 && g.urlPasswd==0 ){
55 g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
56 g.urlFlags |= URL_PROMPT_PW;
57 url_prompt_for_password();
58 }
59 url_remember();
60 #if 0 /* Disabled for now */
61 if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){
62 /* When doing an automatic pull, also automatically pull shuns from
63 ** the server if pull_shuns is enabled.
64 **
@@ -115,10 +116,11 @@
116 }
117 if( urlFlags & URL_REMEMBER ){
118 clone_ssh_db_set_options();
119 }
120 url_parse(zUrl, urlFlags);
121 url_remember();
122 if( g.urlProtocol==0 ){
123 if( urlOptional ) fossil_exit(0);
124 usage("URL");
125 }
126 urlFlags = g.urlFlags;
@@ -264,16 +266,17 @@
266 }
267 if( g.argc==3 ){
268 db_unset("last-sync-url", 0);
269 db_unset("last-sync-pw", 0);
270 if( is_false(g.argv[2]) ) return;
271 url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW|URL_ASK_REMEMBER_PW);
272 }
273 url_remember();
274 zUrl = db_get("last-sync-url", 0);
275 if( zUrl==0 ){
276 fossil_print("off\n");
277 return;
278 }else{
279 url_parse(zUrl, 0);
280 fossil_print("%s\n", g.urlCanonical);
281 }
282 }
283
+13 -24
--- src/url.c
+++ src/url.c
@@ -64,25 +64,23 @@
6464
**
6565
** http://userid:password@host:port/path
6666
**
6767
** SSH url format is:
6868
**
69
-** ssh://userid:password@host:port/path?fossil=path/to/fossil.exe
69
+** ssh://userid@host:port/path?fossil=path/to/fossil.exe
7070
**
7171
*/
7272
void url_parse(const char *zUrl, unsigned int urlFlags){
7373
int i, j, c;
7474
char *zFile = 0;
75
- int bSetUrl = 1;
7675
7776
if( zUrl==0 ){
7877
zUrl = db_get("last-sync-url", 0);
7978
if( zUrl==0 ) return;
8079
if( g.urlPasswd==0 ){
8180
g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
8281
}
83
- bSetUrl = 0;
8482
}
8583
8684
if( strncmp(zUrl, "http://", 7)==0
8785
|| strncmp(zUrl, "https://", 8)==0
8886
|| strncmp(zUrl, "ssh://", 6)==0
@@ -115,19 +113,20 @@
115113
/* Parse up the user-id and password */
116114
for(j=iStart; j<i && zUrl[j]!=':'; j++){}
117115
g.urlUser = mprintf("%.*s", j-iStart, &zUrl[iStart]);
118116
dehttpize(g.urlUser);
119117
if( j<i ){
120
- if( urlFlags & URL_REMEMBER ) urlFlags |= URL_ASK_REMEMBER_PW;
118
+ if( ( urlFlags & URL_REMEMBER ) && g.urlIsSsh==0 ){
119
+ urlFlags |= URL_ASK_REMEMBER_PW;
120
+ }
121121
g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
122122
dehttpize(g.urlPasswd);
123123
}
124
- if( g.urlIsSsh && g.urlPasswd ){
125
- zLogin = mprintf("%t:*@", g.urlUser);
126
- }else{
127
- zLogin = mprintf("%t@", g.urlUser);
124
+ if( g.urlIsSsh ){
125
+ urlFlags &= ~URL_ASK_REMEMBER_PW;
128126
}
127
+ zLogin = mprintf("%t@", g.urlUser);
129128
for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
130129
g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
131130
i = j;
132131
}else{
133132
for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
@@ -234,18 +233,10 @@
234233
}else{
235234
g.urlFlags = urlFlags &= ~URL_REMEMBER_PW;
236235
}
237236
}
238237
}
239
- if( urlFlags & URL_REMEMBER ){
240
- if( bSetUrl ){
241
- db_set("last-sync-url", g.urlCanonical, 0);
242
- }
243
- if( g.urlPasswd && g.urlUser && ( g.urlFlags & URL_REMEMBER_PW ) ){
244
- db_set("last-sync-pw", obscure(g.urlPasswd), 0);
245
- }
246
- }
247238
}
248239
249240
/*
250241
** COMMAND: test-urlparser
251242
**
@@ -453,13 +444,10 @@
453444
if( g.urlPasswd[0]
454445
&& (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
455446
){
456447
if( save_password_prompt() ){
457448
g.urlFlags |= URL_REMEMBER_PW;
458
- if( g.urlFlags & URL_REMEMBER ){
459
- db_set("last-sync-pw", obscure(g.urlPasswd), 0);
460
- }
461449
}else{
462450
g.urlFlags &= ~URL_REMEMBER_PW;
463451
}
464452
}
465453
}else{
@@ -467,18 +455,19 @@
467455
g.urlUser);
468456
}
469457
}
470458
471459
/*
472
-** Remember the URL if requested.
460
+** Remember the URL and password if requested.
473461
*/
474462
void url_remember(void){
475
- db_set("last-sync-url", g.urlCanonical, 0);
476
- if( g.urlFlags & URL_REMEMBER_PW ){
477
- db_set("last-sync-pw", obscure(g.urlPasswd), 0);
463
+ if( g.urlFlags & URL_REMEMBER ){
464
+ db_set("last-sync-url", g.urlCanonical, 0);
465
+ if( g.urlUser!=0 && g.urlPasswd!=0 && ( g.urlFlags & URL_REMEMBER_PW ) ){
466
+ db_set("last-sync-pw", obscure(g.urlPasswd), 0);
467
+ }
478468
}
479
- g.urlFlags |= URL_REMEMBER;
480469
}
481470
482471
/* Preemptively prompt for a password if a username is given in the
483472
** URL but no password.
484473
*/
485474
--- src/url.c
+++ src/url.c
@@ -64,25 +64,23 @@
64 **
65 ** http://userid:password@host:port/path
66 **
67 ** SSH url format is:
68 **
69 ** ssh://userid:password@host:port/path?fossil=path/to/fossil.exe
70 **
71 */
72 void url_parse(const char *zUrl, unsigned int urlFlags){
73 int i, j, c;
74 char *zFile = 0;
75 int bSetUrl = 1;
76
77 if( zUrl==0 ){
78 zUrl = db_get("last-sync-url", 0);
79 if( zUrl==0 ) return;
80 if( g.urlPasswd==0 ){
81 g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
82 }
83 bSetUrl = 0;
84 }
85
86 if( strncmp(zUrl, "http://", 7)==0
87 || strncmp(zUrl, "https://", 8)==0
88 || strncmp(zUrl, "ssh://", 6)==0
@@ -115,19 +113,20 @@
115 /* Parse up the user-id and password */
116 for(j=iStart; j<i && zUrl[j]!=':'; j++){}
117 g.urlUser = mprintf("%.*s", j-iStart, &zUrl[iStart]);
118 dehttpize(g.urlUser);
119 if( j<i ){
120 if( urlFlags & URL_REMEMBER ) urlFlags |= URL_ASK_REMEMBER_PW;
 
 
121 g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
122 dehttpize(g.urlPasswd);
123 }
124 if( g.urlIsSsh && g.urlPasswd ){
125 zLogin = mprintf("%t:*@", g.urlUser);
126 }else{
127 zLogin = mprintf("%t@", g.urlUser);
128 }
 
129 for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
130 g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
131 i = j;
132 }else{
133 for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
@@ -234,18 +233,10 @@
234 }else{
235 g.urlFlags = urlFlags &= ~URL_REMEMBER_PW;
236 }
237 }
238 }
239 if( urlFlags & URL_REMEMBER ){
240 if( bSetUrl ){
241 db_set("last-sync-url", g.urlCanonical, 0);
242 }
243 if( g.urlPasswd && g.urlUser && ( g.urlFlags & URL_REMEMBER_PW ) ){
244 db_set("last-sync-pw", obscure(g.urlPasswd), 0);
245 }
246 }
247 }
248
249 /*
250 ** COMMAND: test-urlparser
251 **
@@ -453,13 +444,10 @@
453 if( g.urlPasswd[0]
454 && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
455 ){
456 if( save_password_prompt() ){
457 g.urlFlags |= URL_REMEMBER_PW;
458 if( g.urlFlags & URL_REMEMBER ){
459 db_set("last-sync-pw", obscure(g.urlPasswd), 0);
460 }
461 }else{
462 g.urlFlags &= ~URL_REMEMBER_PW;
463 }
464 }
465 }else{
@@ -467,18 +455,19 @@
467 g.urlUser);
468 }
469 }
470
471 /*
472 ** Remember the URL if requested.
473 */
474 void url_remember(void){
475 db_set("last-sync-url", g.urlCanonical, 0);
476 if( g.urlFlags & URL_REMEMBER_PW ){
477 db_set("last-sync-pw", obscure(g.urlPasswd), 0);
 
 
478 }
479 g.urlFlags |= URL_REMEMBER;
480 }
481
482 /* Preemptively prompt for a password if a username is given in the
483 ** URL but no password.
484 */
485
--- src/url.c
+++ src/url.c
@@ -64,25 +64,23 @@
64 **
65 ** http://userid:password@host:port/path
66 **
67 ** SSH url format is:
68 **
69 ** ssh://userid@host:port/path?fossil=path/to/fossil.exe
70 **
71 */
72 void url_parse(const char *zUrl, unsigned int urlFlags){
73 int i, j, c;
74 char *zFile = 0;
 
75
76 if( zUrl==0 ){
77 zUrl = db_get("last-sync-url", 0);
78 if( zUrl==0 ) return;
79 if( g.urlPasswd==0 ){
80 g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
81 }
 
82 }
83
84 if( strncmp(zUrl, "http://", 7)==0
85 || strncmp(zUrl, "https://", 8)==0
86 || strncmp(zUrl, "ssh://", 6)==0
@@ -115,19 +113,20 @@
113 /* Parse up the user-id and password */
114 for(j=iStart; j<i && zUrl[j]!=':'; j++){}
115 g.urlUser = mprintf("%.*s", j-iStart, &zUrl[iStart]);
116 dehttpize(g.urlUser);
117 if( j<i ){
118 if( ( urlFlags & URL_REMEMBER ) && g.urlIsSsh==0 ){
119 urlFlags |= URL_ASK_REMEMBER_PW;
120 }
121 g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
122 dehttpize(g.urlPasswd);
123 }
124 if( g.urlIsSsh ){
125 urlFlags &= ~URL_ASK_REMEMBER_PW;
 
 
126 }
127 zLogin = mprintf("%t@", g.urlUser);
128 for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
129 g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
130 i = j;
131 }else{
132 for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
@@ -234,18 +233,10 @@
233 }else{
234 g.urlFlags = urlFlags &= ~URL_REMEMBER_PW;
235 }
236 }
237 }
 
 
 
 
 
 
 
 
238 }
239
240 /*
241 ** COMMAND: test-urlparser
242 **
@@ -453,13 +444,10 @@
444 if( g.urlPasswd[0]
445 && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
446 ){
447 if( save_password_prompt() ){
448 g.urlFlags |= URL_REMEMBER_PW;
 
 
 
449 }else{
450 g.urlFlags &= ~URL_REMEMBER_PW;
451 }
452 }
453 }else{
@@ -467,18 +455,19 @@
455 g.urlUser);
456 }
457 }
458
459 /*
460 ** Remember the URL and password if requested.
461 */
462 void url_remember(void){
463 if( g.urlFlags & URL_REMEMBER ){
464 db_set("last-sync-url", g.urlCanonical, 0);
465 if( g.urlUser!=0 && g.urlPasswd!=0 && ( g.urlFlags & URL_REMEMBER_PW ) ){
466 db_set("last-sync-pw", obscure(g.urlPasswd), 0);
467 }
468 }
 
469 }
470
471 /* Preemptively prompt for a password if a username is given in the
472 ** URL but no password.
473 */
474
+1
--- src/xfer.c
+++ src/xfer.c
@@ -1783,10 +1783,11 @@
17831783
go = 1;
17841784
if( g.cgiOutput==0 ){
17851785
g.urlFlags |= URL_PROMPT_PW;
17861786
g.urlFlags &= ~URL_PROMPTED;
17871787
url_prompt_for_password();
1788
+ url_remember();
17881789
}
17891790
}
17901791
}else{
17911792
blob_appendf(&xfer.err, "server says: %s\n", zMsg);
17921793
nErr++;
17931794
--- src/xfer.c
+++ src/xfer.c
@@ -1783,10 +1783,11 @@
1783 go = 1;
1784 if( g.cgiOutput==0 ){
1785 g.urlFlags |= URL_PROMPT_PW;
1786 g.urlFlags &= ~URL_PROMPTED;
1787 url_prompt_for_password();
 
1788 }
1789 }
1790 }else{
1791 blob_appendf(&xfer.err, "server says: %s\n", zMsg);
1792 nErr++;
1793
--- src/xfer.c
+++ src/xfer.c
@@ -1783,10 +1783,11 @@
1783 go = 1;
1784 if( g.cgiOutput==0 ){
1785 g.urlFlags |= URL_PROMPT_PW;
1786 g.urlFlags &= ~URL_PROMPTED;
1787 url_prompt_for_password();
1788 url_remember();
1789 }
1790 }
1791 }else{
1792 blob_appendf(&xfer.err, "server says: %s\n", zMsg);
1793 nErr++;
1794

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button