Libkleo

dn.cpp
1 /*
2  dn.cpp
3 
4  This file is part of libkleopatra, the KDE keymanagement library
5  SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6  SPDX-FileCopyrightText: 2021 g10 Code GmbH
7  SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8 
9  DN parsing:
10  SPDX-FileCopyrightText: 2002 g10 Code GmbH
11  SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
12 
13  SPDX-License-Identifier: GPL-2.0-or-later
14 */
15 
16 #include <config-libkleo.h>
17 
18 #include "dn.h"
19 #include "libkleo_debug.h"
20 
21 #include "oidmap.h"
22 
23 #include <KLazyLocalizedString>
24 
25 #include <algorithm>
26 
27 #ifdef _MSC_VER
28 #include <string.h>
29 #define strcasecmp _stricmp
30 #endif
31 
32 namespace
33 {
34 static const QStringList defaultOrder = {
35  QStringLiteral("CN"),
36  QStringLiteral("L"),
37  QStringLiteral("_X_"),
38  QStringLiteral("OU"),
39  QStringLiteral("O"),
40  QStringLiteral("C"),
41 };
42 
43 class DNAttributeOrderStore
44 {
45  DNAttributeOrderStore()
46  : mAttributeOrder{defaultOrder}
47  {
48  }
49 
50 public:
51  static DNAttributeOrderStore *instance()
52  {
53  static DNAttributeOrderStore *self = new DNAttributeOrderStore();
54  return self;
55  }
56 
57  const QStringList &attributeOrder() const
58  {
59  return mAttributeOrder.empty() ? defaultOrder : mAttributeOrder;
60  }
61 
62  void setAttributeOrder(const QStringList &order)
63  {
64  mAttributeOrder = order;
65  }
66 
67 private:
68  QStringList mAttributeOrder;
69 };
70 }
71 
72 class Kleo::DN::Private
73 {
74 public:
75  Private()
76  : mRefCount(0)
77  {
78  }
79  Private(const Private &other)
80  : attributes(other.attributes)
81  , reorderedAttributes(other.reorderedAttributes)
82  , mRefCount(0)
83  {
84  }
85 
86  int ref()
87  {
88  return ++mRefCount;
89  }
90 
91  int unref()
92  {
93  if (--mRefCount <= 0) {
94  delete this;
95  return 0;
96  } else {
97  return mRefCount;
98  }
99  }
100 
101  int refCount() const
102  {
103  return mRefCount;
104  }
105 
106  DN::Attribute::List attributes;
107  DN::Attribute::List reorderedAttributes;
108 
109 private:
110  int mRefCount;
111 };
112 
113 namespace
114 {
115 struct DnPair {
116  char *key;
117  char *value;
118 };
119 }
120 
121 // copied from CryptPlug and adapted to work on DN::Attribute::List:
122 
123 #define digitp(p) (*(p) >= '0' && *(p) <= '9')
124 #define hexdigitp(a) (digitp(a) || (*(a) >= 'A' && *(a) <= 'F') || (*(a) >= 'a' && *(a) <= 'f'))
125 #define xtoi_1(p) (*(p) <= '9' ? (*(p) - '0') : *(p) <= 'F' ? (*(p) - 'A' + 10) : (*(p) - 'a' + 10))
126 #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p) + 1))
127 
128 static char *trim_trailing_spaces(char *string)
129 {
130  char *p;
131  char *mark;
132 
133  for (mark = nullptr, p = string; *p; p++) {
134  if (isspace(*p)) {
135  if (!mark) {
136  mark = p;
137  }
138  } else {
139  mark = nullptr;
140  }
141  }
142  if (mark) {
143  *mark = '\0';
144  }
145 
146  return string;
147 }
148 
149 /* Parse a DN and return an array-ized one. This is not a validating
150  parser and it does not support any old-stylish syntax; gpgme is
151  expected to return only rfc2253 compatible strings. */
152 static const unsigned char *parse_dn_part(DnPair *array, const unsigned char *string)
153 {
154  const unsigned char *s;
155  const unsigned char *s1;
156  size_t n;
157  char *p;
158 
159  /* parse attributeType */
160  for (s = string + 1; *s && *s != '='; s++) {
161  ;
162  }
163  if (!*s) {
164  return nullptr; /* error */
165  }
166  n = s - string;
167  if (!n) {
168  return nullptr; /* empty key */
169  }
170  p = (char *)malloc(n + 1);
171 
172  memcpy(p, string, n);
173  p[n] = 0;
174  trim_trailing_spaces((char *)p);
175  // map OIDs to their names:
176  if (const char *name = Kleo::attributeNameForOID(p)) {
177  free(p);
178  p = strdup(name);
179  }
180  array->key = p;
181  string = s + 1;
182 
183  if (*string == '#') {
184  /* hexstring */
185  string++;
186  for (s = string; hexdigitp(s); s++)
187  ;
188  n = s - string;
189  if (!n || (n & 1)) {
190  return nullptr; /* empty or odd number of digits */
191  }
192  n /= 2;
193  array->value = p = (char *)malloc(n + 1);
194 
195  for (s1 = string; n; s1 += 2, n--) {
196  *p++ = xtoi_2(s1);
197  }
198  *p = 0;
199  } else {
200  /* regular v3 quoted string */
201  for (n = 0, s = string; *s; s++) {
202  if (*s == '\\') {
203  /* pair */
204  s++;
205  if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';' || *s == '\\' || *s == '\"' || *s == ' ') {
206  n++;
207  } else if (hexdigitp(s) && hexdigitp(s + 1)) {
208  s++;
209  n++;
210  } else {
211  return nullptr; /* invalid escape sequence */
212  }
213  } else if (*s == '\"') {
214  return nullptr; /* invalid encoding */
215  } else if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';') {
216  break;
217  } else {
218  n++;
219  }
220  }
221 
222  array->value = p = (char *)malloc(n + 1);
223 
224  for (s = string; n; s++, n--) {
225  if (*s == '\\') {
226  s++;
227  if (hexdigitp(s)) {
228  *p++ = xtoi_2(s);
229  s++;
230  } else {
231  *p++ = *s;
232  }
233  } else {
234  *p++ = *s;
235  }
236  }
237  *p = 0;
238  }
239  return s;
240 }
241 
242 /* Parse a DN and return an array-ized one. This is not a validating
243  parser and it does not support any old-stylish syntax; gpgme is
244  expected to return only rfc2253 compatible strings. */
245 static Kleo::DN::Attribute::List parse_dn(const unsigned char *string)
246 {
247  if (!string) {
249  }
250 
252  while (*string) {
253  while (*string == ' ') {
254  string++;
255  }
256  if (!*string) {
257  break; /* ready */
258  }
259 
260  DnPair pair = {nullptr, nullptr};
261  string = parse_dn_part(&pair, string);
262  if (!string) {
263  goto failure;
264  }
265  if (pair.key && pair.value) {
266  result.push_back(Kleo::DN::Attribute(QString::fromUtf8(pair.key), QString::fromUtf8(pair.value)));
267  }
268  free(pair.key);
269  free(pair.value);
270 
271  while (*string == ' ') {
272  string++;
273  }
274  if (*string && *string != ',' && *string != ';' && *string != '+') {
275  goto failure; /* invalid delimiter */
276  }
277  if (*string) {
278  string++;
279  }
280  }
281  return result;
282 
283 failure:
285 }
286 
287 static QList<Kleo::DN::Attribute> parse_dn(const QString &dn)
288 {
289  return parse_dn((const unsigned char *)dn.toUtf8().data());
290 }
291 
292 static QString dn_escape(const QString &s)
293 {
294  QString result;
295  for (int i = 0, end = s.length(); i != end; ++i) {
296  const QChar ch = s[i];
297  switch (ch.unicode()) {
298  case ',':
299  case '+':
300  case '"':
301  case '\\':
302  case '<':
303  case '>':
304  case ';':
305  result += QLatin1Char('\\');
306  // fall through
307  [[fallthrough]];
308  default:
309  result += ch;
310  }
311  }
312  return result;
313 }
314 
315 static QString serialise(const QList<Kleo::DN::Attribute> &dn, const QString &sep)
316 {
317  QStringList result;
318  for (QList<Kleo::DN::Attribute>::const_iterator it = dn.begin(); it != dn.end(); ++it) {
319  if (!(*it).name().isEmpty() && !(*it).value().isEmpty()) {
320  result.push_back((*it).name().trimmed() + QLatin1Char('=') + dn_escape((*it).value().trimmed()));
321  }
322  }
323  return result.join(sep);
324 }
325 
326 static Kleo::DN::Attribute::List reorder_dn(const Kleo::DN::Attribute::List &dn)
327 {
328  const QStringList &attrOrder = Kleo::DN::attributeOrder();
329 
330  Kleo::DN::Attribute::List unknownEntries;
331  Kleo::DN::Attribute::List result;
332  unknownEntries.reserve(dn.size());
333  result.reserve(dn.size());
334 
335  // find all unknown entries in their order of appearance
336  for (Kleo::DN::const_iterator it = dn.begin(); it != dn.end(); ++it) {
337  if (!attrOrder.contains((*it).name())) {
338  unknownEntries.push_back(*it);
339  }
340  }
341 
342  // process the known attrs in the desired order
343  for (QStringList::const_iterator oit = attrOrder.begin(); oit != attrOrder.end(); ++oit) {
344  if (*oit == QLatin1StringView("_X_")) {
345  // insert the unknown attrs
346  std::copy(unknownEntries.begin(), unknownEntries.end(), std::back_inserter(result));
347  unknownEntries.clear(); // don't produce dup's
348  } else {
349  for (Kleo::DN::const_iterator dnit = dn.begin(); dnit != dn.end(); ++dnit) {
350  if ((*dnit).name() == *oit) {
351  result.push_back(*dnit);
352  }
353  }
354  }
355  }
356 
357  return result;
358 }
359 
360 //
361 //
362 // class DN
363 //
364 //
365 
366 Kleo::DN::DN()
367 {
368  d = new Private();
369  d->ref();
370 }
371 
372 Kleo::DN::DN(const QString &dn)
373 {
374  d = new Private();
375  d->ref();
376  d->attributes = parse_dn(dn);
377 }
378 
379 Kleo::DN::DN(const char *utf8DN)
380 {
381  d = new Private();
382  d->ref();
383  if (utf8DN) {
384  d->attributes = parse_dn((const unsigned char *)utf8DN);
385  }
386 }
387 
388 Kleo::DN::DN(const DN &other)
389  : d(other.d)
390 {
391  if (d) {
392  d->ref();
393  }
394 }
395 
396 Kleo::DN::~DN()
397 {
398  if (d) {
399  d->unref();
400  }
401 }
402 
403 const Kleo::DN &Kleo::DN::operator=(const DN &that)
404 {
405  if (this->d == that.d) {
406  return *this;
407  }
408 
409  if (that.d) {
410  that.d->ref();
411  }
412  if (this->d) {
413  this->d->unref();
414  }
415 
416  this->d = that.d;
417 
418  return *this;
419 }
420 
421 // static
422 QStringList Kleo::DN::attributeOrder()
423 {
424  return DNAttributeOrderStore::instance()->attributeOrder();
425 }
426 
427 // static
428 void Kleo::DN::setAttributeOrder(const QStringList &order)
429 {
430  DNAttributeOrderStore::instance()->setAttributeOrder(order);
431 }
432 
433 // static
434 QStringList Kleo::DN::defaultAttributeOrder()
435 {
436  return defaultOrder;
437 }
438 
440 {
441  if (!d) {
442  return QString();
443  }
444  if (d->reorderedAttributes.empty()) {
445  d->reorderedAttributes = reorder_dn(d->attributes);
446  }
447  return serialise(d->reorderedAttributes, QStringLiteral(","));
448 }
449 
451 {
452  return d ? serialise(d->attributes, QStringLiteral(",")) : QString();
453 }
454 
455 QString Kleo::DN::dn(const QString &sep) const
456 {
457  return d ? serialise(d->attributes, sep) : QString();
458 }
459 
460 // static
462 {
463  return dn_escape(value);
464 }
465 
466 void Kleo::DN::detach()
467 {
468  if (!d) {
469  d = new Kleo::DN::Private();
470  d->ref();
471  } else if (d->refCount() > 1) {
472  Kleo::DN::Private *d_save = d;
473  d = new Kleo::DN::Private(*d);
474  d->ref();
475  d_save->unref();
476  }
477 }
478 
479 void Kleo::DN::append(const Attribute &attr)
480 {
481  detach();
482  d->attributes.push_back(attr);
483  d->reorderedAttributes.clear();
484 }
485 
486 QString Kleo::DN::operator[](const QString &attr) const
487 {
488  if (!d) {
489  return QString();
490  }
491  const QString attrUpper = attr.toUpper();
492  for (QList<Attribute>::const_iterator it = d->attributes.constBegin(); it != d->attributes.constEnd(); ++it) {
493  if ((*it).name() == attrUpper) {
494  return (*it).value();
495  }
496  }
497  return QString();
498 }
499 
500 static QList<Kleo::DN::Attribute> empty;
501 
502 Kleo::DN::const_iterator Kleo::DN::begin() const
503 {
504  return d ? d->attributes.constBegin() : empty.constBegin();
505 }
506 
507 Kleo::DN::const_iterator Kleo::DN::end() const
508 {
509  return d ? d->attributes.constEnd() : empty.constEnd();
510 }
511 
512 /////////////////////
513 
514 namespace
515 {
516 static const QMap<QString, KLazyLocalizedString> attributeNamesAndLabels = {
517  // clang-format off
518  {QStringLiteral("CN"), kli18n("Common name") },
519  {QStringLiteral("SN"), kli18n("Surname") },
520  {QStringLiteral("GN"), kli18n("Given name") },
521  {QStringLiteral("L"), kli18n("Location") },
522  {QStringLiteral("T"), kli18n("Title") },
523  {QStringLiteral("OU"), kli18n("Organizational unit")},
524  {QStringLiteral("O"), kli18n("Organization") },
525  {QStringLiteral("PC"), kli18n("Postal code") },
526  {QStringLiteral("C"), kli18n("Country code") },
527  {QStringLiteral("SP"), kli18n("State or province") },
528  {QStringLiteral("DC"), kli18n("Domain component") },
529  {QStringLiteral("BC"), kli18n("Business category") },
530  {QStringLiteral("EMAIL"), kli18n("Email address") },
531  {QStringLiteral("MAIL"), kli18n("Mail address") },
532  {QStringLiteral("MOBILE"), kli18n("Mobile phone number")},
533  {QStringLiteral("TEL"), kli18n("Telephone number") },
534  {QStringLiteral("FAX"), kli18n("Fax number") },
535  {QStringLiteral("STREET"), kli18n("Street address") },
536  {QStringLiteral("UID"), kli18n("Unique ID") },
537  // clang-format on
538 };
539 }
540 
541 // static
542 QStringList Kleo::DN::attributeNames()
543 {
544  return attributeNamesAndLabels.keys();
545 }
546 
547 // static
548 QString Kleo::DN::attributeNameToLabel(const QString &name)
549 {
550  const QString key{name.trimmed().toUpper()};
551  if (attributeNames().contains(key)) {
552  return attributeNamesAndLabels.value(key).toString();
553  }
554  qCWarning(LIBKLEO_LOG) << "Attribute " << key << " doesn't exit. Bug ?";
555  return {};
556 }
DN parser and reorderer.
Definition: dn.h:26
QString toUpper() const const
QString fromUtf8(const char *str, int size)
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
void push_back(const T &value)
QString dn() const
Definition: dn.cpp:450
void reserve(int alloc)
bool empty() const const
QByteArray toUtf8() const const
int length() const const
QString join(const QString &separator) const const
QString prettyDN() const
Definition: dn.cpp:439
QList::iterator begin()
static QString escape(const QString &value)
Definition: dn.cpp:461
QList::iterator end()
char * data()
ushort unicode() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:56:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.