Coverage Report

Created: 2025-08-29 06:26

/src/opensc/src/ui/notify.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * notify.c: Notification implementation
3
 *
4
 * Copyright (C) 2017 Frank Morgner <frankmorgner@gmail.com>
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
21
#ifdef HAVE_CONFIG_H
22
#include "config.h"
23
#endif
24
25
#include "notify.h"
26
27
#if defined(ENABLE_NOTIFY) && (defined(__APPLE__))
28
29
#include "libopensc/internal.h"
30
#include "libopensc/log.h"
31
#include <signal.h>
32
#include <stdlib.h>
33
#include <string.h>
34
#include <sys/wait.h>
35
#include <unistd.h>
36
37
static pid_t child = -1;
38
39
void sc_notify_init(void)
40
{
41
}
42
43
void sc_notify_close(void)
44
{
45
  if (child > 0) {
46
    int i, status;
47
    for (i = 0; child != waitpid(child, &status, WNOHANG); i++) {
48
      switch (i) {
49
        case 0:
50
          kill(child, SIGKILL);
51
          break;
52
        case 1:
53
          kill(child, SIGTERM);
54
          break;
55
        default:
56
          /* SIGTERM was our last resort */
57
          return;
58
      }
59
      usleep(100);
60
    }
61
    child = -1;
62
  }
63
}
64
65
#endif
66
67
#if defined(ENABLE_NOTIFY) && defined(_WIN32)
68
69
#include "common/compat_strlcpy.h"
70
#include "invisible_window.h"
71
#include <shellapi.h>
72
#include <shlwapi.h>
73
74
// {83C35893-99C6-4600-BFDB-45925C53BDD9}
75
static const GUID myGUID = { 0x83c35893, 0x99c6, 0x4600, { 0xbf, 0xdb, 0x45, 0x92, 0x5c, 0x53, 0xbd, 0xd9 } };
76
HINSTANCE sc_notify_instance = NULL;
77
HWND sc_notify_hwnd = NULL;
78
BOOL delete_icon = TRUE;
79
#define IDI_SMARTCARD       102
80
#define WMAPP_NOTIFYCALLBACK (WM_APP + 1)
81
static BOOL RestoreTooltip();
82
void ShowContextMenu(HWND hwnd, POINT pt);
83
84
// we need commctrl v6 for LoadIconMetric()
85
#include <commctrl.h>
86
87
static int GetMonitorScalingRatio(void)
88
{
89
  POINT pt = { 0, 0 };
90
  HMONITOR monitor;
91
  MONITORINFOEX info = { .cbSize = sizeof(MONITORINFOEX) };
92
  DEVMODE devmode = { .dmSize = sizeof(DEVMODE) };
93
  monitor = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
94
  if (!monitor)
95
    goto err;
96
  if (!GetMonitorInfo(monitor, (LPMONITORINFO)&info))
97
    goto err;
98
  if (!EnumDisplaySettings(info.szDevice, ENUM_CURRENT_SETTINGS, &devmode))
99
    goto err;
100
  if (info.rcMonitor.left == info.rcMonitor.right)
101
    goto err;
102
  return 100*devmode.dmPelsWidth/(info.rcMonitor.right - info.rcMonitor.left);
103
err:
104
  return 100;
105
}
106
107
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
108
{
109
  switch (message) {
110
    case WM_COMMAND:
111
      {
112
        int const wmId = LOWORD(wParam);
113
        // Parse the menu selection:
114
        switch (wmId)
115
        {
116
          case WMAPP_EXIT:
117
            break;
118
119
          default:
120
            return DefWindowProc(hwnd, message, wParam, lParam);
121
        }
122
      }
123
      break;
124
125
    case WMAPP_NOTIFYCALLBACK:
126
      switch (LOWORD(lParam)) {
127
        case NIN_BALLOONTIMEOUT:
128
        case NIN_BALLOONUSERCLICK:
129
          RestoreTooltip();
130
          break;
131
132
        case WM_CONTEXTMENU:
133
          {
134
            int scaling = GetMonitorScalingRatio();
135
            POINT const pt = { 100*LOWORD(wParam)/scaling, 100*HIWORD(wParam)/scaling };
136
            ShowContextMenu(hwnd, pt);
137
          }
138
          break;
139
      }
140
      break;
141
142
    default:
143
      return DefWindowProc(hwnd, message, wParam, lParam);
144
  }
145
146
    return 0;
147
}
148
149
static const char* lpszClassName = "NOTIFY_CLASS";
150
151
static BOOL AddNotificationIcon(void)
152
{
153
  NOTIFYICONDATA nid = {0};
154
  TCHAR path[MAX_PATH]={0};
155
  BOOL r;
156
157
  sc_notify_hwnd = create_invisible_window(lpszClassName, WndProc,
158
      sc_notify_instance);
159
  if (!sc_notify_hwnd) {
160
    return FALSE;
161
  }
162
163
  nid.cbSize = sizeof(NOTIFYICONDATA);
164
  nid.hWnd = sc_notify_hwnd;
165
  // add the icon, setting the icon, tooltip, and callback message.
166
  // the icon will be identified with the GUID
167
  nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP | NIF_GUID;
168
  nid.guidItem = myGUID; 
169
  nid.uCallbackMessage = WMAPP_NOTIFYCALLBACK;
170
  LoadIconMetric(sc_notify_instance, MAKEINTRESOURCEW(IDI_SMARTCARD), LIM_SMALL, &nid.hIcon);
171
  if (GetModuleFileName(NULL, path, ARRAYSIZE(path))) {
172
    const char *basename = strrchr(path, '\\');
173
    if (basename) {
174
      basename++;
175
      if (0 != strcmp(basename, "opensc-notify.exe")) {
176
        /* Allow creation of system tray icon only for
177
         * "opensc-notify.exe" to avoid creation of the same icon by
178
         * multiple processes. */
179
        delete_icon = FALSE;
180
        return FALSE;
181
      }
182
    }
183
    strlcpy(nid.szTip, path, ARRAYSIZE(nid.szTip));
184
  } else {
185
    strcpy(nid.szTip, PACKAGE_NAME);
186
  }
187
188
  r = Shell_NotifyIcon(NIM_ADD, &nid);
189
  if (TRUE == r) {
190
    nid.uVersion = NOTIFYICON_VERSION_4;
191
    r = Shell_NotifyIcon(NIM_SETVERSION, &nid);
192
  }
193
194
  return r;
195
}
196
197
static BOOL DeleteNotificationIcon(void)
198
{
199
  BOOL r;
200
  NOTIFYICONDATA nid = {0};
201
202
  if (!delete_icon) {
203
    return FALSE;
204
  }
205
206
  nid.cbSize = sizeof(NOTIFYICONDATA);
207
  nid.uFlags = NIF_GUID;
208
  nid.guidItem = myGUID; 
209
210
  r  = Shell_NotifyIcon(NIM_DELETE, &nid);
211
212
  r &= delete_invisible_window(sc_notify_hwnd, lpszClassName,
213
      sc_notify_instance);
214
  sc_notify_hwnd = NULL;
215
216
  return r;
217
}
218
219
static BOOL RestoreTooltip()
220
{
221
    // After the balloon is dismissed, restore the tooltip.
222
  NOTIFYICONDATA nid = {0};
223
224
  nid.cbSize = sizeof(NOTIFYICONDATA);
225
    nid.uFlags = NIF_SHOWTIP | NIF_GUID;
226
  nid.guidItem = myGUID; 
227
228
    return Shell_NotifyIcon(NIM_MODIFY, &nid);
229
}
230
231
void ShowContextMenu(HWND hwnd, POINT pt)
232
{
233
  HMENU hMenu;
234
  hMenu=CreatePopupMenu();
235
236
    if (hMenu) {
237
    AppendMenu(hMenu, MF_STRING, WMAPP_EXIT, ui_get_str(NULL, NULL, NULL, NOTIFY_EXIT));
238
239
    // our window must be foreground before calling TrackPopupMenu or the menu will not disappear when the user clicks away
240
    SetForegroundWindow(hwnd);
241
242
    // respect menu drop alignment
243
    UINT uFlags = TPM_RIGHTBUTTON;
244
    if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) {
245
      uFlags |= TPM_RIGHTALIGN;
246
    } else {
247
      uFlags |= TPM_LEFTALIGN;
248
    }
249
250
    TrackPopupMenuEx(hMenu, uFlags, pt.x, pt.y, hwnd, NULL);
251
  }
