-rw-r--r-- | core/opie-login/loginapplication.cpp | 193 | ||||
-rw-r--r-- | core/opie-login/loginapplication.h | 40 | ||||
-rw-r--r-- | core/opie-login/loginwindowimpl.cpp | 254 | ||||
-rw-r--r-- | core/opie-login/main.cpp | 124 | ||||
-rw-r--r-- | core/opie-login/opie-login.pro | 2 |
5 files changed, 389 insertions, 224 deletions
diff --git a/core/opie-login/loginapplication.cpp b/core/opie-login/loginapplication.cpp new file mode 100644 index 0000000..cd58c47 --- a/dev/null +++ b/core/opie-login/loginapplication.cpp @@ -0,0 +1,193 @@ +#include <pwd.h> +#include <grp.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> + +#ifdef USEPAM +extern "C" { +#include <security/pam_appl.h> +} +#else +#include <crypt.h> +#include <shadow.h> +#endif + +#include "loginapplication.h" + +LoginApplication *lApp; + +LoginApplication::LoginApplication ( int &argc, char **argv ) + : QPEApplication ( argc, argv, GuiServer ) +{ + lApp = this; +} + +const char *LoginApplication::s_username = 0; + +#ifdef USEPAM + +const char *LoginApplication::s_pam_password = 0; + +int LoginApplication::pam_helper ( int num_msg, const struct pam_message **msg, struct pam_response **resp, void * ) +{ + int replies = 0; + struct pam_response *reply = 0; + int size = sizeof( struct pam_response ); + + for ( int i = 0; i < num_msg; i++ ) { + switch ( msg [i]-> msg_style ) { + case PAM_PROMPT_ECHO_ON: // user name given to PAM already + return PAM_CONV_ERR; + + case PAM_PROMPT_ECHO_OFF: // wants password + reply = (struct pam_response *) ::realloc ( reply, size ); + if ( !reply ) + return PAM_CONV_ERR; + size += sizeof( struct pam_response ); + + reply [replies]. resp_retcode = PAM_SUCCESS; + reply [replies]. resp = ::strdup ( s_pam_password ); + replies++; // PAM frees resp + break; + + case PAM_TEXT_INFO: + break; + + default: + /* unknown or PAM_ERROR_MSG */ + if ( reply ) + ::free ( reply ); + return PAM_CONV_ERR; + } + } + if ( reply ) + *resp = reply; + return PAM_SUCCESS; +} + + +bool LoginApplication::checkPassword ( const char *user, const char *pass ) +{ + static struct pam_conv conv = { &LoginApplication::pam_helper, 0 }; + + int pam_error; + pam_handle_t *pamh = 0; + + pam_error = ::pam_start( "xdm", user, &conv, &pamh ); + if ( pam_error == PAM_SUCCESS ) { + s_pam_password = pass; + pam_error = ::pam_authenticate ( pamh, 0 ); + s_pam_password = 0; + } + ::pam_end ( pamh, pam_error ); + return ( pam_error == PAM_SUCCESS ); +} + +#else + +bool LoginApplication::checkPassword ( const char *user, const char *pass ) +{ + char *encrypted, *correct; + struct passwd *pw; + + if ( !user || !pass ) + return false; + + pw = ::getpwnam ( user ); + + if ( !pw ) + return false; + + if (( ::strcmp ( pw-> pw_passwd, "x" ) == 0 ) || ( ::strcmp ( pw-> pw_passwd, "*" ) == 0 )) { + struct spwd *sp = ::getspnam ( pw-> pw_name ); + + if ( !sp ) + return false; + + correct = sp-> sp_pwdp; + } + else + correct = pw-> pw_passwd; + + if ( correct == 0 || correct[0] == '\0' ) + return true; + + encrypted = ::crypt ( pass, correct ); + return ( ::strcmp ( encrypted, correct ) == 0 ); +} + +#endif + +bool LoginApplication::changeIdentity ( ) +{ + const char *DEFAULT_LOGIN_PATH = "/bin:/usr/bin"; + const char *DEFAULT_ROOT_LOGIN_PATH = "/usr/sbin:/bin:/usr/bin:/sbin"; + + if ( !s_username ) + return false; + struct passwd *pw = ::getpwnam ( s_username ); + if ( !pw ) + return false; + + bool fail = false; + fail |= ( ::initgroups ( pw-> pw_name, pw-> pw_gid )); + ::endgrent ( ); + fail |= ( ::setgid ( pw-> pw_gid )); + fail |= ( ::setuid ( pw-> pw_uid )); + + fail |= ( ::chdir ( pw-> pw_dir ) && ::chdir ( "/" )); + + fail |= ( ::setenv ( "HOME", pw-> pw_dir, 1 )); + fail |= ( ::setenv ( "SHELL", pw-> pw_shell, 1 )); + fail |= ( ::setenv ( "USER", pw-> pw_name, 1 )); + fail |= ( ::setenv ( "LOGNAME", pw-> pw_name, 1 )); + fail |= ( ::setenv ( "PATH", ( pw-> pw_uid ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH ), 1 )); + + return !fail; +} + +bool LoginApplication::login ( ) +{ + char *opie = ::getenv ( "OPIEDIR" ); + char *arg = new char [::strlen ( opie ) + 8 + 1]; + + ::strcpy ( arg, opie ); + ::strcat ( arg, "/bin/qpe" ); + + // start qpe via a login shell + ::execl ( "/bin/sh", "-sh", "-c", arg, 0 ); + + return false; +} + +const char *LoginApplication::loginAs ( ) +{ + return s_username; +} + +void LoginApplication::setLoginAs ( const char *name ) +{ + s_username = name; +} + +QStringList LoginApplication::allUsers ( ) +{ + struct passwd *pwd; + QStringList sl; + + while (( pwd = ::getpwent ( ))) { + if (( pwd-> pw_uid == 0 ) || ( pwd-> pw_uid >= 500 && pwd-> pw_uid < 65534 )) + sl << QString ( pwd-> pw_name ); + } + + ::endpwent ( ); + + return sl; +} + +void LoginApplication::quitToConsole ( ) +{ + QPEApplication::quit ( ); + ::kill ( ::getppid ( ), SIGTERM ); +} diff --git a/core/opie-login/loginapplication.h b/core/opie-login/loginapplication.h new file mode 100644 index 0000000..a709c5b --- a/dev/null +++ b/core/opie-login/loginapplication.h @@ -0,0 +1,40 @@ +#ifndef __OPIE_LOGINAPPLICATION_H__ +#define __OPIE_LOGINAPPLICATION_H__ + +#include <qstringlist.h> + +#include <qpe/qpeapplication.h> + +#ifdef USEPAM +struct pam_message; +struct pam_response; +#endif + +class LoginApplication : public QPEApplication { +public: + LoginApplication ( int &argc, char **argv ); + + static bool checkPassword ( const char *user, const char *password ); + + static const char *loginAs ( ); + static void setLoginAs ( const char *user ); + + static bool changeIdentity ( ); + static bool login ( ); + + static QStringList allUsers ( ); + + void quitToConsole ( ); + +private: + static const char *s_username; + +#ifdef USEPAM + static int pam_helper ( int num_msg, const struct pam_message **msg, struct pam_response **resp, void * ); + static const char *s_pam_password; +#endif +}; + +extern LoginApplication *lApp; + +#endif diff --git a/core/opie-login/loginwindowimpl.cpp b/core/opie-login/loginwindowimpl.cpp index c59338f..3694513 100644 --- a/core/opie-login/loginwindowimpl.cpp +++ b/core/opie-login/loginwindowimpl.cpp @@ -13,27 +13,15 @@ #include <qpe/resource.h> #include <qpe/qcopenvelope_qws.h> +#include <qpe/config.h> #include <opie/odevice.h> #include <stdio.h> - -#include <pwd.h> -#include <grp.h> -#include <unistd.h> #include <stdlib.h> -#include <signal.h> - -#ifdef USEPAM -extern "C" { -#include <security/pam_appl.h> -} -#else -#include <crypt.h> -#include <shadow.h> -#endif #include "loginwindowimpl.h" +#include "loginapplication.h" #include "inputmethods.h" using namespace Opie; @@ -54,7 +42,7 @@ LoginWindowImpl::LoginWindowImpl ( ) : LoginWindow ( 0, "LOGIN-WINDOW", WStyle_C setActiveWindow ( ); m_password-> setFocus ( ); - m_user-> insertStringList ( getAllUsers ( )); + m_user-> insertStringList ( lApp-> allUsers ( )); QTimer::singleShot ( 0, this, SLOT( showIM ( ))); @@ -65,6 +53,13 @@ LoginWindowImpl::LoginWindowImpl ( ) : LoginWindow ( 0, "LOGIN-WINDOW", WStyle_C setBackgroundPixmap ( bgpix ); m_caption-> setText ( m_caption-> text ( ) + tr( "<center>%1 %2</center>" ). arg ( ODevice::inst ( )-> systemString ( )). arg ( ODevice::inst ( )-> systemVersionString ( ))); + + Config cfg ( "opie-login" ); + cfg. setGroup ( "General" ); + QString last = cfg. readEntry ( "LastLogin" ); + + if ( !last. isEmpty ( )) + m_user-> setEditText ( last ); } LoginWindowImpl::~LoginWindowImpl ( ) @@ -90,22 +85,6 @@ void LoginWindowImpl::toggleEchoMode ( bool t ) m_password-> setEchoMode ( t ? QLineEdit::Normal : QLineEdit::Password ); } - -QStringList LoginWindowImpl::getAllUsers ( ) -{ - struct passwd *pwd; - QStringList sl; - - while (( pwd = ::getpwent ( ))) { - if (( pwd-> pw_uid == 0 ) || ( pwd-> pw_uid >= 500 && pwd-> pw_uid < 65534 )) - sl << QString ( pwd-> pw_name ); - } - - ::endpwent ( ); - - return sl; -} - void LoginWindowImpl::showIM ( ) { m_input-> showInputMethod ( ); @@ -118,8 +97,7 @@ void LoginWindowImpl::restart ( ) void LoginWindowImpl::quit ( ) { - qApp-> quit ( ); - ::kill ( ::getppid ( ), SIGUSR1 ); + lApp-> quitToConsole ( ); } void LoginWindowImpl::suspend ( ) @@ -136,207 +114,61 @@ void LoginWindowImpl::backlight ( ) e << -2; // toggle } -#ifdef USEPAM - -static const char *_PAM_SERVICE = "xdm"; -static const char *PAM_password; - -typedef const struct pam_message pam_message_type; - -static int PAM_conv( int, pam_message_type **, struct pam_response **, void * ); - -static struct pam_conv PAM_conversation = { - &PAM_conv, - NULL -}; - -//---------------------------------------------------------------------------- - -static char *COPY_STRING( const char * s ) { - return (s) ? strdup(s) : (char *)NULL; -} - -#define GET_MEM if (reply) realloc(reply, size);\ - else reply = (struct pam_response *)malloc(size); \ - if (!reply) return PAM_CONV_ERR; \ - size += sizeof(struct pam_response) - - -static int PAM_conv( int num_msg, pam_message_type **msg, - struct pam_response **resp, void *) +class WaitLogo : public QLabel { +public: + WaitLogo ( ) : QLabel ( 0, "wait hack!", WStyle_Customize | WStyle_NoBorder | WStyle_Tool ) { - int count = 0, replies = 0; - struct pam_response *reply = NULL; - int size = sizeof(struct pam_response); - - for( count = 0; count < num_msg; count++ ) { - switch (msg[count]->msg_style) { - case PAM_PROMPT_ECHO_ON: - /* user name given to PAM already */ - return PAM_CONV_ERR; - - case PAM_PROMPT_ECHO_OFF: - /* wants password */ - GET_MEM; - reply[replies].resp_retcode = PAM_SUCCESS; - reply[replies].resp = COPY_STRING(PAM_password); - replies++; - /* PAM frees resp */ - break; - case PAM_TEXT_INFO: - break; - default: - /* unknown or PAM_ERROR_MSG */ - if (reply) free (reply); - return PAM_CONV_ERR; - } - } - if (reply) *resp = reply; - return PAM_SUCCESS; -} - + QImage img = Resource::loadImage ( "launcher/new_wait" ); + QPixmap pix; + pix. convertFromImage ( img ); + setPixmap ( pix ); + setAlignment ( AlignCenter ); + move ( 0, 0 ); + resize ( qApp-> desktop ( )-> width ( ), qApp-> desktop ( )-> height ( )); -static bool pwcheck_PAM( const char *user, const char *password ) -{ - bool pw_correct = false; - int pam_error; - int pam_return = 0; - pam_handle_t *pamh = 0; - PAM_password = password; - - pam_error = pam_start( _PAM_SERVICE, user, &PAM_conversation, &pamh ); - if( pam_error == PAM_SUCCESS ) { - pam_error = pam_authenticate( pamh, 0 ); - if( pam_error == PAM_SUCCESS ) { - //-- password correct - pw_correct = true; - pam_return = PAM_SUCCESS; - } else { - pam_return = pam_error; - } - } else { - // cerr << "PAM error: " << pam_strerror( pamh, pam_error ) << endl; + m_visible = false; + show ( ); } - pam_end( pamh, pam_return ); - return pw_correct; -} - -#else - -//---------------------------------------------------------------------------- -static bool pwcheck_Unix( const char *user, const char *pass ) + virtual void showEvent ( QShowEvent *e ) { - char *encrypted, *correct; - struct passwd *pw; - - if ( !user || !pass ) - return false; - - pw = getpwnam ( user ); - - if ( !pw ) - return false; - - if (( strcmp ( pw-> pw_passwd, "x" ) == 0 ) || ( strcmp ( pw-> pw_passwd, "*" ) == 0 )) { - struct spwd *sp = getspnam ( pw-> pw_name ); - - if ( !sp ) - return false; - - correct = sp-> sp_pwdp; + QLabel::showEvent ( e ); + m_visible = true; } - else - correct = pw-> pw_passwd; - if ( correct == 0 || correct[0] == '\0' ) - return true; - - encrypted = crypt ( pass, correct ); - return ( strcmp ( encrypted, correct ) == 0 ); -} - -#endif - - -bool LoginWindowImpl::changeIdentity ( const char *user ) + virtual void paintEvent ( QPaintEvent *e ) { - const char *DEFAULT_LOGIN_PATH = "/bin:/usr/bin"; - const char *DEFAULT_ROOT_LOGIN_PATH = "/usr/sbin:/bin:/usr/bin:/sbin"; - - bool fail = false; - struct passwd *pw = getpwnam ( user ); - - fail |= ( pw == 0 ); - fail |= ( initgroups ( pw-> pw_name, pw-> pw_gid )); - endgrent ( ); - fail |= ( setgid ( pw-> pw_gid )); - fail |= ( setuid ( pw-> pw_uid )); - - fail |= ( chdir ( pw-> pw_dir ) && chdir ( "/" )); - - fail |= ( setenv ( "HOME", pw-> pw_dir, 1 )); - fail |= ( setenv ( "SHELL", pw-> pw_shell, 1 )); - fail |= ( setenv ( "USER", pw-> pw_name, 1 )); - fail |= ( setenv ( "LOGNAME", pw-> pw_name, 1 )); - fail |= ( setenv ( "PATH", ( pw-> pw_uid ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH ), 1 )); - - return !fail; + QLabel::paintEvent ( e ); + if ( m_visible ) + qApp-> quit ( ); } +private: + bool m_visible; +}; + void LoginWindowImpl::login ( ) { const char *user = ::strdup ( m_user-> currentText ( ). local8Bit ( )); const char *pass = ::strdup ( m_password-> text ( ). local8Bit ( )); - bool ok; if ( !user || !user [0] ) return; if ( !pass ) pass = ""; -#if defined( USEPAM ) - ok = pwcheck_PAM ( user, pass ); -#else - ok = pwcheck_Unix ( user, pass ); -#endif + if ( lApp-> checkPassword ( user, pass )) { + Config cfg ( "opie-login" ); + cfg. setGroup ( "General" ); + cfg. writeEntry ( "LastLogin", user ); + cfg. write ( ); + + lApp-> setLoginAs ( user ); - if ( ok ) { - if ( changeIdentity ( user )) { // Draw a big wait icon, the image can be altered in later revisions - QWidget *d = QApplication::desktop ( ); m_input-> hideInputMethod ( ); - - QImage img = Resource::loadImage( "launcher/new_wait" ); - QPixmap pix; - pix. convertFromImage ( img ); - QLabel *w = new QLabel ( 0, "wait hack!", WStyle_Customize | WStyle_NoBorder | WStyle_Tool ); - w-> setPixmap ( pix ); - w-> setAlignment ( AlignCenter ); - w-> showMaximized ( ); - qApp-> processEvents ( ); - - char *opie = ::getenv ( "OPIEDIR" ); - char *arg = new char [::strlen ( opie ) + 8 + 1]; - - ::strcpy ( arg, opie ); - ::strcat ( arg, "/bin/qpe" ); - - // start qpe via a login shell - ::execl ( "/bin/sh", "-sh", "-c", arg, 0 ); - - w-> hide ( ); - delete w; - - QMessageBox::critical ( this, tr( "Failure" ), tr( "Could not start OPIE\n(%1)." ). arg ( arg )); - delete [] arg; - - restart ( ); - } - else { - QMessageBox::critical ( this, tr( "Failure" ), tr( "Could not switch to new user identity" )); - restart ( ); - } + new WaitLogo ( ); + // WaitLogo::showEvent() calls qApp-> quit() } else { QMessageBox::warning ( this, tr( "Wrong password" ), tr( "The given password is incorrect." )); diff --git a/core/opie-login/main.cpp b/core/opie-login/main.cpp index df9451d..91610af 100644 --- a/core/opie-login/main.cpp +++ b/core/opie-login/main.cpp @@ -1,12 +1,17 @@ +#define _GNU_SOURCE + +#include <sys/types.h> +#include <time.h> #include <sys/time.h> #include <sys/resource.h> #include <unistd.h> #include <syslog.h> -#include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> +#include <getopt.h> +#include <string.h> #include <qpe/qpeapplication.h> #include <qpe/qcopenvelope_qws.h> @@ -16,17 +21,24 @@ #include <opie/odevice.h> #include <qwindowsystem_qws.h> -#include <qfile.h> +#include <qmessagebox.h> +#include <qlabel.h> +#include <qtimer.h> +#include "loginapplication.h" #include "loginwindowimpl.h" #include "calibrate.h" using namespace Opie; int login_main ( int argc, char **argv ); -void sigusr1 ( int sig ); +void sigterm ( int sig ); void exit_closelog ( ); +static struct option long_options [] = { + { "autologin", 1, 0, 'a' }, + { 0, 0, 0, 0 } +}; int main ( int argc, char **argv ) @@ -35,6 +47,21 @@ int main ( int argc, char **argv ) ::fprintf ( stderr, "%s can only be executed by root. (or chmod +s)", argv [0] ); return 1; } + if ( ::getuid ( ) != 0 ) // qt doesn't really like SUID and + ::setuid ( 0 ); // messes up things like config files + + char *autolog = 0; + int c; + while (( c = ::getopt_long ( argc, argv, "a:", long_options, 0 )) != -1 ) { + switch ( c ) { + case 'a': + autolog = optarg; + break; + default: + ::fprintf ( stderr, "Usage: %s [-a|--autologin=<user>]\n", argv [0] ); + return 2; + } + } // struct rlimit rl; // ::getrlimit ( RLIMIT_NOFILE, &rl ); @@ -45,7 +72,7 @@ int main ( int argc, char **argv ) ::setpgid ( 0, 0 ); ::setsid ( ); - ::signal ( SIGUSR1, sigusr1 ); + ::signal ( SIGTERM, sigterm ); ::openlog ( "opie-login", LOG_CONS, LOG_AUTHPRIV ); ::atexit ( exit_closelog ); @@ -54,27 +81,82 @@ int main ( int argc, char **argv ) pid_t child = ::fork ( ); if ( child < 0 ) { - ::syslog ( LOG_ERR, "Could not fork process" ); + ::syslog ( LOG_ERR, "Could not fork GUI process\n" ); break; - } else if ( child > 0 ) { int status = 0; + time_t started = ::time ( 0 ); while ( ::waitpid ( child, &status, 0 ) < 0 ) { } + + if (( ::time ( 0 ) - started ) < 3 ) { + if ( autolog ) { + ::syslog ( LOG_ERR, "Respawning too fast -- disabling auto-login\n" ); + autolog = 0; } else { + ::syslog ( LOG_ERR, "Respawning too fast -- going down\n" ); + break; + } + } + int killedbysig = 0; + + if ( WIFSIGNALED( status )) { + switch ( WTERMSIG( status )) { + case SIGINT : + case SIGTERM: + case SIGKILL: + break; + + default : + killedbysig = WTERMSIG( status ); + break; + } + } + if ( killedbysig ) { // qpe was killed by an uncaught signal + qApp = 0; + + QWSServer::setDesktopBackground ( QImage ( )); + QApplication *app = new QApplication ( argc, argv, QApplication::GuiServer ); + app-> setFont ( QFont ( "Helvetica", 10 )); + app-> setStyle ( new QPEStyle ( )); + + const char *sig = ::strsignal ( killedbysig ); + QLabel *l = new QLabel ( 0, "sig", Qt::WStyle_Customize | Qt::WStyle_NoBorder | Qt::WStyle_Tool ); + l-> setText ( LoginWindowImpl::tr( "OPIE was terminated\nby an uncaught signal\n(%1)\n" ). arg ( sig )); + l-> setAlignment ( Qt::AlignCenter ); + l-> move ( 0, 0 ); + l-> resize ( app-> desktop ( )-> width ( ), app-> desktop ( )-> height ( )); + l-> show ( ); + QTimer::singleShot ( 3000, app, SLOT( quit ( ))); + app-> exec ( ); + delete app; + qApp = 0; + } + } + else { + if ( autolog ) { + LoginApplication::setLoginAs ( autolog ); + + if ( LoginApplication::changeIdentity ( )) + ::exit ( LoginApplication::login ( )); + else + ::exit ( 0 ); + } + else ::exit ( login_main ( argc, argv )); } } return 0; } -void sigusr1 ( int /*sig*/ ) +void sigterm ( int /*sig*/ ) { ::exit ( 0 ); } + void exit_closelog ( ) { ::closelog ( ); @@ -181,13 +263,14 @@ private: + int login_main ( int argc, char **argv ) { QWSServer::setDesktopBackground( QImage() ); - QPEApplication app ( argc, argv, QApplication::GuiServer ); + LoginApplication *app = new LoginApplication ( argc, argv ); - app. setFont ( QFont ( "Helvetica", 10 )); - app. setStyle ( new QPEStyle ( )); + app-> setFont ( QFont ( "Helvetica", 10 )); + app-> setStyle ( new QPEStyle ( )); ODevice::inst ( )-> setSoftSuspend ( true ); @@ -208,14 +291,29 @@ int login_main ( int argc, char **argv ) LoginWindowImpl *lw = new LoginWindowImpl ( ); - app. setMainWidget ( lw ); - lw-> setGeometry ( 0, 0, app. desktop ( )-> width ( ), app. desktop ( )-> height ( )); + app-> setMainWidget ( lw ); + lw-> setGeometry ( 0, 0, app-> desktop ( )-> width ( ), app-> desktop ( )-> height ( )); lw-> show ( ); - int rc = app. exec ( ); + int rc = app-> exec ( ); ODevice::inst ( )-> setSoftSuspend ( false ); + if ( app-> loginAs ( )) { + if ( app-> changeIdentity ( )) { + app-> login ( ); + + // if login succeeds, it never comes back + + QMessageBox::critical ( 0, LoginWindowImpl::tr( "Failure" ), LoginWindowImpl::tr( "Could not start OPIE." )); + rc = 1; + } + else { + QMessageBox::critical ( 0, LoginWindowImpl::tr( "Failure" ), LoginWindowImpl::tr( "Could not switch to new user identity" )); + rc = 2; + } + + } return rc; } diff --git a/core/opie-login/opie-login.pro b/core/opie-login/opie-login.pro index 35c1ed0..45b9a7a 100644 --- a/core/opie-login/opie-login.pro +++ b/core/opie-login/opie-login.pro @@ -2,10 +2,12 @@ TEMPLATE = app CONFIG = qt warn_on debug usepam HEADERS = loginwindowimpl.h \ + loginapplication.h \ ../launcher/inputmethods.h \ ../apps/calibrate/calibrate.h SOURCES = loginwindowimpl.cpp \ + loginapplication.cpp \ ../launcher/inputmethods.cpp \ ../apps/calibrate/calibrate.cpp \ main.cpp |