1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2000-2001 Dawit Alemayehu <adawit@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "authinfo.h"
9 
10 #ifndef KIO_ANDROID_STUB
11 #include <QDBusArgument>
12 #include <QDBusMetaType>
13 #endif
14 #include <QDataStream>
15 #include <QDir>
16 #include <QFile>
17 #include <QTextStream>
18 
19 #include <QStandardPaths>
20 
21 using namespace KIO;
22 
23 //////
24 
25 class ExtraField
26 {
27 public:
ExtraField()28     ExtraField()
29         : flags(AuthInfo::ExtraFieldNoFlags)
30     {
31     }
32 
ExtraField(const ExtraField & other)33     ExtraField(const ExtraField &other)
34         : customTitle(other.customTitle)
35         , flags(other.flags)
36         , value(other.value)
37     {
38     }
39 
operator =(const ExtraField & other)40     ExtraField &operator=(const ExtraField &other)
41     {
42         customTitle = other.customTitle;
43         flags = other.flags;
44         value = other.value;
45         return *this;
46     }
47 
48     QString customTitle; // reserved for future use
49     AuthInfo::FieldFlags flags;
50     QVariant value;
51 };
52 Q_DECLARE_METATYPE(ExtraField)
53 
54 static QDataStream &operator<<(QDataStream &s, const ExtraField &extraField)
55 {
56     s << extraField.customTitle;
57     s << static_cast<int>(extraField.flags);
58     s << extraField.value;
59     return s;
60 }
61 
operator >>(QDataStream & s,ExtraField & extraField)62 static QDataStream &operator>>(QDataStream &s, ExtraField &extraField)
63 {
64     s >> extraField.customTitle;
65     int i;
66     s >> i;
67     extraField.flags = AuthInfo::FieldFlags(i);
68     s >> extraField.value;
69     return s;
70 }
71 
72 #ifndef KIO_ANDROID_STUB
operator <<(QDBusArgument & argument,const ExtraField & extraField)73 static QDBusArgument &operator<<(QDBusArgument &argument, const ExtraField &extraField)
74 {
75     argument.beginStructure();
76     argument << extraField.customTitle << static_cast<int>(extraField.flags) << QDBusVariant(extraField.value);
77     argument.endStructure();
78     return argument;
79 }
80 
operator >>(const QDBusArgument & argument,ExtraField & extraField)81 static const QDBusArgument &operator>>(const QDBusArgument &argument, ExtraField &extraField)
82 {
83     QDBusVariant value;
84     int flag;
85 
86     argument.beginStructure();
87     argument >> extraField.customTitle >> flag >> value;
88     argument.endStructure();
89 
90     extraField.value = value.variant();
91     extraField.flags = KIO::AuthInfo::FieldFlags(flag);
92     return argument;
93 }
94 #endif
95 
96 class KIO::AuthInfoPrivate
97 {
98 public:
99     QMap<QString, ExtraField> extraFields;
100 };
101 
102 //////
103 
AuthInfo()104 AuthInfo::AuthInfo()
105     : d(new AuthInfoPrivate())
106 {
107     modified = false;
108     readOnly = false;
109     verifyPath = false;
110     keepPassword = false;
111     AuthInfo::registerMetaTypes();
112 }
113 
AuthInfo(const AuthInfo & info)114 AuthInfo::AuthInfo(const AuthInfo &info)
115     : d(new AuthInfoPrivate())
116 {
117     (*this) = info;
118     AuthInfo::registerMetaTypes();
119 }
120 
~AuthInfo()121 AuthInfo::~AuthInfo()
122 {
123     delete d;
124 }
125 
operator =(const AuthInfo & info)126 AuthInfo &AuthInfo::operator=(const AuthInfo &info)
127 {
128     url = info.url;
129     username = info.username;
130     password = info.password;
131     prompt = info.prompt;
132     caption = info.caption;
133     comment = info.comment;
134     commentLabel = info.commentLabel;
135     realmValue = info.realmValue;
136     digestInfo = info.digestInfo;
137     verifyPath = info.verifyPath;
138     readOnly = info.readOnly;
139     keepPassword = info.keepPassword;
140     modified = info.modified;
141     d->extraFields = info.d->extraFields;
142     return *this;
143 }
144 
isModified() const145 bool AuthInfo::isModified() const
146 {
147     return modified;
148 }
149 
setModified(bool flag)150 void AuthInfo::setModified(bool flag)
151 {
152     modified = flag;
153 }
154 
155 /////
156 
setExtraField(const QString & fieldName,const QVariant & value)157 void AuthInfo::setExtraField(const QString &fieldName, const QVariant &value)
158 {
159     d->extraFields[fieldName].value = value;
160 }
161 
setExtraFieldFlags(const QString & fieldName,const FieldFlags flags)162 void AuthInfo::setExtraFieldFlags(const QString &fieldName, const FieldFlags flags)
163 {
164     d->extraFields[fieldName].flags = flags;
165 }
166 
getExtraField(const QString & fieldName) const167 QVariant AuthInfo::getExtraField(const QString &fieldName) const
168 {
169     const auto it = d->extraFields.constFind(fieldName);
170     if (it == d->extraFields.constEnd()) {
171         return QVariant();
172     }
173     return it->value;
174 }
175 
getExtraFieldFlags(const QString & fieldName) const176 AuthInfo::FieldFlags AuthInfo::getExtraFieldFlags(const QString &fieldName) const
177 {
178     const auto it = d->extraFields.constFind(fieldName);
179     if (it == d->extraFields.constEnd()) {
180         return AuthInfo::ExtraFieldNoFlags;
181     }
182     return it->flags;
183 }
184 
registerMetaTypes()185 void AuthInfo::registerMetaTypes()
186 {
187     qRegisterMetaType<ExtraField>();
188     qRegisterMetaType<KIO::AuthInfo>();
189 #ifndef KIO_ANDROID_STUB
190     qDBusRegisterMetaType<ExtraField>();
191     qDBusRegisterMetaType<KIO::AuthInfo>();
192 #endif
193 }
194 
195 /////
196 
operator <<(QDataStream & s,const AuthInfo & a)197 QDataStream &KIO::operator<<(QDataStream &s, const AuthInfo &a)
198 {
199     s << quint8(1) << a.url << a.username << a.password << a.prompt << a.caption << a.comment << a.commentLabel << a.realmValue << a.digestInfo << a.verifyPath
200       << a.readOnly << a.keepPassword << a.modified << a.d->extraFields;
201     return s;
202 }
203 
operator >>(QDataStream & s,AuthInfo & a)204 QDataStream &KIO::operator>>(QDataStream &s, AuthInfo &a)
205 {
206     quint8 version;
207     s >> version >> a.url >> a.username >> a.password >> a.prompt >> a.caption >> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo >> a.verifyPath
208         >> a.readOnly >> a.keepPassword >> a.modified >> a.d->extraFields;
209     return s;
210 }
211 
212 #ifndef KIO_ANDROID_STUB
operator <<(QDBusArgument & argument,const AuthInfo & a)213 QDBusArgument &KIO::operator<<(QDBusArgument &argument, const AuthInfo &a)
214 {
215     argument.beginStructure();
216     argument << quint8(1) << a.url.toString() << a.username << a.password << a.prompt << a.caption << a.comment << a.commentLabel << a.realmValue
217              << a.digestInfo << a.verifyPath << a.readOnly << a.keepPassword << a.modified << a.d->extraFields;
218     argument.endStructure();
219     return argument;
220 }
221 
operator >>(const QDBusArgument & argument,AuthInfo & a)222 const QDBusArgument &KIO::operator>>(const QDBusArgument &argument, AuthInfo &a)
223 {
224     QString url;
225     quint8 version;
226 
227     argument.beginStructure();
228     argument >> version >> url >> a.username >> a.password >> a.prompt >> a.caption >> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo
229         >> a.verifyPath >> a.readOnly >> a.keepPassword >> a.modified >> a.d->extraFields;
230     argument.endStructure();
231 
232     a.url = QUrl(url);
233     return argument;
234 }
235 #endif
236 
237 typedef QList<NetRC::AutoLogin> LoginList;
238 typedef QMap<QString, LoginList> LoginMap;
239 
240 class Q_DECL_HIDDEN NetRC::NetRCPrivate
241 {
242 public:
NetRCPrivate()243     NetRCPrivate()
244         : isDirty(false)
245         , index(-1)
246     {
247     }
248     QString extract(const QString &buf, const QString &key);
249     void getMachinePart(const QString &line);
250     void getMacdefPart(const QString &line);
251 
252     bool isDirty;
253     LoginMap loginMap;
254     QTextStream fstream;
255     QString type;
256     int index;
257 };
258 
259 NetRC *NetRC::instance = nullptr;
260 
NetRC()261 NetRC::NetRC()
262     : d(new NetRCPrivate)
263 {
264 }
265 
~NetRC()266 NetRC::~NetRC()
267 {
268     delete instance;
269     instance = nullptr;
270     delete d;
271 }
272 
self()273 NetRC *NetRC::self()
274 {
275     if (!instance) {
276         instance = new NetRC;
277     }
278     return instance;
279 }
280 
lookup(const QUrl & url,AutoLogin & login,bool userealnetrc,const QString & _type,LookUpMode mode)281 bool NetRC::lookup(const QUrl &url, AutoLogin &login, bool userealnetrc, const QString &_type, LookUpMode mode)
282 {
283     // qDebug() << "AutoLogin lookup for: " << url.host();
284     if (!url.isValid()) {
285         return false;
286     }
287 
288     QString type = _type;
289     if (type.isEmpty()) {
290         type = url.scheme();
291     }
292 
293     if (d->loginMap.isEmpty() || d->isDirty) {
294         d->loginMap.clear();
295 
296         QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kionetrc");
297         bool kionetrcStatus = parse(filename);
298         bool netrcStatus = false;
299         if (userealnetrc) {
300             filename = QDir::homePath() + QLatin1String("/.netrc");
301             netrcStatus = parse(filename);
302         }
303 
304         if (!(kionetrcStatus || netrcStatus)) {
305             return false;
306         }
307     }
308 
309     const auto loginIt = d->loginMap.constFind(type);
310     if (loginIt == d->loginMap.constEnd()) {
311         return false;
312     }
313 
314     const LoginList &l = *loginIt;
315     if (l.isEmpty()) {
316         return false;
317     }
318 
319     for (const AutoLogin &log : l) {
320         if ((mode & defaultOnly) == defaultOnly && log.machine == QLatin1String("default") && (login.login.isEmpty() || login.login == log.login)) {
321             login.type = log.type;
322             login.machine = log.machine;
323             login.login = log.login;
324             login.password = log.password;
325             login.macdef = log.macdef;
326         }
327 
328         if ((mode & presetOnly) == presetOnly && log.machine == QLatin1String("preset") && (login.login.isEmpty() || login.login == log.login)) {
329             login.type = log.type;
330             login.machine = log.machine;
331             login.login = log.login;
332             login.password = log.password;
333             login.macdef = log.macdef;
334         }
335 
336         if ((mode & exactOnly) == exactOnly && log.machine == url.host() && (login.login.isEmpty() || login.login == log.login)) {
337             login.type = log.type;
338             login.machine = log.machine;
339             login.login = log.login;
340             login.password = log.password;
341             login.macdef = log.macdef;
342             break;
343         }
344     }
345 
346     return true;
347 }
348 
reload()349 void NetRC::reload()
350 {
351     d->isDirty = true;
352 }
353 
parse(const QString & fileName)354 bool NetRC::parse(const QString &fileName)
355 {
356     QFile file(fileName);
357     if (file.permissions() != (QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)) {
358         return false;
359     }
360     if (!file.open(QIODevice::ReadOnly)) {
361         return false;
362     }
363 
364     d->fstream.setDevice(&file);
365 
366     QString line;
367 
368     while (!d->fstream.atEnd()) {
369         line = d->fstream.readLine().simplified();
370 
371         // If line is a comment or is empty, read next line
372         if ((line.startsWith(QLatin1Char('#')) || line.isEmpty())) {
373             continue;
374         }
375 
376         // If line refers to a machine, maybe it is spread in more lines.
377         // getMachinePart() will take care of getting all the info and putting it into loginMap.
378         if ((line.startsWith(QLatin1String("machine")) || line.startsWith(QLatin1String("default")) || line.startsWith(QLatin1String("preset")))) {
379             d->getMachinePart(line);
380             continue;
381         }
382 
383         // If line refers to a macdef, it will be more than one line.
384         // getMacdefPart() will take care of getting all the lines of the macro
385         // and putting them into loginMap
386         if (line.startsWith(QLatin1String("macdef"))) {
387             d->getMacdefPart(line);
388             continue;
389         }
390     }
391     return true;
392 }
393 
extract(const QString & buf,const QString & key)394 QString NetRC::NetRCPrivate::extract(const QString &buf, const QString &key)
395 {
396     QStringList stringList = buf.split(QLatin1Char(' '), Qt::SkipEmptyParts);
397     int i = stringList.indexOf(key);
398     if ((i != -1) && (i + 1 < stringList.size())) {
399         return stringList.at(i + 1);
400     } else {
401         return QString();
402     }
403 }
404 
getMachinePart(const QString & line)405 void NetRC::NetRCPrivate::getMachinePart(const QString &line)
406 {
407     QString buf = line;
408     while (!(buf.contains(QLatin1String("login"))
409              && (buf.contains(QLatin1String("password")) || buf.contains(QLatin1String("account")) || buf.contains(QLatin1String("type"))))) {
410         buf += QLatin1Char(' ') + fstream.readLine().simplified();
411     }
412 
413     // Once we've got all the info, process it.
414     AutoLogin l;
415     l.machine = extract(buf, QStringLiteral("machine"));
416     if (l.machine.isEmpty()) {
417         if (buf.contains(QLatin1String("default"))) {
418             l.machine = QStringLiteral("default");
419         } else if (buf.contains(QLatin1String("preset"))) {
420             l.machine = QStringLiteral("preset");
421         }
422     }
423 
424     l.login = extract(buf, QStringLiteral("login"));
425     l.password = extract(buf, QStringLiteral("password"));
426     if (l.password.isEmpty()) {
427         l.password = extract(buf, QStringLiteral("account"));
428     }
429 
430     type = l.type = extract(buf, QStringLiteral("type"));
431     if (l.type.isEmpty() && !l.machine.isEmpty()) {
432         type = l.type = QStringLiteral("ftp");
433     }
434 
435     loginMap[l.type].append(l);
436     index = loginMap[l.type].count() - 1;
437 }
438 
getMacdefPart(const QString & line)439 void NetRC::NetRCPrivate::getMacdefPart(const QString &line)
440 {
441     QString buf = line;
442     QString macro = extract(buf, QStringLiteral("macdef"));
443     QString newLine;
444     while (!fstream.atEnd()) {
445         newLine = fstream.readLine().simplified();
446         if (!newLine.isEmpty()) {
447             buf += QLatin1Char('\n') + newLine;
448         } else {
449             break;
450         }
451     }
452     loginMap[type][index].macdef[macro].append(buf);
453 }
454