]> andersk Git - gssapi-openssh.git/blob - openssh/gss-serv.c
resolved conflicts with import of OpenSSH 3.3p1
[gssapi-openssh.git] / openssh / gss-serv.c
1 /*
2  * Copyright (c) 2001 Simon Wilkinson. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include "includes.h"
26
27 #ifdef GSSAPI
28
29 #include "ssh.h"
30 #include "ssh1.h"
31 #include "ssh2.h"
32 #include "xmalloc.h"
33 #include "buffer.h"
34 #include "bufaux.h"
35 #include "packet.h"
36 #include "compat.h"
37 #include <openssl/evp.h>
38 #include "cipher.h"
39 #include "kex.h"
40 #include "auth.h"
41 #include "log.h"
42 #include "channels.h"
43 #include "session.h"
44 #include "dispatch.h"
45 #include "servconf.h"
46 #include "compat.h"
47 #include "misc.h"
48
49 #include "ssh-gss.h"
50
51 extern ServerOptions options;
52 extern u_char *session_id2;
53 extern int session_id2_len;
54
55 void    userauth_reply(Authctxt *authctxt, int authenticated);
56 static void gssapi_unsetenv(const char *var);
57
58 typedef struct ssh_gssapi_cred_cache {
59         char *filename;
60         char *envvar;
61         char *envval;
62         void *data;
63 } ssh_gssapi_cred_cache;
64
65 static struct ssh_gssapi_cred_cache gssapi_cred_store = {NULL,NULL,NULL};
66 unsigned char ssh1_key_digest[16]; /* used for ssh1 gssapi */
67
68 /*
69  * Environment variables pointing to delegated credentials
70  */
71 static char *delegation_env[] = {
72   "X509_USER_PROXY",            /* GSSAPI/SSLeay */
73   "KRB5CCNAME",                 /* Krb5 and possibly SSLeay */
74   NULL
75 };
76
77 Authmethod method_external = {
78         "external-keyx",
79         userauth_external,
80         &options.gss_authentication
81 };
82
83 Authmethod method_gssapi = {
84         "gssapi",
85         userauth_gssapi,
86         &options.gss_authentication
87 };
88
89 #ifdef KRB5
90
91 #ifdef HEIMDAL
92 #include <krb5.h>
93 #else
94 #include <gssapi_krb5.h>
95 #define krb5_get_err_text(context,code) error_message(code)
96 #endif
97
98 static krb5_context krb_context = NULL;
99
100 /* Initialise the krb5 library, so we can use it for those bits that
101  * GSSAPI won't do */
102
103 int ssh_gssapi_krb5_init() {
104         krb5_error_code problem;
105         
106         if (krb_context !=NULL)
107                 return 1;
108                 
109         problem = krb5_init_context(&krb_context);
110         if (problem) {
111                 log("Cannot initialize krb5 context");
112                 return 0;
113         }
114         krb5_init_ets(krb_context);
115
116         return 1;       
117 }                       
118
119 /* Check if this user is OK to login. This only works with krb5 - other 
120  * GSSAPI mechanisms will need their own.
121  * Returns true if the user is OK to log in, otherwise returns 0
122  */
123
124 int
125 ssh_gssapi_krb5_userok(char *name) {
126         krb5_principal princ;
127         int retval;
128
129         if (ssh_gssapi_krb5_init() == 0)
130                 return 0;
131                 
132         if ((retval=krb5_parse_name(krb_context, gssapi_client_name.value, 
133                                     &princ))) {
134                 log("krb5_parse_name(): %.100s", 
135                         krb5_get_err_text(krb_context,retval));
136                 return 0;
137         }
138         if (krb5_kuserok(krb_context, princ, name)) {
139                 retval = 1;
140                 log("Authorized to %s, krb5 principal %s (krb5_kuserok)",name,
141                     (char *)gssapi_client_name.value);
142         }
143         else
144                 retval = 0;
145         
146         krb5_free_principal(krb_context, princ);
147         return retval;
148 }
149         
150 /* Make sure that this is called _after_ we've setuid to the user */
151
152 /* This writes out any forwarded credentials. Its specific to the Kerberos
153  * GSSAPI mechanism
154  *
155  * We assume that our caller has made sure that the user has selected
156  * delegated credentials, and that the client_creds structure is correctly
157  * populated.
158  */
159
160 void
161 ssh_gssapi_krb5_storecreds() {
162         krb5_ccache ccache;
163         krb5_error_code problem;
164         krb5_principal princ;
165         char ccname[35];
166         static char name[40];
167         int tmpfd;
168         OM_uint32 maj_status,min_status;
169
170
171         if (gssapi_client_creds==NULL) {
172                 debug("No credentials stored"); 
173                 return;
174         }
175                 
176         if (ssh_gssapi_krb5_init() == 0)
177                 return;
178
179         if (options.gss_use_session_ccache) {
180                 snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d_XXXXXX",geteuid());
181        
182                 if ((tmpfd = mkstemp(ccname))==-1) {
183                         log("mkstemp(): %.100s", strerror(errno));
184                         return;
185                 }
186                 if (fchmod(tmpfd, S_IRUSR | S_IWUSR) == -1) {
187                         log("fchmod(): %.100s", strerror(errno));
188                         close(tmpfd);
189                         return;
190                 }
191         } else {
192                 snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d",geteuid());
193                 tmpfd = open(ccname, O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
194                 if (tmpfd == -1) {
195                         log("open(): %.100s", strerror(errno));
196                         return;
197                 }
198         }
199
200         close(tmpfd);
201         snprintf(name, sizeof(name), "FILE:%s",ccname);
202  
203         if ((problem = krb5_cc_resolve(krb_context, name, &ccache))) {
204                 log("krb5_cc_default(): %.100s", 
205                         krb5_get_err_text(krb_context,problem));
206                 return;
207         }
208
209         if ((problem = krb5_parse_name(krb_context, gssapi_client_name.value, 
210                                        &princ))) {
211                 log("krb5_parse_name(): %.100s", 
212                         krb5_get_err_text(krb_context,problem));
213                 krb5_cc_destroy(krb_context,ccache);
214                 return;
215         }
216         
217         if ((problem = krb5_cc_initialize(krb_context, ccache, princ))) {
218                 log("krb5_cc_initialize(): %.100s", 
219                         krb5_get_err_text(krb_context,problem));
220                 krb5_free_principal(krb_context,princ);
221                 krb5_cc_destroy(krb_context,ccache);
222                 return;
223         }
224         
225         krb5_free_principal(krb_context,princ);
226
227         #ifdef HEIMDAL
228         if ((problem = krb5_cc_copy_cache(krb_context, 
229                                            gssapi_client_creds->ccache,
230                                            ccache))) {
231                 log("krb5_cc_copy_cache(): %.100s", 
232                         krb5_get_err_text(krb_context,problem));
233                 krb5_cc_destroy(krb_context,ccache);
234                 return;
235         }
236         #else
237         if ((maj_status = gss_krb5_copy_ccache(&min_status, 
238                                                gssapi_client_creds, 
239                                                ccache))) {
240                 log("gss_krb5_copy_ccache() failed");
241                 ssh_gssapi_error(maj_status,min_status);
242                 krb5_cc_destroy(krb_context,ccache);
243                 return;
244         }
245         #endif
246         
247         krb5_cc_close(krb_context,ccache);
248
249
250 #ifdef USE_PAM
251         do_pam_putenv("KRB5CCNAME",name);
252 #endif
253
254         gssapi_cred_store.filename=strdup(ccname);
255         gssapi_cred_store.envvar="KRB5CCNAME";
256         gssapi_cred_store.envval=strdup(name);
257
258         return;
259 }
260
261 #endif /* KRB5 */
262
263 #ifdef GSI
264 #include <globus_gss_assist.h>
265
266 /*
267  * Check if this user is OK to login under GSI. User has been authenticated
268  * as identity in global 'client_name.value' and is trying to log in as passed
269  * username in 'name'.
270  *
271  * Returns non-zero if user is authorized, 0 otherwise.
272  */
273 int
274 ssh_gssapi_gsi_userok(char *name)
275 {
276     int authorized = 0;
277     
278     /* This returns 0 on success */
279     authorized = (globus_gss_assist_userok(gssapi_client_name.value,
280                                            name) == 0);
281     
282     log("GSI user %s is%s authorized as target user %s",
283         (char *) gssapi_client_name.value, (authorized ? "" : " not"), name);
284     
285     return authorized;
286 }
287
288 /*
289  * Handle setting up child environment for GSI.
290  *
291  * Make sure that this is called _after_ we've setuid to the user.
292  */
293 void
294 ssh_gssapi_gsi_storecreds()
295 {
296         OM_uint32       major_status;
297         OM_uint32       minor_status;
298         
299         
300         if (gssapi_client_creds != NULL)
301         {
302                 char *creds_env = NULL;
303
304                 /*
305                  * This is the current hack with the GSI gssapi library to
306                  * export credentials to disk.
307                  */
308
309                 debug("Exporting delegated credentials");
310                 
311                 minor_status = 0xdee0;  /* Magic value */
312                 major_status =
313                         gss_inquire_cred(&minor_status,
314                                          gssapi_client_creds,
315                                          (gss_name_t *) &creds_env,
316                                          NULL,
317                                          NULL,
318                                          NULL);
319
320                 if ((major_status == GSS_S_COMPLETE) &&
321                     (minor_status == 0xdee1) &&
322                     (creds_env != NULL))
323                 {
324                         char            *value;
325                                 
326                         /*
327                          * String is of the form:
328                          * X509_USER_DELEG_PROXY=filename
329                          * so we parse out the filename
330                          * and then set X509_USER_PROXY
331                          * to point at it.
332                          */
333                         value = strchr(creds_env, '=');
334                         
335                         if (value != NULL)
336                         {
337                                 *value = '\0';
338                                 value++;
339 #ifdef USE_PAM
340                                 do_pam_putenv("X509_USER_PROXY",value);
341 #endif
342                                 gssapi_cred_store.filename=NULL;
343                                 gssapi_cred_store.envvar="X509_USER_PROXY";
344                                 gssapi_cred_store.envval=strdup(value);
345
346                                 return;
347                         }
348                         else
349                         {
350                                 log("Failed to parse delegated credentials string '%s'",
351                                     creds_env);
352                         }
353                 }
354                 else
355                 {
356                         log("Failed to export delegated credentials (error %ld)",
357                             major_status);
358                 }
359         }       
360 }
361
362 #endif /* GSI */
363
364 void
365 ssh_gssapi_cleanup_creds(void *ignored)
366 {
367         if (gssapi_cred_store.filename!=NULL) {
368                 /* Unlink probably isn't sufficient */
369                 debug("removing gssapi cred file\"%s\"",gssapi_cred_store.filename);
370                 unlink(gssapi_cred_store.filename);
371         }
372 }
373
374 void 
375 ssh_gssapi_storecreds()
376 {
377         switch (gssapi_client_type) {
378 #ifdef KRB5
379         case GSS_KERBEROS:
380                 ssh_gssapi_krb5_storecreds();
381                 break;
382 #endif
383 #ifdef GSI
384         case GSS_GSI:
385                 ssh_gssapi_gsi_storecreds();
386                 break;
387 #endif /* GSI */
388         case GSS_LAST_ENTRY:
389                 /* GSSAPI not used in this authentication */
390                 debug("No GSSAPI credentials stored");
391                 break;
392         default:
393                 log("ssh_gssapi_do_child: Unknown mechanism");
394         
395         }
396         
397         if (options.gss_cleanup_creds) {
398                 fatal_add_cleanup(ssh_gssapi_cleanup_creds, NULL);
399         }
400
401 }
402
403 /* This allows GSSAPI methods to do things to the childs environment based
404  * on the passed authentication process and credentials.
405  *
406  * Question: If we didn't use userauth_external for some reason, should we
407  * still delegate credentials?
408  */
409 void 
410 ssh_gssapi_do_child(char ***envp, u_int *envsizep) 
411 {
412
413         if (gssapi_cred_store.envvar!=NULL && 
414             gssapi_cred_store.envval!=NULL) {
415             
416                 debug("Setting %s to %s", gssapi_cred_store.envvar,
417                                           gssapi_cred_store.envval);                              
418                 child_set_env(envp, envsizep, gssapi_cred_store.envvar, 
419                                               gssapi_cred_store.envval);
420         }
421
422         switch(gssapi_client_type) {
423 #ifdef KRB5
424         case GSS_KERBEROS: break;
425 #endif
426 #ifdef GSI
427         case GSS_GSI: break;
428 #endif
429         case GSS_LAST_ENTRY:
430                 debug("No GSSAPI credentials stored");
431                 break;
432         default:
433                 log("ssh_gssapi_do_child: Unknown mechanism");
434         }
435 }
436
437 int
438 ssh_gssapi_userok(char *user)
439 {
440         if (gssapi_client_name.length==0 || 
441             gssapi_client_name.value==NULL) {
442                 debug("No suitable client data");
443                 return 0;
444         }
445         switch (gssapi_client_type) {
446 #ifdef KRB5
447         case GSS_KERBEROS:
448                 return(ssh_gssapi_krb5_userok(user));
449                 break; /* Not reached */
450 #endif
451 #ifdef GSI
452         case GSS_GSI:
453                 return(ssh_gssapi_gsi_userok(user));
454                 break; /* Not reached */
455 #endif /* GSI */
456         case GSS_LAST_ENTRY:
457                 debug("Client not GSSAPI");
458                 break;
459         default:
460                 debug("Unknown client authentication type");
461         }
462         return(0);
463 }
464
465 int
466 userauth_external(Authctxt *authctxt)
467 {
468         packet_check_eom();
469
470         return(ssh_gssapi_userok(authctxt->user));
471 }
472
473 void input_gssapi_token(int type, u_int32_t plen, void *ctxt);
474 void input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt);
475
476 /* We only support those mechanisms that we know about (ie ones that we know
477  * how to check local user kuserok and the like
478  */
479 int
480 userauth_gssapi(Authctxt *authctxt)
481 {
482         gss_OID_desc    oid= {0,NULL};
483         Gssctxt         *ctxt;
484         int             mechs;
485         gss_OID_set     supported;
486         int             present;
487         OM_uint32       ms;
488         u_int           len;
489         
490         if (!authctxt->valid || authctxt->user == NULL)
491                 return 0;
492                 
493         if (datafellows & SSH_OLD_GSSAPI) {
494                 debug("Early drafts of GSSAPI userauth not supported");
495                 return 0;
496         }
497         
498         mechs=packet_get_int();
499         if (mechs==0) {
500                 debug("Mechanism negotiation is not supported");
501                 return 0;
502         }
503
504         ssh_gssapi_supported_oids(&supported);
505         do {
506                 if (oid.elements)
507                         xfree(oid.elements);
508                 oid.elements = packet_get_string(&len);
509                 oid.length = len;
510                 gss_test_oid_set_member(&ms, &oid, supported, &present);
511                 mechs--;
512         } while (mechs>0 && !present);
513         
514         if (!present) {
515                 xfree(oid.elements);
516                 return(0);
517         }
518         
519         ctxt=xmalloc(sizeof(Gssctxt));
520         authctxt->methoddata=(void *)ctxt;
521         
522         ssh_gssapi_build_ctx(ctxt);
523         ssh_gssapi_set_oid(ctxt,&oid);
524
525         if (ssh_gssapi_acquire_cred(ctxt))
526                 return 0;
527
528         /* Send SSH_MSG_USERAUTH_GSSAPI_RESPONSE */
529
530         if (!compat20)
531         packet_start(SSH_SMSG_AUTH_GSSAPI_RESPONSE);
532         else
533         packet_start(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE);
534         packet_put_string(oid.elements,oid.length);
535         packet_send();
536         packet_write_wait();
537         xfree(oid.elements);
538                 
539         if (!compat20)
540         dispatch_set(SSH_MSG_AUTH_GSSAPI_TOKEN,
541                                 &input_gssapi_token);
542         else
543         dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, 
544                      &input_gssapi_token);
545         authctxt->postponed = 1;
546         
547         return 0;
548 }
549
550 void
551 input_gssapi_token(int type, u_int32_t plen, void *ctxt)
552 {
553         Authctxt *authctxt = ctxt;
554         Gssctxt *gssctxt;
555         gss_buffer_desc send_tok,recv_tok;
556         OM_uint32 maj_status, min_status;
557         
558         if (authctxt == NULL || authctxt->methoddata == NULL)
559                 fatal("No authentication or GSSAPI context");
560                 
561         gssctxt=authctxt->methoddata;
562
563         recv_tok.value=packet_get_string(&recv_tok.length);
564         
565         maj_status=ssh_gssapi_accept_ctx(gssctxt, &recv_tok, &send_tok, NULL);
566         packet_check_eom();
567         
568         if (GSS_ERROR(maj_status)) {
569                 /* Failure <sniff> */
570                 ssh_gssapi_send_error(maj_status,min_status);
571                 authctxt->postponed = 0;
572                 dispatch_set(SSH_MSG_AUTH_GSSAPI_TOKEN, NULL);
573                 dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
574                 userauth_finish(authctxt, 0, "gssapi");
575         }
576                         
577         if (send_tok.length != 0) {
578                 /* Send a packet back to the client */
579                 if (!compat20)
580                 packet_start(SSH_MSG_AUTH_GSSAPI_TOKEN);
581                 else
582                 packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
583                 packet_put_string(send_tok.value,send_tok.length);
584                 packet_send();
585                 packet_write_wait();
586                 gss_release_buffer(&min_status, &send_tok);
587         }
588         
589         if (maj_status == GSS_S_COMPLETE) {
590                 dispatch_set(SSH_MSG_AUTH_GSSAPI_TOKEN, NULL);
591                 dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,NULL);
592                 /* ssh1 does not have an extra message here */
593                 if (!compat20)
594                 input_gssapi_exchange_complete(0, 0, ctxt);
595                 else
596                 dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,
597                              &input_gssapi_exchange_complete);
598         }
599 }
600
601 /* This is called when the client thinks we've completed authentication.
602  * It should only be enabled in the dispatch handler by the function above,
603  * which only enables it once the GSSAPI exchange is complete.
604  */
605  
606 void
607 input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt)
608 {
609         Authctxt *authctxt = ctxt;
610         Gssctxt *gssctxt;
611         int authenticated;
612
613         if(strcmp(authctxt->user,"") == 0) {
614                 char *user;
615                 char *gridmapped_name = NULL;
616                 struct passwd *pw = NULL;
617                 if(globus_gss_assist_gridmap(gssapi_client_name.value,
618                            &gridmapped_name) == 0) {
619                         user = gridmapped_name;
620                         debug("I gridmapped and got %s", user);
621                         pw = getpwnam(user);
622                         if (pw && allowed_user(pw)) {
623                                 authctxt->user = user;
624                                 authctxt->pw = pwcopy(pw);
625                                 authctxt->valid = 1;
626                         }
627                 }
628         }
629
630         
631         if (authctxt == NULL || authctxt->methoddata == NULL)
632                 fatal("No authentication or GSSAPI context");
633                 
634         gssctxt=authctxt->methoddata;
635
636         /* This should never happen, but better safe than sorry. */
637         if (gssctxt->status != GSS_S_COMPLETE) {
638                 packet_disconnect("Context negotiation is not complete");
639         }
640
641         if (ssh_gssapi_getclient(gssctxt,&gssapi_client_type,
642                                  &gssapi_client_name,
643                                  &gssapi_client_creds)) {
644                 fatal("Couldn't convert client name");
645         }
646                                                 
647         authenticated = ssh_gssapi_userok(authctxt->user);
648
649         /* ssh1 needs to exchange the hash of the keys */
650         if (!compat20) {
651                 if (authenticated) {
652
653                         OM_uint32 maj_status, min_status;
654                         gss_buffer_desc gssbuf,msg_tok;
655
656                         /* ssh1 uses wrap */
657                         gssbuf.value=ssh1_key_digest;
658                         gssbuf.length=sizeof(ssh1_key_digest);
659                         if ((maj_status=gss_wrap(&min_status,
660                                         gssctxt->context,
661                                         0,
662                                         GSS_C_QOP_DEFAULT,
663                                         &gssbuf,
664                                         NULL,
665                                         &msg_tok))) {
666                                 ssh_gssapi_error(maj_status,min_status);
667                                 fatal("Couldn't wrap keys");
668                         }
669                         packet_start(SSH_SMSG_AUTH_GSSAPI_HASH);
670                         packet_put_string((char *)msg_tok.value,msg_tok.length);
671                         packet_send();
672                         packet_write_wait();
673                         gss_release_buffer(&min_status,&msg_tok);
674                 } else {
675                     packet_start(SSH_MSG_AUTH_GSSAPI_ABORT);
676                     packet_send();
677                     packet_write_wait();
678                 }
679         }
680
681         authctxt->postponed = 0;
682         dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
683         dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL);
684         userauth_finish(authctxt, authenticated, "gssapi");
685 }
686
687 /*
688  * Clean our environment on startup. This means removing any environment
689  * strings that might inadvertantly been in root's environment and 
690  * could cause serious security problems if we think we set them.
691  */
692 void
693 ssh_gssapi_clean_env(void)
694 {
695   char *envstr;
696   int envstr_index;
697
698   
699    for (envstr_index = 0;
700        (envstr = delegation_env[envstr_index]) != NULL;
701        envstr_index++) {
702
703      if (getenv(envstr)) {
704        debug("Clearing environment variable %s", envstr);
705        gssapi_unsetenv(envstr);
706      }
707    }
708 }
709
710 /*
711  * Wrapper around unsetenv.
712  */
713 static void
714 gssapi_unsetenv(const char *var)
715 {
716 #ifdef HAVE_UNSETENV
717     unsetenv(var);
718
719 #else /* !HAVE_UNSETENV */
720     extern char **environ;
721     char **p1 = environ;        /* New array list */
722     char **p2 = environ;        /* Current array list */
723     int len = strlen(var);
724
725     /*
726      * Walk through current environ array (p2) copying each pointer
727      * to new environ array (p1) unless the pointer is to the item
728      * we want to delete. Copy happens in place.
729      */
730     while (*p2) {
731         if ((strncmp(*p2, var, len) == 0) &&
732             ((*p2)[len] == '=')) {
733             /*
734              * *p2 points at item to be deleted, just skip over it
735              */
736             p2++;
737         } else {
738             /*
739              * *p2 points at item we want to save, so copy it
740              */
741             *p1 = *p2;
742             p1++;
743             p2++;
744         }
745     }
746
747     /* And make sure new array is NULL terminated */
748     *p1 = NULL;
749 #endif /* HAVE_UNSETENV */
750 }
751
752 #endif /* GSSAPI */
This page took 1.0128 seconds and 5 git commands to generate.