a/src/upmpd.cxx b/src/upmpd.cxx
...
...
14
 *   Free Software Foundation, Inc.,
14
 *   Free Software Foundation, Inc.,
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
16
 */
17
17
18
#include "upmpd.hxx"
18
#include "upmpd.hxx"
19
20
#include <errno.h>                      // for errno
21
#include <fcntl.h>                      // for open, O_CREAT, O_RDWR
22
#include <pwd.h>                        // for getpwnam, passwd
23
#include <signal.h>                     // for sigaction, SIG_IGN, etc
24
#include <stdio.h>                      // for fprintf, perror, stderr
25
#include <stdlib.h>                     // for atoi, getenv, exit
26
#include <sys/param.h>                  // for MIN
27
#include <unistd.h>                     // for geteuid, chown, sleep, etc
28
#include <string.h>                     // for memset
29
#include <grp.h>
30
31
#include <iostream>                     // for basic_ostream, operator<<, etc
32
#include <string>                       // for string, operator<<, etc
33
#include <unordered_map>                // for unordered_map, etc
34
#include <vector>                       // for vector, vector<>::iterator
35
19
36
#include "libupnpp/device/device.hxx"   // for UpnpDevice, UpnpService
20
#include "libupnpp/device/device.hxx"   // for UpnpDevice, UpnpService
37
#include "libupnpp/log.hxx"             // for LOGFAT, LOGERR, Logger, etc
21
#include "libupnpp/log.hxx"             // for LOGFAT, LOGERR, Logger, etc
38
#include "libupnpp/upnpplib.hxx"        // for LibUPnP
22
#include "libupnpp/upnpplib.hxx"        // for LibUPnP
39
23
...
...
56
using namespace std;
40
using namespace std;
57
using namespace std::placeholders;
41
using namespace std::placeholders;
58
using namespace UPnPP;
42
using namespace UPnPP;
59
43
60
bool OHService::getEventData(bool all, vector<string>& names, 
44
bool OHService::getEventData(bool all, vector<string>& names, 
61
                                      vector<std::string>& values)
45
                             vector<std::string>& values)
62
{
46
{
63
    //LOGDEB("OHService::getEventData" << std::endl);
47
    //LOGDEB("OHService::getEventData" << std::endl);
64
48
65
    std::unordered_map<std::string, std::string> state, changed;
49
    std::unordered_map<std::string, std::string> state, changed;
66
    makestate(state);
50
    makestate(state);
...
...
87
             const unordered_map<string, VDirContent>& files,
71
             const unordered_map<string, VDirContent>& files,
88
             MPDCli *mpdcli, Options opts)
72
             MPDCli *mpdcli, Options opts)
89
    : UpnpDevice(deviceid, files), m_mpdcli(mpdcli), m_mpds(0),
73
    : UpnpDevice(deviceid, files), m_mpdcli(mpdcli), m_mpds(0),
90
      m_options(opts.options),
74
      m_options(opts.options),
91
      m_mcachefn(opts.cachefn),
75
      m_mcachefn(opts.cachefn),
92
      m_rdctl(0), m_avt(0), m_ohpr(0), m_ohpl(0), m_ohrd(0), m_ohrcv(0), m_sndrcv(0),
76
      m_rdctl(0), m_avt(0), m_ohpr(0), m_ohpl(0), m_ohrd(0), m_ohrcv(0),
93
      m_friendlyname(friendlyname)
77
      m_sndrcv(0), m_friendlyname(friendlyname)