252
}
253
254
255
static void notify_shell(struct sc_context *ctx,
256
    const char *title, const char *text, LPCTSTR icon_path, int icon_index)
257
{
258
  NOTIFYICONDATA nid = {0};
259
  HICON icon = NULL;
260
261
  nid.cbSize = sizeof(NOTIFYICONDATA);
262
    nid.uFlags = NIF_GUID;
263
    nid.guidItem = myGUID;
264
265
  if (title) {
266
    strlcpy(nid.szInfoTitle, title, ARRAYSIZE(nid.szInfoTitle));
267
  }
268
  if (text) {
269
    nid.uFlags |= NIF_INFO;
270
    strlcpy(nid.szInfo, text, ARRAYSIZE(nid.szInfo));
271
  }
272
  if (icon_path) {
273
    ExtractIconEx(icon_path, icon_index, &icon, NULL, 1);
274
    if (icon) {
275
      nid.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
276
      nid.hBalloonIcon = icon;
277
    }
278
  }
279
280
    Shell_NotifyIcon(NIM_MODIFY, &nid);
281
282
  if (icon) {
283
    DestroyIcon(icon);
284
  }
285
}
286
287
void sc_notify_init(void)
288
{
289
  if (!sc_notify_instance) {
290
    /* returns the HINSTANCE of the exe. If the code executes in a DLL,
291
     * sc_notify_instance_notify should be pre-initialized */
292
    sc_notify_instance = GetModuleHandle(NULL);
293
  }
294
  AddNotificationIcon();
295
}
296
297
void sc_notify_close(void)
298
{
299
  DeleteNotificationIcon();
300
}
301
302
void sc_notify(const char *title, const char *text)
303
{
304
  notify_shell(NULL, title, text, NULL, 0);
305
}
306
307
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
308
    struct sc_pkcs15_card *p15card, enum ui_str id)
