1 /**
2 * File Tree Walking EntropySource
3 * 
4 * Copyright:
5 * (C) 1999-2008 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.entropy.proc_walk;
12 
13 import botan.constants;
14 version(Posix):
15 static if (BOTAN_HAS_ENTROPY_SRC_PROC_WALKER):
16 
17 import botan.entropy.entropy_src;
18 import memutils.vector;
19 import botan.utils.types;
20 import core.stdc.string;
21 import core.stdc.config;
22 import core.sys.posix.sys.types;
23 import core.sys.posix.sys.stat;
24 import core.sys.posix.fcntl;
25 import core.sys.posix.unistd;
26 import core.sys.posix.dirent;
27 import std.string : toStringz;
28 import std.array;
29 
30 final class DirectoryWalker : FileDescriptorSource
31 {
32 public:
33     this(in string root) 
34     {
35         m_cur_dir = makePair!(DIR*, string)(null, "");
36         if (DIR* root_dir = opendir(root.toStringz))
37             m_cur_dir = makePair(root_dir, root);
38     }
39     
40     ~this()
41     {
42         if (m_cur_dir.first)
43             closedir(m_cur_dir.first);
44     }
45     
46     override int nextFd() 
47     {
48         while (true)
49         {
50             Pair!(dirent*, string) entry = getNextDirent();
51             
52             if (!entry.first)
53                 break; // no more dirs
54             
55             const string filename = cast(string) entry.first.d_name[0 .. strlen(entry.first.d_name.ptr)];
56             
57             if (filename == "." || filename == "..")
58                 continue;
59             
60             const string full_path = entry.second ~ '/' ~ filename;
61             
62             stat_t stat_buf;
63             if (.lstat(full_path.toStringz, &stat_buf) == -1)
64                 continue;
65             
66             if (S_ISDIR(stat_buf.st_mode))
67             {
68                 addDirectory(full_path);
69             }
70             else if (S_ISREG(stat_buf.st_mode) && (stat_buf.st_mode & S_IROTH))
71             {
72                 int fd = .open(full_path.toStringz, O_RDONLY | O_NOCTTY);
73                 
74                 if (fd > 0)
75                     return fd;
76             }
77         }
78         
79         return -1;
80     }
81 
82 private:
83     void addDirectory(in string dirname)
84     {
85         m_dirlist.insertBack(dirname);
86     }
87     
88     Pair!(dirent*, string) getNextDirent()
89     {
90         while (m_cur_dir.first)
91         {
92             if (dirent* dir = readdir(m_cur_dir.first))
93                 return makePair(dir, m_cur_dir.second);
94             
95             closedir(m_cur_dir.first);
96             m_cur_dir = makePair!(DIR*, string)(null, "");
97             
98             while (!m_dirlist.empty && !m_cur_dir.first)
99             {
100                 const string next_dir_name = m_dirlist.front;
101                 m_dirlist = Vector!string(m_dirlist[1 .. $]);
102                 
103                 if (DIR* next_dir = opendir(next_dir_name.toStringz))
104                     m_cur_dir = makePair(next_dir, next_dir_name);
105             }
106         }
107         
108         return Pair!(dirent*, string)(); // nothing left
109     }
110     
111     Pair!(DIR*, string) m_cur_dir;
112     Vector!string m_dirlist;
113 }
114 
115 
116 interface FileDescriptorSource
117 {
118 public:
119     abstract int nextFd();
120 
121 }
122 
123 /**
124 * File Tree Walking Entropy Source
125 */
126 final class ProcWalkingEntropySource : EntropySource
127 {
128 public:
129     @property string name() const { return "Proc Walker"; }
130 
131     void poll(ref EntropyAccumulator accum)
132     {
133         __gshared immutable size_t MAX_FILES_READ_PER_POLL = 2048;
134         const double ENTROPY_ESTIMATE = 1.0 / (8*1024);
135         
136         if (!m_dir)
137             m_dir = new DirectoryWalker(m_path);
138         
139         SecureVector!ubyte* io_buffer = &accum.getIoBuffer(4096);
140         foreach (size_t i; 0 .. MAX_FILES_READ_PER_POLL)
141         {
142             int fd = m_dir.nextFd();
143             
144             // If we've exhaused this walk of the directory, halt the poll
145             if (fd == -1)
146             {
147                 destroy(m_dir);
148                 m_dir = null;
149                 break;
150             }
151             
152             ssize_t got = .read(fd, io_buffer.ptr, 4096);
153             close(fd);
154             
155             if (got > 0)
156                 accum.add(io_buffer.ptr, got, ENTROPY_ESTIMATE);
157             
158             if (accum.pollingGoalAchieved())
159                 break;
160         }
161     }
162 
163     this(in string root_dir)
164     {
165         m_path = root_dir;
166     }
167 
168     ~this() { if (m_dir) destroy(m_dir); }
169 
170 private:
171     const string m_path;
172     FileDescriptorSource m_dir;
173 }
174 
175 @nogc nothrow pure private:
176 bool S_ISTYPE( mode_t mode, uint mask ) { return ( mode & S_IFMT ) == mask; }
177 bool S_ISDIR( mode_t mode )  { return S_ISTYPE( mode, S_IFDIR );  }
178 bool S_ISREG( mode_t mode )  { return S_ISTYPE( mode, S_IFREG );  }