/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugins.d/mandos-client.c

  • Committer: Teddy Hogeborn
  • Date: 2015-07-20 03:03:33 UTC
  • Revision ID: teddy@recompile.se-20150720030333-203m2aeblypcsfte
Bug fix for GnuTLS 3: be compatible with old 2048-bit DSA keys.

The mandos-keygen program in Mandos version 1.6.0 and older generated
2048-bit DSA keys, and when GnuTLS uses these it has trouble
connecting using the Mandos default priority string.  This was
previously fixed in Mandos 1.6.2, but the bug reappeared when using
GnuTLS 3, so the default priority string has to change again; this
time also the Mandos client has to change its default, so now the
server and the client should use the same default priority string:

SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256

* mandos (main/server_defaults): Changed default priority string.
* mandos-options.xml (/section/para[id="priority_compat"]): Removed.
  (/section/para[id="priority"]): Changed default priority string.
* mandos.conf ([DEFAULT]/priority): - '' -
* mandos.conf.xml (OPTIONS/priority): Refer to the id "priority"
                                      instead of "priority_compat".
* mandos.xml (OPTIONS/--priority): - '' -
* plugins.d/mandos-client.c (main): Changed default priority string.

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
 * "browse_callback", and parts of "main".
10
10
 * 
11
11
 * Everything else is
12
 
 * Copyright © 2008-2017 Teddy Hogeborn
13
 
 * Copyright © 2008-2017 Björn Påhlsson
14
 
 * 
15
 
 * This file is part of Mandos.
16
 
 * 
17
 
 * Mandos is free software: you can redistribute it and/or modify it
18
 
 * under the terms of the GNU General Public License as published by
19
 
 * the Free Software Foundation, either version 3 of the License, or
20
 
 * (at your option) any later version.
21
 
 * 
22
 
 * Mandos is distributed in the hope that it will be useful, but
 
12
 * Copyright © 2008-2015 Teddy Hogeborn
 
13
 * Copyright © 2008-2015 Björn Påhlsson
 
14
 * 
 
15
 * This program is free software: you can redistribute it and/or
 
16
 * modify it under the terms of the GNU General Public License as
 
17
 * published by the Free Software Foundation, either version 3 of the
 
18
 * License, or (at your option) any later version.
 
19
 * 
 
20
 * This program is distributed in the hope that it will be useful, but
23
21
 * WITHOUT ANY WARRANTY; without even the implied warranty of
24
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25
23
 * General Public License for more details.
26
24
 * 
27
25
 * You should have received a copy of the GNU General Public License
28
 
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
26
 * along with this program.  If not, see
 
27
 * <http://www.gnu.org/licenses/>.
29
28
 * 
30
29
 * Contact the authors at <mandos@recompile.se>.
31
30
 */
48
47
                                   strtof(), abort() */
49
48
#include <stdbool.h>            /* bool, false, true */
50
49
#include <string.h>             /* strcmp(), strlen(), strerror(),
51
 
                                   asprintf(), strncpy(), strsignal()
52
 
                                */
 
50
                                   asprintf(), strcpy() */
53
51
#include <sys/ioctl.h>          /* ioctl */
54
52
#include <sys/types.h>          /* socket(), inet_pton(), sockaddr,
55
53
                                   sockaddr_in6, PF_INET6,
59
57
#include <sys/socket.h>         /* socket(), struct sockaddr_in6,
60
58
                                   inet_pton(), connect(),
61
59
                                   getnameinfo() */
62
 
#include <fcntl.h>              /* open(), unlinkat(), AT_REMOVEDIR */
 
60
#include <fcntl.h>              /* open(), unlinkat() */
63
61
#include <dirent.h>             /* opendir(), struct dirent, readdir()
64
62
                                 */
65
63
#include <inttypes.h>           /* PRIu16, PRIdMAX, intmax_t,
66
64
                                   strtoimax() */
67
 
#include <errno.h>              /* perror(), errno, EINTR, EINVAL,
68
 
                                   EAI_SYSTEM, ENETUNREACH,
69
 
                                   EHOSTUNREACH, ECONNREFUSED, EPROTO,
70
 
                                   EIO, ENOENT, ENXIO, ENOMEM, EISDIR,
71
 
                                   ENOTEMPTY,
 
65
#include <errno.h>              /* perror(), errno,
72
66
                                   program_invocation_short_name */