309
{
310
  const char *title, *text;
311
  LPCTSTR icon_path = NULL;
312
  int icon_index = 0;
313
  title = ui_get_str(ctx, atr, p15card, id);
314
  text = ui_get_str(ctx, atr, p15card, id+1);
315
316
  switch (id) {
317
    case NOTIFY_CARD_INSERTED:
318
      icon_path = TEXT("%SYSTEMROOT%\\system32\\SCardDlg.dll");
319
      icon_index = 3;
320
      break;
321
    case NOTIFY_CARD_REMOVED:
322
      icon_path = TEXT("%SYSTEMROOT%\\system32\\SCardDlg.dll");
323
      icon_index = 2;
324
      break;
325
    case NOTIFY_PIN_GOOD:
326
      icon_path = TEXT("%SYSTEMROOT%\\system32\\certmgr.dll");
327
      icon_index = 16;
328
      break;
329
    case NOTIFY_PIN_BAD:
330
      icon_path = TEXT("%SYSTEMROOT%\\system32\\certmgr.dll");
331
      icon_index = 11;
332
      break;
333
    default:
334
      break;
335
  }
336
337
  notify_shell(ctx, title, text, icon_path, icon_index);
338
}
339
340
#elif defined(ENABLE_NOTIFY) && defined(__APPLE__)
341
342
static void notify_proxy(struct sc_context *ctx,
343
    const char *title, const char* subtitle,
344
    const char *text, const char *icon, const char *sound,
345
    const char *group)
