1 /*
2 * Copyright (C) 2018 Rafael Ostertag
3 *
4 * This file is part of YAPET.
5 *
6 * YAPET is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
9 * version.
10 *
11 * YAPET is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14 * details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * YAPET. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Additional permission under GNU GPL version 3 section 7
20 *
21 * If you modify this program, or any covered work, by linking or combining it
22 * with the OpenSSL project's OpenSSL library (or a modified version of that
23 * library), containing parts covered by the terms of the OpenSSL or SSLeay
24 * licenses, Rafael Ostertag grants you additional permission to convey the
25 * resulting work. Corresponding Source for a non-source form of such a
26 * combination shall include the source code for the parts of OpenSSL used as
27 * well as that of the covered work.
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 #include <unistd.h>
35
36 #include <cstdio>
37 #include <cstring>
38 #include <fstream>
39 #include <iostream>
40
41 #include "aes256factory.hh"
42 #include "consts.h"
43 #include "csvimport.h"
44 #include "csvline.hh"
45 #include "file.h"
46 #include "intl.h"
47
48 constexpr char NEW_LINE_CHARACTER{'\n'};
49 constexpr char DOUBLE_QUOTE{'"'};
50
51 constexpr auto NUMBER_OF_FIELDS{5};
52 // the max line length. Computed from the field sizes of a YAPET password
53 // record.
54 constexpr int MAX_LINE_LENGTH{
55 yapet::PasswordRecord::NAME_SIZE + yapet::PasswordRecord::HOST_SIZE +
56 yapet::PasswordRecord::USERNAME_SIZE +
57 yapet::PasswordRecord::PASSWORD_SIZE + yapet::PasswordRecord::COMMENT_SIZE +
58 // for the separators
59 NUMBER_OF_FIELDS -
60 1
61 // null terminators, one for each field
62 - 5};
63
logError(unsigned long lno,const std::string & errmsg)64 void CSVImport::logError(unsigned long lno, const std::string& errmsg) {
65 if (verbose) {
66 std::cout << 'e';
67 std::cout.flush();
68 }
69
70 LogEntry tmp;
71 tmp.lineNumber = lno;
72 tmp.message = errmsg;
73
74 logs.push_back(tmp);
75 had_errors = true;
76 num_errors++;
77 }
78
csvLineToPasswordRecord(yapet::CSVLine & csvLine,std::unique_ptr<yapet::Crypto> & crypto)79 yapet::PasswordListItem CSVImport::csvLineToPasswordRecord(
80 yapet::CSVLine& csvLine, std::unique_ptr<yapet::Crypto>& crypto) {
81 yapet::PasswordRecord passwordRecord;
82
83 passwordRecord.name(csvLine[0].c_str());
84 passwordRecord.host(csvLine[1].c_str());
85 passwordRecord.username(csvLine[2].c_str());
86 passwordRecord.password(csvLine[3].c_str());
87 passwordRecord.comment(csvLine[4].c_str());
88
89 auto serializedRecord{passwordRecord.serialize()};
90 auto encryptedRecord{crypto->encrypt(serializedRecord)};
91
92 return yapet::PasswordListItem{csvLine[0].c_str(), encryptedRecord};
93 }
94
95 /**
96 * The constructor tests whether the given source file exists and can be
97 * read. May return a \c std::runtime_error if this is not the case
98 *
99 * @param src the file path of the source file.
100 *
101 * @param dst the file path of the destination file.
102 *
103 * @param sep the separator used for fields.
104 *
105 * @param verb enable/disable verbosity. Default \c true.
106 */
107
CSVImport(std::string src,std::string dst,char sep,bool verb)108 CSVImport::CSVImport(std::string src, std::string dst, char sep, bool verb)
109 : srcfile(src),
110 dstfile(dst),
111 separator(sep),
112 verbose(verb),
113 had_errors(false),
114 num_errors(0) {
115 if (access(srcfile.c_str(), R_OK | F_OK) == -1) {
116 char msg[YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE];
117 std::snprintf(msg, YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE,
118 _("Cannot access '%s'"), srcfile.c_str());
119 throw std::runtime_error(msg);
120 }
121 }
122
123 namespace {
openCsvFile(const std::string & fileName)124 inline std::ifstream openCsvFile(const std::string& fileName) {
125 std::ifstream file{fileName};
126
127 if (!file) {
128 char msg[YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE];
129 std::snprintf(msg, YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE,
130 _("Cannot open '%s'"), fileName.c_str());
131 throw std::runtime_error(msg);
132 }
133
134 return file;
135 }
136
read(std::ifstream & csvFile,char & lookAhead)137 inline char read(std::ifstream& csvFile, char& lookAhead) {
138 char tmp = lookAhead;
139 csvFile.get(lookAhead);
140 return tmp;
141 }
142
143 /**
144 * Read a line and honor escaped newline character.
145 */
readLine(std::ifstream & csvFile,char separator,CSVImport::line_number_type & lineNumber)146 inline std::string readLine(std::ifstream& csvFile, char separator,
147 CSVImport::line_number_type& lineNumber) {
148 std::string line;
149
150 bool inEscapedField = false;
151 bool startOfLine = true;
152
153 char currentCharacter;
154 char lookAhead;
155
156 csvFile.get(lookAhead);
157 while (!csvFile.eof()) {
158 currentCharacter = read(csvFile, lookAhead);
159
160 if (currentCharacter == NEW_LINE_CHARACTER) {
161 lineNumber++;
162 }
163
164 if (currentCharacter == NEW_LINE_CHARACTER && !inEscapedField) {
165 csvFile.putback(lookAhead);
166 csvFile.clear();
167 break;
168 }
169
170 if (startOfLine && currentCharacter == DOUBLE_QUOTE) {
171 startOfLine = false;
172 inEscapedField = true;
173 line += DOUBLE_QUOTE;
174 continue;
175 }
176
177 if (currentCharacter == DOUBLE_QUOTE && lookAhead == DOUBLE_QUOTE &&
178 inEscapedField) {
179 line += currentCharacter;
180 line += read(csvFile, lookAhead);
181 continue;
182 }
183
184 if (currentCharacter == DOUBLE_QUOTE &&
185 (lookAhead == separator || lookAhead == NEW_LINE_CHARACTER)) {
186 inEscapedField = false;
187 line += currentCharacter;
188 continue;
189 }
190
191 if (currentCharacter == separator && !inEscapedField) {
192 line += currentCharacter;
193
194 if (lookAhead == DOUBLE_QUOTE) {
195 inEscapedField = true;
196 line += read(csvFile, lookAhead);
197 }
198 continue;
199 }
200
201 line += currentCharacter;
202 startOfLine = false;
203 }
204
205 if (inEscapedField) {
206 throw std::invalid_argument(_("'\"' mismatch"));
207 }
208
209 return line;
210 }
211
212 } // namespace
213
214 /**
215 * Does the import.
216 *
217 * @param pw the password set on the destination file.
218 */
import(const char * pw)219 void CSVImport::import(const char* pw) {
220 auto csvFile{::openCsvFile(srcfile)};
221
222 auto password{yapet::toSecureArray(pw)};
223 auto keyingParameters{yapet::Key256::newDefaultKeyingParameters()};
224 std::shared_ptr<yapet::AbstractCryptoFactory> cryptoFactory{
225 new yapet::Aes256Factory{password, keyingParameters}};
226 auto crypto{cryptoFactory->crypto()};
227 std::unique_ptr<YAPET::File> yapetFile{
228 new YAPET::File{cryptoFactory, dstfile, true}};
229
230 std::list<yapet::PasswordListItem> list;
231
232 line_number_type lineNumber = 0;
233
234 yapet::CSVLine csvLine{NUMBER_OF_FIELDS, separator};
235
236 for (std::string line;
237 line = ::readLine(csvFile, separator, lineNumber), !csvFile.eof();) {
238 if (line.size() > MAX_LINE_LENGTH) {
239 logError(lineNumber, _("Line too long"));
240 continue;
241 }
242
243 try {
244 csvLine.parseLine(line);
245 } catch (std::exception& e) {
246 logError(lineNumber, e.what());
247 continue;
248 }
249
250 auto passwordListItem{csvLineToPasswordRecord(csvLine, crypto)};
251 list.push_back(passwordListItem);
252
253 if (verbose) {
254 std::cout << ".";
255 std::cout.flush();
256 }
257 }
258
259 if (verbose) std::cout << std::endl;
260
261 yapetFile->save(list);
262 csvFile.close();
263 }
264
265 /**
266 * Prints the log entries to stdout.
267 */
printLog() const268 void CSVImport::printLog() const {
269 if (logs.size() == 0) return;
270
271 std::list<LogEntry>::const_iterator it = logs.begin();
272
273 while (it != logs.end()) {
274 std::cout << _("Line ") << (*it).lineNumber << ": " << (*it).message
275 << std::endl;
276 it++;
277 }
278 }
279