94
{
78
{
95
    bool avtnoev = (m_options & upmpdNoAV) != 0; 
79
    bool avtnoev = (m_options & upmpdNoAV) != 0; 
96
    // Note: the order is significant here as it will be used when
80
    // Note: the order is significant here as it will be used when
97
    // calling the getStatus() methods, and we want AVTransport to
81
    // calling the getStatus() methods, and we want AVTransport to
98
    // update the mpd status for everybody
82
    // update the mpd status for everybody
...
...
156
const MpdStatus& UpMpd::getMpdStatus()
140
const MpdStatus& UpMpd::getMpdStatus()
157
{
141
{
158
    m_mpds = &m_mpdcli->getStatus();
142
    m_mpds = &m_mpdcli->getStatus();
159
    return *m_mpds;
143
    return *m_mpds;
160
}
144
}
161
162
/////////////////////////////////////////////////////////////////////
163
// Main program
164
165
#include "conftree.hxx"
166
167
static char *thisprog;
168
169
static int op_flags;
170
#define OPT_MOINS 0x1
171
#define OPT_h     0x2
172
#define OPT_p     0x4
173
#define OPT_d     0x8
174
#define OPT_D     0x10
175
#define OPT_c     0x20
176
#define OPT_l     0x40
177
#define OPT_f     0x80
178
#define OPT_q     0x100
179
#define OPT_i     0x200
180
#define OPT_P     0x400
181
#define OPT_O     0x800
182
#define OPT_v     0x1000
183
184
static const char usage[] = 
185
    "-c configfile \t configuration file to use\n"
186
    "-h host    \t specify host MPD is running on\n"
187
    "-p port     \t specify MPD port\n"
188
    "-d logfilename\t debug messages to\n"
189
    "-l loglevel\t  log level (0-6)\n"
190
    "-D    \t run as a daemon\n"
191
    "-f friendlyname\t define device displayed name\n"
192
    "-q 0|1\t if set, we own the mpd queue, else avoid clearing it whenever we feel like it\n"
193
    "-i iface    \t specify network interface name to be used for UPnP\n"
194
    "-P upport    \t specify port number to be used for UPnP\n"
195
    "-O 0|1\t decide if we run and export the OpenHome services\n"
196
    "-v      \tprint version info\n"
197
    "\n"
198
    ;
199
200
static void
201
versionInfo(FILE *fp)
202
{
203
    fprintf(fp, "Upmpdcli %s %s\n",
204
           UPMPDCLI_PACKAGE_VERSION, LibUPnP::versionString().c_str());
205
}
206
207
static void
208
Usage(FILE *fp = stderr)
209
{
210
    fprintf(fp, "%s: usage:\n%s", thisprog, usage);
211
    versionInfo(fp);
212
    exit(1);
213
}
214
215
216
static const string dfltFriendlyName("UpMpd");
217
218
// This is global
219
string g_protocolInfo;
220
221
// Static for cleanup in sig handler.
222
static UpnpDevice *dev;
223
224
string g_datadir(DATADIR "/");
225
226
// Global
227
string g_configfilename(CONFIGDIR "/upmpdcli.conf");
228
ConfSimple *g_config;
229
230
static void onsig(int)
231
{
232
    LOGDEB("Got sig" << endl);
233
    dev->shouldExit();
234
}
235
236
static const int catchedSigs[] = {SIGINT, SIGQUIT, SIGTERM};
237
static void setupsigs()
238
{
239
    struct sigaction action;
240
    action.sa_handler = onsig;
241
    action.sa_flags = 0;
242
    sigemptyset(&action.sa_mask);
243
    for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++)
244
        if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) {
245
            if (sigaction(catchedSigs[i], &action, 0) < 0) {
246
                perror("Sigaction failed");
247
            }
248
        }
249
}
250
251
int main(int argc, char *argv[])
252
{
253
    // Path for the sc2mpd command, or empty
254
    string sc2mpdpath;
255
256
    // Sender mode: path for the command creating the mpd and mpd2sc
257
    // processes, and port for the auxiliary mpd.
258
    string senderpath;
259
    int sendermpdport = 6700;
260
261
    // Main MPD parameters
262
    string mpdhost("localhost");
263
    int mpdport = 6600;
264
    string mpdpassword;
265
266
    string logfilename;
267
    int loglevel(Logger::LLINF);
268
    string friendlyname(dfltFriendlyName);
269
    bool ownqueue = true;
270
    bool enableAV = true;
271
    bool enableOH = true;
272
    bool ohmetapersist = true;
273
    string upmpdcliuser("upmpdcli");
274
    string pidfilename("/var/run/upmpdcli.pid");
275
    string iconpath(DATADIR "/icon.png");
276
    string presentationhtml(DATADIR "/presentation.html");
277
    string iface;
278
    unsigned short upport = 0;
279
    string upnpip;
280
281
    const char *cp;
282
    if ((cp = getenv("UPMPD_HOST")))
283
        mpdhost = cp;
284
    if ((cp = getenv("UPMPD_PORT")))
285
        mpdport = atoi(cp);
286
    if ((cp = getenv("UPMPD_FRIENDLYNAME")))
287
        friendlyname = atoi(cp);
288
    if ((cp = getenv("UPMPD_CONFIG")))
289
        g_configfilename = cp;
290
    if ((cp = getenv("UPMPD_UPNPIFACE")))
291
        iface = cp;
292
    if ((cp = getenv("UPMPD_UPNPPORT")))
293
        upport = atoi(cp);
294
295
    thisprog = argv[0];
296
    argc--; argv++;
297
    while (argc > 0 && **argv == '-') {
298
        (*argv)++;
299
        if (!(**argv))
300
            Usage();
301
        while (**argv)
302
            switch (*(*argv)++) {
303
            case 'c':   op_flags |= OPT_c; if (argc < 2)  Usage();
304
                g_configfilename = *(++argv); argc--; goto b1;
305
            case 'D':   op_flags |= OPT_D; break;
306
            case 'd':   op_flags |= OPT_d; if (argc < 2)  Usage();
307
                logfilename = *(++argv); argc--; goto b1;
308
            case 'f':   op_flags |= OPT_f; if (argc < 2)  Usage();
309
                friendlyname = *(++argv); argc--; goto b1;
310
            case 'h':   op_flags |= OPT_h; if (argc < 2)  Usage();
311
                mpdhost = *(++argv); argc--; goto b1;
312
            case 'i':   op_flags |= OPT_i; if (argc < 2)  Usage();
313
                iface = *(++argv); argc--; goto b1;
314
            case 'l':   op_flags |= OPT_l; if (argc < 2)  Usage();
315
                loglevel = atoi(*(++argv)); argc--; goto b1;
316
            case 'O': {
317
                op_flags |= OPT_O; 
318
                if (argc < 2)  Usage();
319
                const char *cp =  *(++argv);
320
                if (*cp == '1' || *cp == 't' || *cp == 'T' || *cp == 'y' || 
321
                    *cp == 'Y')
322
                    enableOH = true;
323
                argc--; goto b1;
324
            }
325
            case 'P':   op_flags |= OPT_P; if (argc < 2)  Usage();
326
                upport = atoi(*(++argv)); argc--; goto b1;
327
            case 'p':   op_flags |= OPT_p; if (argc < 2)  Usage();
328
                mpdport = atoi(*(++argv)); argc--; goto b1;
329
            case 'q':   op_flags |= OPT_q; if (argc < 2)  Usage();
330
                ownqueue = atoi(*(++argv)) != 0; argc--; goto b1;
331
            case 'v': versionInfo(stdout); exit(0); break;
332
            default: Usage();   break;
333
            }
334
    b1: argc--; argv++;
335
    }
336
337
    if (argc != 0)
338
        Usage();
339
340
    UpMpd::Options opts;
341
342
    string cachedir;
343
    string onstart;
344
    string onstop;
345
    string onvolumechange;
346
    if (!g_configfilename.empty()) {
347
        g_config = new ConfSimple(g_configfilename.c_str(), 1, true);
348
        if (!g_config || !g_config->ok()) {
349
            cerr << "Could not open config: " << g_configfilename << endl;
350
            return 1;
351
        }
352
353
        string value;
354
        if (!(op_flags & OPT_d))
355
            g_config->get("logfilename", logfilename);
356
        if (!(op_flags & OPT_f))
357
            g_config->get("friendlyname", friendlyname);
358
        if (!(op_flags & OPT_l) && g_config->get("loglevel", value))
359
            loglevel = atoi(value.c_str());
360
        if (!(op_flags & OPT_h))
361
            g_config->get("mpdhost", mpdhost);
362
        if (!(op_flags & OPT_p) && g_config->get("mpdport", value)) {
363
            mpdport = atoi(value.c_str());
364
        }
365
        g_config->get("mpdpassword", mpdpassword);
366
        if (!(op_flags & OPT_q) && g_config->get("ownqueue", value)) {
367
            ownqueue = atoi(value.c_str()) != 0;
368
        }
369
        if (g_config->get("openhome", value)) {
370
            enableOH = atoi(value.c_str()) != 0;
371
        }
372
        if (g_config->get("upnpav", value)) {
373
            enableAV = atoi(value.c_str()) != 0;
374
        }
375
        if (g_config->get("ohmetapersist", value)) {
376
            ohmetapersist = atoi(value.c_str()) != 0;
377
        }
378
        g_config->get("iconpath", iconpath);
379
        g_config->get("presentationhtml", presentationhtml);
380
        g_config->get("cachedir", cachedir);
381
        g_config->get("onstart", onstart);
382
        g_config->get("onstop", onstop);
383
        g_config->get("onvolumechange", onvolumechange);
384
        if (!(op_flags & OPT_i)) {
385
            g_config->get("upnpiface", iface);
386
            if (iface.empty()) {
387
                g_config->get("upnpip", upnpip);
388
            }
389
        }
390
        if (!(op_flags & OPT_P) && g_config->get("upnpport", value)) {
391
            upport = atoi(value.c_str());
392
        }
393
        if (g_config->get("schttpport", value))
394
            opts.schttpport = atoi(value.c_str());
395
        g_config->get("scplaymethod", opts.scplaymethod);
396
        g_config->get("sc2mpd", sc2mpdpath);
397
        if (g_config->get("ohmetasleep", value))
398
            opts.ohmetasleep = atoi(value.c_str());
399
400
        g_config->get("scsenderpath", senderpath);
401
        if (g_config->get("scsendermpdport", value))
402
            sendermpdport = atoi(value.c_str());
403
    }
404
    if (Logger::getTheLog(logfilename) == 0) {
405
        cerr << "Can't initialize log" << endl;
406
        return 1;
407
    }
408
    Logger::getTheLog("")->setLogLevel(Logger::LogLevel(loglevel));
409
410
    Pidfile pidfile(pidfilename);
411
412
    // If started by root, do the pidfile + change uid thing
413
    uid_t runas(0);
414
    gid_t runasg(0);
415
    if (geteuid() == 0) {
416
        struct passwd *pass = getpwnam(upmpdcliuser.c_str());
417
        if (pass == 0) {
418
            LOGFAT("upmpdcli won't run as root and user " << upmpdcliuser << 
419
                   " does not exist " << endl);
420
            return 1;
421
        }
422
        runas = pass->pw_uid;
423
        runasg = pass->pw_gid;
424
425
        pid_t pid;
426
        if ((pid = pidfile.open()) != 0) {
427
            LOGFAT("Can't open pidfile: " << pidfile.getreason() << 
428
                   ". Return (other pid?): " << pid << endl);
429
            return 1;
430
        }
431
        if (pidfile.write_pid() != 0) {
432
            LOGFAT("Can't write pidfile: " << pidfile.getreason() << endl);
433
            return 1;
434
        }
435
  if (cachedir.empty())
436
            cachedir = "/var/cache/upmpdcli";
437
    } else {
438
  if (cachedir.empty())
439
            cachedir = path_cat(path_tildexpand("~") , "/.cache/upmpdcli");
440
    }
441
442
    string& mcfn = opts.cachefn;
443
    if (ohmetapersist) {
444
        opts.cachefn = path_cat(cachedir, "/metacache");
445
        if (!path_makepath(cachedir, 0755)) {
446
            LOGERR("makepath("<< cachedir << ") : errno : " << errno << endl);
447
        } else {
448
            int fd;
449
            if ((fd = open(mcfn.c_str(), O_CREAT|O_RDWR, 0644)) < 0) {
450
                LOGERR("creat("<< mcfn << ") : errno : " << errno << endl);
451
            } else {
452
                close(fd);
453
                if (geteuid() == 0 && chown(mcfn.c_str(), runas, -1) != 0) {
454
                    LOGERR("chown("<< mcfn << ") : errno : " << errno << endl);
455
                }
456
                if (geteuid() == 0 && chown(cachedir.c_str(), runas, -1) != 0) {
457
                    LOGERR("chown("<< cachedir << ") : errno : " << errno << endl);
458
                }
459
            }
460
        }
461
    }
462
    
463
    if ((op_flags & OPT_D)) {
464
        if (daemon(1, 0)) {
465
            LOGFAT("Daemon failed: errno " << errno << endl);
466
            return 1;
467
        }
468
    }
469
470
    if (geteuid() == 0) {
471
        // Need to rewrite pid, it may have changed with the daemon call
472
        pidfile.write_pid();
473
        if (!logfilename.empty() && logfilename.compare("stderr")) {
474
            if (chown(logfilename.c_str(), runas, -1) < 0) {
475
                LOGERR("chown("<<logfilename<<") : errno : " << errno << endl);
476
            }
477
        }
478
        if (initgroups(upmpdcliuser.c_str(), runasg) < 0) {
479
            LOGERR("initgroup failed. Errno: " << errno << endl);
480
        }
481
        if (setuid(runas) < 0) {
482
            LOGFAT("Can't set my uid to " << runas << " current: " << geteuid()
483
                   << endl);
484
            return 1;
485
        }
486
#if 0        
487
        gid_t list[100];
488
        int ng = getgroups(100, list);
489
        cerr << "GROUPS: ";
490
        for (int i = 0; i < ng; i++) {
491
            cerr << int(list[i]) << " ";
492
        }
493
        cerr << endl;
494
#endif
495
    }
496
497
//// Dropped root 
498
499
    if (sc2mpdpath.empty()) {
500
        // Do we have an sc2mpd command installed (for songcast)?
501
        if (!ExecCmd::which("sc2mpd", sc2mpdpath))
502
            sc2mpdpath.clear();
503
    }
504
    if (senderpath.empty()) {
505
        // Do we have an scmakempdsender command installed (for
506
        // starting the songcast sender and its auxiliary mpd)?
507
        if (!ExecCmd::which("scmakempdsender", senderpath))
508
            senderpath.clear();
509
    }
510
    
511
    if (!sc2mpdpath.empty()) {
512
        // Check if sc2mpd is actually there
513
        if (access(sc2mpdpath.c_str(), X_OK|R_OK) != 0) {
514
            LOGERR("Specified path for sc2mpd: " << sc2mpdpath << 
515
                   " is not executable" << endl);
516
            sc2mpdpath.clear();
517
        }
518
    }
519
520
    if (!senderpath.empty()) {
521
        // Check that both the starter script and the mpd2sc sender
522
        // command are executable. We'll assume that mpd is ok
523
        if (access(senderpath.c_str(), X_OK|R_OK) != 0) {
524
            LOGERR("The specified path for the sender starter script: ["
525
                   << senderpath <<
526
                   "] is not executable, disabling the sender mode.\n");
527
            senderpath.clear();
528
        } else {
529
            string path;
530
            if (!ExecCmd::which("mpd2sc", path)) {
531
                LOGERR("Sender starter was specified and found but the mpd2sc "
532
                       "command is not found (or executable). Disabling "
533
                       "the sender mode.\n");
534
                senderpath.clear();
535
            }
536
        }
537
    }
538
539
540
    // Initialize MPD client object. Retry until it works or power fail.
541
    MPDCli *mpdclip = 0;
542
    int mpdretrysecs = 2;
543
    for (;;) {
544
        mpdclip = new MPDCli(mpdhost, mpdport, mpdpassword, onstart, onstop,
545
                             onvolumechange);
546
        if (mpdclip == 0) {
547
            LOGFAT("Can't allocate MPD client object" << endl);
548
            return 1;
549
        }
550
        if (!mpdclip->ok()) {
551
            LOGERR("MPD connection failed" << endl);
552
            delete mpdclip;
553
            mpdclip = 0;
554
            sleep(mpdretrysecs);
555
            mpdretrysecs = MIN(2*mpdretrysecs, 120);
556
        } else {
557
            break;
558
        }
559
    }
560
561
    // Initialize libupnpp, and check health
562
    LibUPnP *mylib = 0;
563
    string hwaddr;
564
    int libretrysecs = 10;
565
    for (;;) {
566
        // Libupnp init fails if we're started at boot and the network
567
        // is not ready yet. So retry this forever
568
        mylib = LibUPnP::getLibUPnP(true, &hwaddr, iface, upnpip, upport);
569
        if (mylib) {
570
            break;
571
        }
572
        sleep(libretrysecs);
573
        libretrysecs = MIN(2*libretrysecs, 120);
574
    }
575
576
    if (!mylib->ok()) {
577
        LOGFAT("Lib init failed: " <<
578
               mylib->errAsString("main", mylib->getInitError()) << endl);
579
        return 1;
580
    }
581
582
    if ((cp = getenv("UPMPDCLI_UPNPLOGFILENAME"))) {
583
        char *cp1 = getenv("UPMPDCLI_UPNPLOGLEVEL");
584
        int loglevel = LibUPnP::LogLevelNone;
585
        if (cp1) {
586
            loglevel = atoi(cp1);
587
        }
588
        loglevel = loglevel < 0 ? 0: loglevel;
589
        loglevel = loglevel > int(LibUPnP::LogLevelDebug) ? 
590
            int(LibUPnP::LogLevelDebug) : loglevel;
591
592
        if (loglevel != LibUPnP::LogLevelNone) {
593
            mylib->setLogFileName(cp, LibUPnP::LogLevel(loglevel));
594
        }
595
    }
596
597
    // Create unique ID
598
    string UUID = LibUPnP::makeDevUUID(friendlyname, hwaddr);
599
600
    // Initialize the data we serve through HTTP (device and service
601
    // descriptions, icons, presentation page, etc.)
602
    unordered_map<string, VDirContent> files;
603
    if (!initHttpFs(files, g_datadir, UUID, friendlyname, enableAV, enableOH,
604
                    !senderpath.empty(),
605
                    iconpath, presentationhtml)) {
606
        exit(1);
607
    }
608
609
    if (ownqueue)
610
        opts.options |= UpMpd::upmpdOwnQueue;
611
    if (enableOH)
612
        opts.options |= UpMpd::upmpdDoOH;
613
    if (ohmetapersist)
614
        opts.options |= UpMpd::upmpdOhMetaPersist;
615
    if (!sc2mpdpath.empty()) {
616
        opts.sc2mpdpath = sc2mpdpath;
617
        opts.options |= UpMpd::upmpdOhReceiver;
618
    }
619
    if (!senderpath.empty()) {
620
        opts.options |= UpMpd::upmpdOhSenderReceiver;
621
        opts.senderpath = senderpath;
622
        opts.sendermpdport = sendermpdport;
623
    }
624
625
    if (!enableAV)
626
        opts.options |= UpMpd::upmpdNoAV;
627
    // Initialize the UPnP device object.
628
    UpMpd device(string("uuid:") + UUID, friendlyname, 
629
                 files, mpdclip, opts);
630
    dev = &device;
631
632
    // And forever generate state change events.
633
    LOGDEB("Entering event loop" << endl);
634
    setupsigs();
635
    device.eventloop();
636
    LOGDEB("Event loop returned" << endl);
637
638
    return 0;
639
}