73
67
#include <time.h>               /* nanosleep(), time(), sleep() */
74
68
#include <net/if.h>             /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
519
513
  fprintf_plus(stderr, "GnuTLS: %s", string);
520
514
}
521
515
 
522
 
__attribute__((nonnull(1, 2, 4), warn_unused_result))
 
516
__attribute__((nonnull, warn_unused_result))
523
517
static int init_gnutls_global(const char *pubkeyfilename,
524
518
                              const char *seckeyfilename,
525
519
                              const char *dhparamsfilename,
531
525
    fprintf_plus(stderr, "Initializing GnuTLS\n");
532
526
  }
533
527
  
 
528
  ret = gnutls_global_init();
 
529
  if(ret != GNUTLS_E_SUCCESS){
 
530
    fprintf_plus(stderr, "GnuTLS global_init: %s\n",
 
531
                 safer_gnutls_strerror(ret));
 
532
    return -1;
 
533
  }
 
534
  
534
535
  if(debug){
535
536
    /* "Use a log level over 10 to enable all debugging options."
536
537
     * - GnuTLS manual
544
545
  if(ret != GNUTLS_E_SUCCESS){
545
546
    fprintf_plus(stderr, "GnuTLS memory error: %s\n",
546
547
                 safer_gnutls_strerror(ret));
 
548
    gnutls_global_deinit();
547
549
    return -1;
548
550
  }
549
551
  
627
629
                     safer_gnutls_strerror(ret));
628
630
        dhparamsfilename = NULL;
629
631
      }
630
 
      free(params.data);
631
632
    } while(false);
632
633
  }
633
634
  if(dhparamsfilename == NULL){
754
755
 globalfail:
755
756
  
756
757
  gnutls_certificate_free_credentials(mc->cred);
 
758
  gnutls_global_deinit();
757
759
  gnutls_dh_params_deinit(mc->dh_params);
758
760
  return -1;
759
761
}
820
822
 
821
823
/* Set effective uid to 0, return errno */
822
824
__attribute__((warn_unused_result))
823
 
int raise_privileges(void){
824
 
  int old_errno = errno;
825
 
  int ret = 0;
 
825
error_t raise_privileges(void){
 
826
  error_t old_errno = errno;
 
827
  error_t ret_errno = 0;
826
828
  if(seteuid(0) == -1){
827
 
    ret = errno;
 
829
    ret_errno = errno;
828
830
  }
829
831
  errno = old_errno;
830
 
  return ret;
 
832
  return ret_errno;
831
833
}
832
834
 
833
835
/* Set effective and real user ID to 0.  Return errno. */
834
836
__attribute__((warn_unused_result))
835
 
int raise_privileges_permanently(void){
836
 
  int old_errno = errno;
837
 
  int ret = raise_privileges();
838
 
  if(ret != 0){
 
837
error_t raise_privileges_permanently(void){
 
838
  error_t old_errno = errno;
 
839
  error_t ret_errno = raise_privileges();
 
840
  if(ret_errno != 0){
839
841
    errno = old_errno;
840
 
    return ret;
 
842
    return ret_errno;
841
843
  }
842
844
  if(setuid(0) == -1){
843
 
    ret = errno;
 
845
    ret_errno = errno;
844
846
  }
845
847
  errno = old_errno;
846
 
  return ret;
 
848
  return ret_errno;
847
849
}
848
850
 
849
851
/* Set effective user ID to unprivileged saved user ID */
850
852
__attribute__((warn_unused_result))
851
 
int lower_privileges(void){
852
 
  int old_errno = errno;
853
 
  int ret = 0;
 
853
error_t lower_privileges(void){
 
854
  error_t old_errno = errno;
 
855
  error_t ret_errno = 0;
854
856
  if(seteuid(uid) == -1){
855
 
    ret = errno;
 
857
    ret_errno = errno;
856
858
  }
857
859
  errno = old_errno;
858
 
  return ret;
 
860
  return ret_errno;
859
861
}
860
862
 
861
863
/* Lower privileges permanently */
862
864
__attribute__((warn_unused_result))
863
 
int lower_privileges_permanently(void){
864
 
  int old_errno = errno;
865
 
  int ret = 0;
 
865
error_t lower_privileges_permanently(void){
 
866
  error_t old_errno = errno;
 
867
  error_t ret_errno = 0;
866
868
  if(setuid(uid) == -1){
867
 
    ret = errno;
 
869
    ret_errno = errno;
868
870
  }
869
871
  errno = old_errno;
870
 
  return ret;
 
872
  return ret_errno;
871
873
}
872
874
 
873
875
/* Helper function to add_local_route() and delete_local_route() */
1080
1082
    bool match = false;
1081
1083
    {
1082
1084
      char *interface = NULL;
1083
 
      while((interface = argz_next(mc->interfaces,
1084
 
                                   mc->interfaces_size,
1085
 
                                   interface))){
 
1085
      while((interface=argz_next(mc->interfaces, mc->interfaces_size,
 
1086
                                 interface))){
1086
1087
        if(if_nametoindex(interface) == (unsigned int)if_index){
1087
1088
          match = true;
1088
1089
          break;
1222
1223
                    sizeof(struct sockaddr_in));
1223
1224
    }
1224
1225
    if(ret < 0){
1225
 
      if(((errno == ENETUNREACH) or (errno == EHOSTUNREACH))
 
1226
      if(errno == ENETUNREACH
1226
1227
         and if_index != AVAHI_IF_UNSPEC
1227
1228
         and connect_to == NULL
1228
1229
         and not route_added and
1241
1242
           with an explicit route added with the server's address.
1242
1243
           
1243
1244
           Avahi bug reference:
1244
 
           https://lists.freedesktop.org/archives/avahi/2010-February/001833.html
 
1245
           http://lists.freedesktop.org/archives/avahi/2010-February/001833.html
1245
1246
           https://bugs.debian.org/587961
1246
1247
        */
1247
1248
        if(debug){
1427
1428
                                               &decrypted_buffer, mc);
1428
1429
    if(decrypted_buffer_size >= 0){
1429
1430
      
1430
 
      clearerr(stdout);
1431
1431
      written = 0;
1432
1432
      while(written < (size_t) decrypted_buffer_size){
1433
1433
        if(quit_now){
1449
1449
        }
1450
1450
        written += (size_t)ret;
1451
1451
      }
1452
 
      ret = fflush(stdout);
1453
 
      if(ret != 0){
1454
 
        int e = errno;
1455
 
        if(debug){
1456
 
          fprintf_plus(stderr, "Error writing encrypted data: %s\n",
1457
 
                       strerror(errno));
1458
 
        }
1459
 
        errno = e;
1460
 
        goto mandos_end;
1461
 
      }
1462
1452
      retval = 0;
1463
1453
    }
1464
1454
  }
1495
1485
  return retval;
1496
1486
}
1497
1487
 
 
1488
__attribute__((nonnull))
1498
1489
static void resolve_callback(AvahiSServiceResolver *r,
1499
1490
                             AvahiIfIndex interface,
1500
1491
                             AvahiProtocol proto,
1637
1628
__attribute__((nonnull, warn_unused_result))
1638
1629
bool get_flags(const char *ifname, struct ifreq *ifr){
1639
1630
  int ret;
1640
 
  int old_errno;
 
1631
  error_t ret_errno;
1641
1632
  
1642
1633
  int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1643
1634
  if(s < 0){
1644
 
    old_errno = errno;
 
1635
    ret_errno = errno;
1645
1636
    perror_plus("socket");
1646
 
    errno = old_errno;
 
1637
    errno = ret_errno;
1647
1638
    return false;
1648
1639
  }
1649
 
  strncpy(ifr->ifr_name, ifname, IF_NAMESIZE);
1650
 
  ifr->ifr_name[IF_NAMESIZE-1] = '\0'; /* NUL terminate */
 
1640
  strcpy(ifr->ifr_name, ifname);
1651
1641
  ret = ioctl(s, SIOCGIFFLAGS, ifr);
1652
1642
  if(ret == -1){
1653
1643
    if(debug){
1654
 
      old_errno = errno;
 
1644
      ret_errno = errno;
1655
1645
      perror_plus("ioctl SIOCGIFFLAGS");
1656
 
      errno = old_errno;
 
1646
      errno = ret_errno;
1657
1647
    }
1658
1648
    return false;
1659
1649
  }
1923
1913
      return;
1924
1914
    }
1925
1915
  }
 
1916
#ifdef __GLIBC__
 
1917
#if __GLIBC_PREREQ(2, 15)
1926
1918
  int numhooks = scandirat(hookdir_fd, ".", &direntries,
1927
1919
                           runnable_hook, alphasort);
 
1920
#else  /* not __GLIBC_PREREQ(2, 15) */
 
1921
  int numhooks = scandir(hookdir, &direntries, runnable_hook,
 
1922
                         alphasort);
 
1923
#endif  /* not __GLIBC_PREREQ(2, 15) */
 
1924
#else   /* not __GLIBC__ */
 
1925
  int numhooks = scandir(hookdir, &direntries, runnable_hook,
 
1926
                         alphasort);
 
1927
#endif  /* not __GLIBC__ */
1928
1928
  if(numhooks == -1){
1929
1929
    perror_plus("scandir");
1930
1930
    return;
2085
2085
}
2086
2086
 
2087
2087
__attribute__((nonnull, warn_unused_result))
2088
 
int bring_up_interface(const char *const interface,
2089
 
                       const float delay){
2090
 
  int old_errno = errno;
 
2088
error_t bring_up_interface(const char *const interface,
 
2089
                           const float delay){
 
2090
  error_t old_errno = errno;
2091
2091
  int ret;
2092
2092
  struct ifreq network;
2093
2093
  unsigned int if_index = if_nametoindex(interface);
2103
2103
  }
2104
2104
  
2105
2105
  if(not interface_is_up(interface)){
2106
 
    int ret_errno = 0;
2107
 
    int ioctl_errno = 0;
 
2106
    error_t ret_errno = 0, ioctl_errno = 0;
2108
2107
    if(not get_flags(interface, &network)){
2109
2108
      ret_errno = errno;
2110
2109
      fprintf_plus(stderr, "Failed to get flags for interface "
2197
2196
  
2198
2197
  /* Sleep checking until interface is running.
2199
2198
     Check every 0.25s, up to total time of delay */
2200
 
  for(int i = 0; i < delay * 4; i++){
 
2199
  for(int i=0; i < delay * 4; i++){
2201
2200
    if(interface_is_running(interface)){
2202
2201
      break;
2203
2202
    }
2213
2212
}
2214
2213
 
2215
2214
__attribute__((nonnull, warn_unused_result))
2216
 
int take_down_interface(const char *const interface){
2217
 
  int old_errno = errno;
 
2215
error_t take_down_interface(const char *const interface){
 
2216
  error_t old_errno = errno;
2218
2217
  struct ifreq network;
2219
2218
  unsigned int if_index = if_nametoindex(interface);
2220
2219
  if(if_index == 0){
2223
2222
    return ENXIO;
2224
2223
  }
2225
2224
  if(interface_is_up(interface)){
2226
 
    int ret_errno = 0;
2227
 
    int ioctl_errno = 0;
 
2225
    error_t ret_errno = 0, ioctl_errno = 0;
2228
2226
    if(not get_flags(interface, &network) and debug){
2229
2227
      ret_errno = errno;
2230
2228
      fprintf_plus(stderr, "Failed to get flags for interface "
2480
2478
                         .args_doc = "",
2481
2479
                         .doc = "Mandos client -- Get and decrypt"
2482
2480
                         " passwords from a Mandos server" };
2483
 
    ret_errno = argp_parse(&argp, argc, argv,
2484
 
                           ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
2485
 
    switch(ret_errno){
 
2481
    ret = argp_parse(&argp, argc, argv,
 
2482
                     ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
 
2483
    switch(ret){
2486
2484
    case 0:
2487
2485
      break;
2488
2486
    case ENOMEM:
2489
2487
    default:
2490
 
      errno = ret_errno;
 
2488
      errno = ret;
2491
2489
      perror_plus("argp_parse");
2492
2490
      exitcode = EX_OSERR;
2493
2491
      goto end;
2499
2497
  
2500
2498
  {
2501
2499
    /* Work around Debian bug #633582:
2502
 
       <https://bugs.debian.org/633582> */
 
2500
       <http://bugs.debian.org/633582> */
2503
2501
    
2504
2502
    /* Re-raise privileges */
2505
 
    ret = raise_privileges();
2506
 
    if(ret != 0){
2507
 
      errno = ret;
 
2503
    ret_errno = raise_privileges();
 
2504
    if(ret_errno != 0){
 
2505
      errno = ret_errno;
2508
2506
      perror_plus("Failed to raise privileges");
2509
2507
    } else {
2510
2508
      struct stat st;
2574
2572
      }
2575
2573
      
2576
2574
      /* Lower privileges */
2577
 
      ret = lower_privileges();
2578
 
      if(ret != 0){
2579
 
        errno = ret;
 
2575
      ret_errno = lower_privileges();
 
2576
      if(ret_errno != 0){
 
2577
        errno = ret_errno;
2580
2578
        perror_plus("Failed to lower privileges");
2581
2579
      }
2582
2580
    }
2910
2908
    
2911
2909
    /* Allocate a new server */
2912
2910
    mc.server = avahi_server_new(avahi_simple_poll_get(simple_poll),
2913
 
                                 &config, NULL, NULL, &ret);
 
2911
                                 &config, NULL, NULL, &ret_errno);
2914
2912
    
2915
2913
    /* Free the Avahi configuration data */
2916
2914
    avahi_server_config_free(&config);
2919
2917
  /* Check if creating the Avahi server object succeeded */
2920
2918
  if(mc.server == NULL){
2921
2919
    fprintf_plus(stderr, "Failed to create Avahi server: %s\n",
2922
 
                 avahi_strerror(ret));
 
2920
                 avahi_strerror(ret_errno));
2923
2921
    exitcode = EX_UNAVAILABLE;
2924
2922
    goto end;
2925
2923
  }
2960
2958
 end:
2961
2959
  
2962
2960
  if(debug){
2963
 
    if(signal_received){
2964
 
      fprintf_plus(stderr, "%s exiting due to signal %d: %s\n",
2965
 
                   argv[0], signal_received,
2966
 
                   strsignal(signal_received));
2967
 
    } else {
2968
 
      fprintf_plus(stderr, "%s exiting\n", argv[0]);
2969
 
    }
 
2961
    fprintf_plus(stderr, "%s exiting\n", argv[0]);
2970
2962
  }
2971
2963
  
2972
2964
  /* Cleanup things */
2983
2975
  
2984
2976
  if(gnutls_initialized){
2985
2977
    gnutls_certificate_free_credentials(mc.cred);
 
2978
    gnutls_global_deinit();
2986
2979
    gnutls_dh_params_deinit(mc.dh_params);
2987
2980
  }
2988
2981
  
3011
3004
  
3012
3005
  /* Re-raise privileges */
3013
3006
  {
3014
 
    ret = raise_privileges();
3015
 
    if(ret != 0){
3016
 
      errno = ret;
 
3007
    ret_errno = raise_privileges();
 
3008
    if(ret_errno != 0){
 
3009
      errno = ret_errno;
3017
3010
      perror_plus("Failed to raise privileges");
3018
3011
    } else {
3019
3012
      
3024
3017
      /* Take down the network interfaces which were brought up */
3025
3018
      {
3026
3019
        char *interface = NULL;
3027
 
        while((interface = argz_next(interfaces_to_take_down,
3028
 
                                     interfaces_to_take_down_size,
3029
 
                                     interface))){
3030
 
          ret = take_down_interface(interface);
3031
 
          if(ret != 0){
3032
 
            errno = ret;
 
3020
        while((interface=argz_next(interfaces_to_take_down,
 
3021
                                   interfaces_to_take_down_size,
 
3022
                                   interface))){
 
3023
          ret_errno = take_down_interface(interface);
 
3024
          if(ret_errno != 0){
 
3025
            errno = ret_errno;
3033
3026
            perror_plus("Failed to take down interface");
3034
3027
          }
3035
3028
        }
3040
3033
      }
3041
3034
    }
3042
3035
    
3043
 
    ret = lower_privileges_permanently();
3044
 
    if(ret != 0){
3045
 
      errno = ret;
 
3036
    ret_errno = lower_privileges_permanently();
 
3037
    if(ret_errno != 0){
 
3038
      errno = ret_errno;
3046
3039
      perror_plus("Failed to lower privileges permanently");
3047
3040
    }
3048
3041
  }
3050
3043
  free(interfaces_to_take_down);
3051
3044
  free(interfaces_hooks);
3052
3045
  
3053
 
  void clean_dir_at(int base, const char * const dirname,
3054
 
                    uintmax_t level){
3055
 
    struct dirent **direntries = NULL;
3056
 
    int dret;
3057
 
    int dir_fd = (int)TEMP_FAILURE_RETRY(openat(base, dirname,
3058
 
                                                O_RDONLY
3059
 
                                                | O_NOFOLLOW
3060
 
                                                | O_DIRECTORY
3061
 
                                                | O_PATH));
3062
 
    if(dir_fd == -1){
3063
 
      perror_plus("open");
3064
 
    }
3065
 
    int numentries = scandirat(dir_fd, ".", &direntries,
3066
 
                               notdotentries, alphasort);
3067
 
    if(numentries >= 0){
3068
 
      for(int i = 0; i < numentries; i++){
3069
 
        if(debug){
3070
 
          fprintf_plus(stderr, "Unlinking \"%s/%s\"\n",
3071
 
                       dirname, direntries[i]->d_name);
3072
 
        }
3073
 
        dret = unlinkat(dir_fd, direntries[i]->d_name, 0);
3074
 
        if(dret == -1){
3075
 
          if(errno == EISDIR){
3076
 
              dret = unlinkat(dir_fd, direntries[i]->d_name,
3077
 
                              AT_REMOVEDIR);
3078
 
          }         
3079
 
          if((dret == -1) and (errno == ENOTEMPTY)
3080
 
             and (strcmp(direntries[i]->d_name, "private-keys-v1.d")
3081
 
                  == 0) and (level == 0)){
3082
 
            /* Recurse only in this special case */
3083
 
            clean_dir_at(dir_fd, direntries[i]->d_name, level+1);
3084
 
            dret = 0;
3085
 
          }
3086
 
          if(dret == -1){
3087
 
            fprintf_plus(stderr, "unlink(\"%s/%s\"): %s\n", dirname,
3088
 
                         direntries[i]->d_name, strerror(errno));
3089
 
          }
3090
 
        }
3091
 
        free(direntries[i]);
3092
 
      }
3093
 
      
3094
 
      /* need to clean even if 0 because man page doesn't specify */
3095
 
      free(direntries);
3096
 
      if(numentries == -1){
3097
 
        perror_plus("scandirat");
3098
 
      }
3099
 
      dret = unlinkat(base, dirname, AT_REMOVEDIR);
3100
 
      if(dret == -1 and errno != ENOENT){
3101
 
        perror_plus("rmdir");
3102
 
      }
3103
 
    } else {
3104
 
      perror_plus("scandirat");
3105
 
    }
3106
 
    close(dir_fd);
3107
 
  }
3108
 
  
3109
3046
  /* Removes the GPGME temp directory and all files inside */
3110
3047
  if(tempdir != NULL){
3111
 
    clean_dir_at(-1, tempdir, 0);
 
3048
    struct dirent **direntries = NULL;
 
3049
    int tempdir_fd = (int)TEMP_FAILURE_RETRY(open(tempdir, O_RDONLY
 
3050
                                                  | O_NOFOLLOW
 
3051
                                                  | O_DIRECTORY
 
3052
                                                  | O_PATH));
 
3053
    if(tempdir_fd == -1){
 
3054
      perror_plus("open");
 
3055
    } else {
 
3056
#ifdef __GLIBC__
 
3057
#if __GLIBC_PREREQ(2, 15)
 
3058
      int numentries = scandirat(tempdir_fd, ".", &direntries,
 
3059
                                 notdotentries, alphasort);
 
3060
#else  /* not __GLIBC_PREREQ(2, 15) */
 
3061
      int numentries = scandir(tempdir, &direntries, notdotentries,
 
3062
                               alphasort);
 
3063
#endif  /* not __GLIBC_PREREQ(2, 15) */
 
3064
#else   /* not __GLIBC__ */
 
3065
      int numentries = scandir(tempdir, &direntries, notdotentries,
 
3066
                               alphasort);
 
3067
#endif  /* not __GLIBC__ */
 
3068
      if(numentries >= 0){
 
3069
        for(int i = 0; i < numentries; i++){
 
3070
          ret = unlinkat(tempdir_fd, direntries[i]->d_name, 0);
 
3071
          if(ret == -1){
 
3072
            fprintf_plus(stderr, "unlinkat(open(\"%s\", O_RDONLY),"
 
3073
                         " \"%s\", 0): %s\n", tempdir,
 
3074
                         direntries[i]->d_name, strerror(errno));
 
3075
          }
 
3076
          free(direntries[i]);
 
3077
        }
 
3078
        
 
3079
        /* need to clean even if 0 because man page doesn't specify */
 
3080
        free(direntries);
 
3081
        if(numentries == -1){
 
3082
          perror_plus("scandir");
 
3083
        }
 
3084
        ret = rmdir(tempdir);
 
3085
        if(ret == -1 and errno != ENOENT){
 
3086
          perror_plus("rmdir");
 
3087
        }
 
3088
      }
 
3089
      close(tempdir_fd);
 
3090
    }
3112
3091
  }
3113
3092
  
3114
3093
  if(quit_now){