346
{
347
  /* terminal-notifier does not reliably activate keychain when clicked on
348
   * the notification
349
   * (https://github.com/julienXX/terminal-notifier/issues/196), that's why
350
   * we're including NotificationProxy which has similar features */
351
  const char notificationproxy[] = "/Library/OpenSC/Applications/NotificationProxy.app/Contents/MacOS/NotificationProxy";
352
353
  if (ctx && ctx->app_name
354
      && (0 == strcmp(ctx->app_name, "opensc-pkcs11")
355
        || 0 == strcmp(ctx->app_name, "onepin-opensc-pkcs11"))) {
356
    /* some programs don't like forking when loading our PKCS#11 module,
357
     * see https://github.com/OpenSC/OpenSC/issues/1174.
358
     * TODO implementing an XPC service which sends the notification should
359
     * work, though. See
360
     * https://github.com/OpenSC/OpenSC/issues/1304#issuecomment-376656003 */
361
    return;
362
  }
363
364
  if (child > 0) {
365
    int status;
366
    if (0 == waitpid(child, &status, WNOHANG)) {
367
      kill(child, SIGKILL);
368
      usleep(100);
369
      if (0 == waitpid(child, &status, WNOHANG)) {
370
        sc_log(ctx, "Can't kill %ld, skipping current notification", (long) child);
371
        return;
372
      }
373
    }
374
  }
375
376
  child = fork();
377
  switch (child) {
378
    case 0:
379
      /* child process */
380
381
      /* for some reason the user _tokend can call brew's installation of
382
       * terminal-notifier, but it cannot call `/usr/bin/open` with
383
       * NotificationProxy.app that we're shipping... However, while
384
       * `sudo -u _tokend /usr/local/bin/terminal-notifier -title test`
385
       * works in the terminal, it hangs when executed from the tokend
386
       * process.  For now, we try to deliver the notification anyway
387
       * making sure that we are waiting for only one forked process. */
388
      if (0 > execl(notificationproxy, notificationproxy,
389
            title ? title : "",
390
            subtitle ? subtitle : "",
391
            text ? text : "",
392
            icon ? icon : "",
393
            group ? group : "",
394
            sound ? sound : "",
395
            (char *) NULL)) {
396
        perror("exec failed");
397
        exit(0);
398
      }
399
      break;
400
    case -1:
401
      sc_log(ctx, "failed to fork for notification");
402
      break;
403
    default:
404
      if (ctx) {
405
        sc_log(ctx, "Created %ld for notification:", (long) child);
406
        sc_log(ctx, "%s %s %s %s %s %s %s", notificationproxy,
407
            title ? title : "",
408
            subtitle ? subtitle : "",
409
            text ? text : "",
410
            icon ? icon : "",
411
            group ? group : "",
412
            sound ? sound : "");
413
      }
414
      break;
415
  }
416
}
417
418
void sc_notify(const char *title, const char *text)
419
{
420
  notify_proxy(NULL, title, NULL, text, NULL, NULL, NULL);
421
}
422
423
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
424
    struct sc_pkcs15_card *p15card, enum ui_str id)
425
{
426
  const char *title, *text, *icon, *group;
427
  title = ui_get_str(ctx, atr, p15card, id);
428
  text = ui_get_str(ctx, atr, p15card, id+1);
429
430
  if (p15card && p15card->card && p15card->card->reader) {
431
    group = p15card->card->reader->name;
432
  } else {
433
    group = ctx ? ctx->app_name : NULL;
434
  }
435
436
  switch (id) {
437
    case NOTIFY_CARD_INSERTED:
438
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/VCard.icns";
439
      break;
440
    case NOTIFY_CARD_REMOVED:
441
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/EjectMediaIcon.icns";
442
      break;
443
    case NOTIFY_PIN_GOOD:
444
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/UnlockedIcon.icns";
445
      break;
446
    case NOTIFY_PIN_BAD:
447
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/LockedIcon.icns";
448
      break;
449
    default:
450
      icon = NULL;
451
      break;
452
  }
453
454
  notify_proxy(ctx, title, NULL, text, icon, NULL, group);
455
}
456
457
#elif defined(ENABLE_NOTIFY) && defined(ENABLE_GIO2)
458
459
#include <gio/gio.h>
460
#include "libopensc/log.h"
461
462
static GApplication *application = NULL;
463
464
void sc_notify_init(void)
465
{
466
  if (!application) {
467
    application = g_application_new("org.opensc.notify", G_APPLICATION_NON_UNIQUE);
468
  }
469
  if (application && FALSE == g_application_get_is_registered(application)) {
470
    g_application_register(application, NULL, NULL);
471
  }
472
}
473
474
void sc_notify_close(void)
475
{
476
  if (application) {
477
    g_object_unref(application);
478
    application = NULL;
479
  }
480
}
481
482
static void notify_gio(struct sc_context *ctx,
483
    const char *title, const char *text, const char *icon,
484
    const char *group)
485
{
486
  if (application
487
      && g_application_get_is_registered(application)
488
      && g_application_get_dbus_connection(application)) {
489
    GIcon *gicon = NULL;
490
    GNotification *notification = g_notification_new(title);
491
    if (!notification) {
492
      return;
493
    }
494
495
    if (text) {
496
      g_notification_set_body(notification, text);
497
    }
498
    if (icon) {
499
      gicon = g_themed_icon_new(icon);
500
      if (gicon) {
501
        g_notification_set_icon(notification, gicon);
502
      }
503
    }
504
505
    if (ctx) {
506
      sc_log(ctx, "%s %s %s %s",
507
          title ? title : "",
508
          text ? text : "",
509
          icon ? icon : "",
510
          group ? group : "");
511
    }
512
513
    g_application_send_notification(application, group, notification);
514
515
    if (gicon) {
516
      g_object_unref(gicon);
517
    }
518
    g_object_unref(notification);
519
  }
520
}
521
522
#else
523
524
0
void sc_notify_init(void) {}
525
0
void sc_notify_close(void) {}
526
0
void sc_notify(const char *title, const char *text) {}
527
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
528
0
    struct sc_pkcs15_card *p15card, enum ui_str id) {}
529
530
#endif
531
532
#if defined(ENABLE_NOTIFY) && defined(ENABLE_GIO2)
533
void sc_notify(const char *title, const char *text)
534
{
535
  notify_gio(NULL, title, text, NULL, NULL);
536
}
537
538
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
539
    struct sc_pkcs15_card *p15card, enum ui_str id)
540
{
541
  const char *title, *text, *icon, *group;
542
  title = ui_get_str(ctx, atr, p15card, id);
543
  text = ui_get_str(ctx, atr, p15card, id+1);
544
545
  if (p15card && p15card->card && p15card->card->reader) {
546
    group = p15card->card->reader->name;
547
  } else {
548
    group = ctx ? ctx->app_name : NULL;
549
  }
550
551
  switch (id) {
552
    case NOTIFY_CARD_INSERTED:
553
      icon = "contact-new";
554
      break;
555
    case NOTIFY_CARD_REMOVED:
556
      icon = "media-eject";
557
      break;
558
    case NOTIFY_PIN_GOOD:
559
      icon = "changes-allow";
560
      break;
561
    case NOTIFY_PIN_BAD:
562
      icon = "changes-prevent";
563
      break;
564
    default:
565
      icon = NULL;
566
      break;
567
  }
568
569
  notify_gio(ctx, title, text, icon, group);
570
}
571
572
#endif