diff --git a/GuiMainWindow.cpp b/GuiMainWindow.cpp index 4a1e02b..93cde2f 100644 --- a/GuiMainWindow.cpp +++ b/GuiMainWindow.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "GuiMainWindow.h" #include "GuiTerminalWindow.h" #include "GuiSettingsWindow.h" @@ -73,6 +74,12 @@ void GuiMainWindow::closeTerminal(int index) { GuiTerminalWindow *termWnd = (GuiTerminalWindow*)tabArea->widget(index); if (termWnd) { + if (termWnd->cfg.warn_on_close && + termWnd->as->qtsock->state() == QAbstractSocket::ConnectedState && + QMessageBox::No == QMessageBox::question(this, "Exit Confirmation?", + "Are you sure you want to close this session?", + QMessageBox::Yes|QMessageBox::No)) + return; terminalList.removeAll(termWnd); } tabArea->removeTab(index); @@ -80,9 +87,27 @@ void GuiMainWindow::closeTerminal(int index) void GuiMainWindow::closeTerminal(GuiTerminalWindow *termWnd) { + if (termWnd->cfg.warn_on_close && + termWnd->as->qtsock->state() == QAbstractSocket::ConnectedState && + QMessageBox::No == QMessageBox::question(this, "Exit Confirmation?", + "Are you sure you want to close this session?", + QMessageBox::Yes|QMessageBox::No)) + return; tabArea->removeTab(tabArea->indexOf(termWnd)); terminalList.removeAll(termWnd); } + +void GuiMainWindow::closeEvent ( QCloseEvent * event ) +{ + event->ignore(); + if (QMessageBox::Yes == QMessageBox::question(this, "Exit Confirmation?", + "Are you sure you want to close all the sessions?", + QMessageBox::Yes|QMessageBox::No)) + { + event->accept(); + } +} + void GuiMainWindow::openSettingsWindow() { GuiSettingsWindow *ss = new GuiSettingsWindow(mainWindow); diff --git a/GuiMainWindow.h b/GuiMainWindow.h index a03d491..1be5127 100644 --- a/GuiMainWindow.h +++ b/GuiMainWindow.h @@ -21,6 +21,7 @@ class GuiMainWindow : public QMainWindow ~GuiMainWindow(); GuiTerminalWindow *newTerminal(); bool winEvent ( MSG * msg, long * result ); + void closeEvent ( QCloseEvent * event ); QTabWidget *tabArea; private: diff --git a/GuiSettingsWindow.cpp b/GuiSettingsWindow.cpp index 6c36909..0761004 100644 --- a/GuiSettingsWindow.cpp +++ b/GuiSettingsWindow.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "QtCommon.h" #include "QtConfig.h" extern "C" { @@ -66,7 +67,7 @@ GuiSettingsWindow::GuiSettingsWindow(QWidget *parent) : ui->gp_logfile->setId(ui->rb_sessionlog_overwrite, LGXF_OVR); ui->gp_logfile->setId(ui->rb_sessionlog_append, LGXF_APN); - ui->gp_logfile->setId(ui->rb_sessionlog_askuser, LGXF_ASK); + ui->gp_logfile->setId(ui->rb_sessionlog_askuser, LGXF_ASK__); ui->gp_termopt_echo->setId(ui->rb_termopt_echoauto, AUTO); ui->gp_termopt_echo->setId(ui->rb_termopt_echoon, FORCE_ON); @@ -121,18 +122,28 @@ void GuiSettingsWindow::on_treeWidget_itemClicked(QTreeWidgetItem *item, int col ui->stackedWidget->setCurrentIndex(item->data(column, Qt::UserRole).toInt()); } -void qstring_to_char(char *dst, QString src, int dstlen) -{ - QByteArray name = src.toUtf8(); - strncpy(dst, name.constData(), dstlen); -} - void GuiSettingsWindow::on_buttonBox_accepted() { int rc; - GuiTerminalWindow *newWnd = mainWindow->newTerminal(); + GuiTerminalWindow *newWnd; + + if (ui->le_hostname->text() == "" && + ui->l_saved_sess->currentItem()->text() == QUTTY_DEFAULT_CONFIG_SETTINGS) { + return; + } else if (ui->le_hostname->text() == "") { + char config_name[100]; + qstring_to_char(config_name, ui->l_saved_sess->currentItem()->text(), sizeof(config_name)); + if (qutty_config.config_list.find(config_name) == qutty_config.config_list.end()) + return; + setConfig(&qutty_config.config_list[config_name]); + } + + newWnd = mainWindow->newTerminal(); newWnd->cfg = *this->getConfig(); + // check for NOT_YET_SUPPORTED configs + chkUnsupportedConfigs(newWnd->cfg); + if ((rc=newWnd->initTerminal())) { delete newWnd; } else { // success @@ -154,8 +165,8 @@ void GuiSettingsWindow::setConfig(Config *_cfg) // update the ui with the given settings ui->le_hostname->setText(cfg.host); - ui->le_port->setText(QString::number(cfg.port)); (cfg.protocol==PROT_SSH ? ui->rb_contype_ssh : ui->rb_contype_telnet)->click(); + ui->le_port->setText(QString::number(cfg.port)); ui->le_saved_sess->setText(cfg.config_name); QList sel_saved_sess = ui->l_saved_sess->findItems(cfg.config_name, Qt::MatchExactly); if (sel_saved_sess.size() > 0) @@ -164,7 +175,10 @@ void GuiSettingsWindow::setConfig(Config *_cfg) /* Options controlling session logging */ ui->gp_seslog->button(cfg.logtype)->click(); ui->le_sessionlog_filename->setText(cfg.logfilename.path); - ui->gp_logfile->button(cfg.logxfovr)->click(); + if (cfg.logxfovr == LGXF_ASK) // handle -ve value + ui->gp_logfile->button(LGXF_ASK__)->click(); + else + ui->gp_logfile->button(cfg.logxfovr)->click(); ui->chb_sessionlog_flush->setChecked(cfg.logflush); ui->chb_sessionlog_omitpasswd->setChecked(cfg.logomitpass); ui->chb_sessionlog_omitdata->setChecked(cfg.logomitdata); @@ -232,6 +246,9 @@ void GuiSettingsWindow::setConfig(Config *_cfg) ui->le_termtype->setText(cfg.termtype); ui->le_termspeed->setText(cfg.termspeed); + /* ssh options */ + ui->le_remote_cmd->setText(cfg.remote_cmd); + /* ssh auth options */ ui->chb_ssh_no_userauth->setChecked(cfg.ssh_no_userauth); ui->chb_ssh_show_banner->setChecked(cfg.ssh_show_banner); @@ -322,6 +339,9 @@ Config *GuiSettingsWindow::getConfig() qstring_to_char(cfg->termtype, ui->le_termtype->text(), sizeof(cfg->termtype)); qstring_to_char(cfg->termspeed, ui->le_termspeed->text(), sizeof(cfg->termspeed)); + /* ssh options */ + qstring_to_char(cfg->remote_cmd, ui->le_remote_cmd->text(), sizeof(cfg->remote_cmd)); + /* ssh auth options */ cfg->ssh_no_userauth = ui->chb_ssh_no_userauth->isChecked(); cfg->ssh_show_banner = ui->chb_ssh_show_banner->isChecked(); @@ -402,3 +422,20 @@ void GuiSettingsWindow::on_btn_ssh_auth_browse_keyfile_clicked() this, tr("Select private key file"), ui->le_ssh_auth_keyfile->text(), tr("*.ppk"))); } + +void GuiSettingsWindow::chkUnsupportedConfigs(Config &cfg) +{ + QString opt_unsupp = ""; + + if (cfg.try_gssapi_auth) { + cfg.try_gssapi_auth = 0; + } + if (cfg.portfwd[0] != '\0') { + cfg.portfwd[0] = '\0'; + opt_unsupp += " * SSH Tunnels/port forwarding\n"; + } + if (opt_unsupp.length() > 0) + QMessageBox::warning(NULL, QObject::tr("Qutty Configuration"), + QObject::tr("Following options are not yet supported in QuTTY.\n\n%1") + .arg(opt_unsupp)); +} diff --git a/GuiSettingsWindow.h b/GuiSettingsWindow.h index 1878067..ff68812 100644 --- a/GuiSettingsWindow.h +++ b/GuiSettingsWindow.h @@ -68,6 +68,8 @@ private slots: void on_btn_ssh_auth_browse_keyfile_clicked(); + void chkUnsupportedConfigs(Config &cfg); + private: Ui::GuiSettingsWindow *ui; }; diff --git a/GuiSettingsWindow.ui b/GuiSettingsWindow.ui index 7eaa8e1..972d435 100644 --- a/GuiSettingsWindow.ui +++ b/GuiSettingsWindow.ui @@ -4146,7 +4146,7 @@ Remote command: - + 10 diff --git a/GuiTerminalWindow.cpp b/GuiTerminalWindow.cpp index e6a670f..2ac7afb 100644 --- a/GuiTerminalWindow.cpp +++ b/GuiTerminalWindow.cpp @@ -27,6 +27,7 @@ GuiTerminalWindow::GuiTerminalWindow(QWidget *parent) : connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vertScrollBarAction(int))); connect(verticalScrollBar(), SIGNAL(sliderMoved(int)), this, SLOT(vertScrollBarMoved(int))); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(vertScrollBarMoved(int))); QPalette pal(palette()); // set black background // not working as expected @@ -70,22 +71,13 @@ int GuiTerminalWindow::initTerminal() { char *realhost = NULL; char *ip_addr = cfg.host; + void *logctx; memset(&ucsdata, 0, sizeof(struct unicode_data)); init_ucs(&cfg, &ucsdata); setTermFont(&cfg.font); cfgtopalette(&cfg); - term = term_init(&cfg, &ucsdata, this); - term_size(term, cfg.height, cfg.width, cfg.savelines); - resize(cfg.width*fontWidth, cfg.height*fontHeight); - // resize according to config if window is smaller - if ( !(mainWindow->windowState() & Qt::WindowMaximized) && - ( mainWindow->size().width() < cfg.width*fontWidth || - mainWindow->size().height() < cfg.height*fontHeight)) - mainWindow->resize(cfg.width*fontWidth, - cfg.height*fontHeight); - backend = backend_from_proto(cfg.protocol); const char * error = backend->init(this, &backhandle, &cfg, (char*)ip_addr, cfg.port, &realhost, 1, 0); if (realhost) @@ -99,6 +91,18 @@ int GuiTerminalWindow::initTerminal() goto cu0; } + term = term_init(&cfg, &ucsdata, this); + logctx = log_init(NULL, &cfg); + term_provide_logctx(term, logctx); + term_size(term, cfg.height, cfg.width, cfg.savelines); + resize(cfg.width*fontWidth, cfg.height*fontHeight); + // resize according to config if window is smaller + if ( !(mainWindow->windowState() & Qt::WindowMaximized) && + ( mainWindow->size().width() < cfg.width*fontWidth || + mainWindow->size().height() < cfg.height*fontHeight)) + mainWindow->resize(cfg.width*fontWidth, + cfg.height*fontHeight); + switch(cfg.protocol) { case PROT_TELNET: as = (Actual_Socket)get_telnet_socket(backhandle); @@ -111,6 +115,10 @@ int GuiTerminalWindow::initTerminal() } qtsock = as->qtsock; QObject::connect(as->qtsock, SIGNAL(readyRead()), this, SLOT(readyRead())); + QObject::connect(as->qtsock, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(sockError())); + QObject::connect(as->qtsock, SIGNAL(disconnected()), + this, SLOT(sockDisconnected())); /* * Connect the terminal to the backend for resize purposes. @@ -139,6 +147,8 @@ TmuxWindowPane *GuiTerminalWindow::initTmuxClientTerminal(TmuxGateway *gateway, cfgtopalette(&cfg); term = term_init(&cfg, &ucsdata, this); + void *logctx = log_init(NULL, &cfg); + term_provide_logctx(term, logctx); // resize according to config if window is smaller if ( !(mainWindow->windowState() & Qt::WindowMaximized) && ( mainWindow->size().width() < cfg.width*fontWidth || @@ -688,3 +698,20 @@ void GuiTerminalWindow::detachTmuxControllerMode() _tmuxGateway = NULL; _tmuxMode = TMUX_MODE_NONE; } + +void GuiTerminalWindow::sockError (QAbstractSocket::SocketError socketError) +{ + char errStr[256]; + qstring_to_char(errStr, as->qtsock->errorString(), sizeof(errStr)); + (*as->plug)->closing(as->plug, errStr, socketError, 0); +} + +void GuiTerminalWindow::sockDisconnected() +{ + char errStr[256], winTitle[256]; + qstring_to_char(errStr, as->qtsock->errorString(), sizeof(errStr)); + (*as->plug)->closing(as->plug, errStr, as->qtsock->error(), 0); + qstring_to_char(winTitle, this->windowTitle(), sizeof(winTitle)); + strncat(winTitle, " (inactive)", sizeof(winTitle)); + set_title(this, winTitle); +} diff --git a/GuiTerminalWindow.h b/GuiTerminalWindow.h index 3ca84b9..3651d9b 100644 --- a/GuiTerminalWindow.h +++ b/GuiTerminalWindow.h @@ -109,6 +109,8 @@ public slots: void vertScrollBarAction(int action); void vertScrollBarMoved(int value); void detachTmuxControllerMode(); + void sockError(QAbstractSocket::SocketError socketError); + void sockDisconnected(); }; diff --git a/QtCommon.cpp b/QtCommon.cpp index abcb7df..511983a 100644 --- a/QtCommon.cpp +++ b/QtCommon.cpp @@ -437,3 +437,9 @@ int tmux_from_backend(void *frontend, int is_stderr, const char *data, int len) GuiTerminalWindow *f = static_cast(frontend); return f->tmuxGateway()->fromBackend(is_stderr, data, len); } + +void qstring_to_char(char *dst, QString src, int dstlen) +{ + QByteArray name = src.toUtf8(); + strncpy(dst, name.constData(), dstlen); +} diff --git a/QtCommon.h b/QtCommon.h index c7a6bbc..6962828 100644 --- a/QtCommon.h +++ b/QtCommon.h @@ -87,4 +87,6 @@ typedef struct telnet_tag { Pinger pinger; } *Telnet; +void qstring_to_char(char *dst, QString src, int dstlen); + #endif // QTCOMMON_H diff --git a/QtConfig.cpp b/QtConfig.cpp index ba18eb2..c2316e2 100644 --- a/QtConfig.cpp +++ b/QtConfig.cpp @@ -8,6 +8,9 @@ #include #include #include +extern "C" { +#include "WINDOWS\STORAGE.H" +} QtConfig::QtConfig() { @@ -181,6 +184,11 @@ bool QtConfig::restoreConfig() { config_list.clear(); QFile file(QDir::home().filePath("qutty.xml")); + + if (!file.exists()) { + restoreFromPuttyWinRegistry(); + } + if (!file.exists()) { Config cfg; initConfigDefaults(&cfg); @@ -199,6 +207,61 @@ bool QtConfig::restoreConfig() return true; } +/* + * Windows only: Try loading the sessions stored in registry by PUTTY + */ +bool QtConfig::restoreFromPuttyWinRegistry() +{ + bool rc = true; + struct sesslist savedSess; + void *sesskey; + Config cfg; + + get_sesslist(&savedSess, TRUE); + qDebug() << "putty nsessions " << savedSess.nsessions; + for (int i=0; iconfig_list[cfg.config_name] = cfg; + + qDebug() << "putty session " << i << " name " << savedSess.sessions[i] + << " host " << cfg.host << " port " << cfg.port; + } + + // load ssh hostkey list from registry + void *handle = enum_sshhostkey_start(); + uchar hostkey[512], hostkey_val[2048]; + if (handle) { + while (enum_sshhostkey_next(handle, hostkey, sizeof(hostkey), + hostkey_val, sizeof(hostkey_val))) { + qutty_config.ssh_host_keys[string((char*)hostkey)] = string((char*)hostkey_val); + } + enum_sshhostkey_finish(handle); + } + + if (savedSess.nsessions > 0) { + rc = this->saveConfig(); + if (rc) { + QMessageBox::information(NULL, QObject::tr("Qutty first-time Configuration"), + QObject::tr("Automatically loaded %1 saved sessions from PuTTY") + .arg(savedSess.nsessions-1)); + } else { + QMessageBox::warning(NULL, QObject::tr("Qutty first-time Configuration"), + QObject::tr("Failed to save %1 saved sessions from PuTTY") + .arg(savedSess.nsessions-1)); + } + } + + get_sesslist(&savedSess, FALSE); /* free */ + + return rc; +} + bool QtConfig::saveConfig() { QFile file(QDir::home().filePath("qutty.xml")); diff --git a/QtConfig.h b/QtConfig.h index 192aff8..dec0ef2 100644 --- a/QtConfig.h +++ b/QtConfig.h @@ -1,7 +1,9 @@ #ifndef QTCONFIG_H #define QTCONFIG_H +extern "C" { #include "putty.h" +} #include #include #include @@ -20,6 +22,9 @@ class QtConfig { bool restoreConfig(); bool saveConfig(); + +private: + bool restoreFromPuttyWinRegistry(); }; // all global config is here diff --git a/QtUnicode.cpp b/QtUnicode.cpp index b731494..1007c96 100644 --- a/QtUnicode.cpp +++ b/QtUnicode.cpp @@ -8,14 +8,30 @@ extern "C" { #include "putty.h" } #include +#include #define CS_QTEXTCODEC 11111111 void *get_text_codec (const char *line_codepage) { - if (!line_codepage || !*line_codepage) - return NULL; - return QTextCodec::codecForName(line_codepage); + QTextCodec *codec = NULL; + if (line_codepage && *line_codepage) { + qDebug() << __FUNCTION__ << " using line_codepage " << line_codepage; + codec = QTextCodec::codecForName(line_codepage); + if (!codec) { + char codepage[100]; + strncpy(codepage, line_codepage, sizeof(codepage)); + if(strtok(codepage, ":(")) + codec = QTextCodec::codecForName(codepage); + } + } + if (!codec) + codec = QTextCodec::codecForLocale(); + if (!codec) + codec = QTextCodec::codecForCStrings(); + qDebug() << __FUNCTION__ << " using codec " + << (codec ? codec->name().constData() : "NULL"); + return codec; } void init_ucs(Config *cfg, struct unicode_data *ucsdata) diff --git a/QuTTY.pro b/QuTTY.pro index f0856ed..8d70b10 100644 --- a/QuTTY.pro +++ b/QuTTY.pro @@ -31,6 +31,9 @@ SOURCES += \ QtX11.cpp \ QtConfig.cpp \ puttysrc/WINDOWS/winnoise.c \ + puttysrc/WINDOWS/winstore.c \ + puttysrc/WINDOWS/windefs.c \ + puttysrc/WINDOWS/settings.c \ puttysrc/PGSSAPI.C \ puttysrc/INT64.C \ puttysrc/ssh.c \ @@ -82,6 +85,7 @@ HEADERS += \ QtLogDbg.h \ QtConfig.h \ QtConfigTag.h \ + puttysrc/WINDOWS/STORAGE.H \ puttysrc/TREE234.H \ puttysrc/TERMINAL.H \ puttysrc/SSHGSSC.H \ diff --git a/puttysrc/WINDOWS/STORAGE.H b/puttysrc/WINDOWS/STORAGE.H new file mode 100644 index 0000000..88888cc --- /dev/null +++ b/puttysrc/WINDOWS/STORAGE.H @@ -0,0 +1,136 @@ +/* + * storage.h: interface defining functions for storage and recovery + * of PuTTY's persistent data. + */ + +#ifndef PUTTY_STORAGE_H +#define PUTTY_STORAGE_H + +#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY" +#define PUTTY_REG_PARENT "Software\\SimonTatham" +#define PUTTY_REG_PARENT_CHILD "PuTTY" +#define PUTTY_REG_GPARENT "Software" +#define PUTTY_REG_GPARENT_CHILD "SimonTatham" + +/* Result values for the jumplist registry functions. */ +#define JUMPLISTREG_OK 0 +#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1 +#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2 +#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3 +#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4 +#define JUMPLISTREG_ERROR_INVALID_VALUE 5 + +/* ---------------------------------------------------------------------- + * Functions to save and restore PuTTY sessions. Note that this is + * only the low-level code to do the reading and writing. The + * higher-level code that translates a Config structure into a set + * of (key,value) pairs is elsewhere, since it doesn't (mostly) + * change between platforms. + */ + +/* + * Write a saved session. The caller is expected to call + * open_setting_w() to get a `void *' handle, then pass that to a + * number of calls to write_setting_s() and write_setting_i(), and + * then close it using close_settings_w(). At the end of this call + * sequence the settings should have been written to the PuTTY + * persistent storage area. + * + * A given key will be written at most once while saving a session. + * Keys may be up to 255 characters long. String values have no length + * limit. + * + * Any returned error message must be freed after use. + */ +/* +void *open_settings_w(const char *sessionname, char **errmsg); +void write_setting_s(void *handle, const char *key, const char *value); +void write_setting_i(void *handle, const char *key, int value); +void write_setting_filename(void *handle, const char *key, Filename value); +void write_setting_fontspec(void *handle, const char *key, FontSpec font); +void close_settings_w(void *handle); +*/ + +/* + * Read a saved session. The caller is expected to call + * open_setting_r() to get a `void *' handle, then pass that to a + * number of calls to read_setting_s() and read_setting_i(), and + * then close it using close_settings_r(). + * + * read_setting_s() writes into the provided buffer and returns a + * pointer to the same buffer. + * + * If a particular string setting is not present in the session, + * read_setting_s() can return NULL, in which case the caller + * should invent a sensible default. If an integer setting is not + * present, read_setting_i() returns its provided default. + * + * read_setting_filename() and read_setting_fontspec() each read into + * the provided buffer, and return zero if they failed to. + */ +void *open_settings_r(const char *sessionname); +char *read_setting_s(void *handle, const char *key, char *buffer, int buflen); +int read_setting_i(void *handle, const char *key, int defvalue); +int read_setting_filename(void *handle, const char *key, Filename *value); +int read_setting_fontspec(void *handle, const char *key, FontSpec *font); +void close_settings_r(void *handle); + +/* + * Delete a whole saved session. + */ +//void del_settings(const char *sessionname); + +/* + * Enumerate all saved sessions. + */ +void *enum_settings_start(void); +char *enum_settings_next(void *handle, char *buffer, int buflen); +void enum_settings_finish(void *handle); + +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's host key database. + */ + +/* + * See if a host key matches the database entry. Return values can + * be 0 (entry matches database), 1 (entry is absent in database), + * or 2 (entry exists in database and is different). + */ +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key); + +/* + * Write a host key into the database, overwriting any previous + * entry that might have been there. + */ +//void store_host_key(const char *hostname, int port, +// const char *keytype, const char *key); + +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's random number seed file. + */ + +//typedef void (*noise_consumer_t) (void *data, int len); + +/* + * Read PuTTY's random seed file and pass its contents to a noise + * consumer function. + */ +//void read_random_seed(noise_consumer_t consumer); + +/* + * Write PuTTY's random seed file from a given chunk of noise. + */ +//void write_random_seed(void *data, int len); + +/* ---------------------------------------------------------------------- + * Cleanup function: remove all of PuTTY's persistent state. + */ +//void cleanup_all(void); + +void *enum_sshhostkey_start(void); +int enum_sshhostkey_next(void *handle, unsigned char *hostkey, DWORD hostkeylen, + unsigned char *hostkey_val, DWORD hostkey_val_len); +void enum_sshhostkey_finish(void *handle); + +#endif diff --git a/puttysrc/WINDOWS/settings.c b/puttysrc/WINDOWS/settings.c new file mode 100644 index 0000000..ec43a6c --- /dev/null +++ b/puttysrc/WINDOWS/settings.c @@ -0,0 +1,1034 @@ +/* + * settings.c: read and write saved sessions. (platform-independent) + */ +/* + * In QuTTY this file is only used for loading saved sessions in + * Windows registry by PuTTY + * So this file is platform-dependent + */ + +#include +#include +#include +#include "putty.h" +#include "storage.h" + +int default_protocol; +int default_port; + +const struct keyvalwhere gsslibkeywords[] = { + { "gssapi32", 0, -1, -1 }, + { "sspi", 1, -1, -1 }, + { "custom", 2, -1, -1 }, +}; + + +/* The cipher order given here is the default order. */ +static const struct keyvalwhere ciphernames[] = { + { "aes", CIPHER_AES, -1, -1 }, + { "blowfish", CIPHER_BLOWFISH, -1, -1 }, + { "3des", CIPHER_3DES, -1, -1 }, + { "WARN", CIPHER_WARN, -1, -1 }, + { "arcfour", CIPHER_ARCFOUR, -1, -1 }, + { "des", CIPHER_DES, -1, -1 } +}; + +static const struct keyvalwhere kexnames[] = { + { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, + { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, + { "dh-group1-sha1", KEX_DHGROUP1, -1, -1 }, + { "rsa", KEX_RSA, KEX_WARN, -1 }, + { "WARN", KEX_WARN, -1, -1 } +}; + +/* + * All the terminal modes that we know about for the "TerminalModes" + * setting. (Also used by config.c for the drop-down list.) + * This is currently precisely the same as the set in ssh.c, but could + * in principle differ if other backends started to support tty modes + * (e.g., the pty backend). + */ +const char *const ttymodes[] = { + "INTR", "QUIT", "ERASE", "KILL", "EOF", + "EOL", "EOL2", "START", "STOP", "SUSP", + "DSUSP", "REPRINT", "WERASE", "LNEXT", "FLUSH", + "SWTCH", "STATUS", "DISCARD", "IGNPAR", "PARMRK", + "INPCK", "ISTRIP", "INLCR", "IGNCR", "ICRNL", + "IUCLC", "IXON", "IXANY", "IXOFF", "IMAXBEL", + "ISIG", "ICANON", "XCASE", "ECHO", "ECHOE", + "ECHOK", "ECHONL", "NOFLSH", "TOSTOP", "IEXTEN", + "ECHOCTL", "ECHOKE", "PENDIN", "OPOST", "OLCUC", + "ONLCR", "OCRNL", "ONOCR", "ONLRET", "CS7", + "CS8", "PARENB", "PARODD", NULL +}; + +/* + * Convenience functions to access the backends[] array + * (which is only present in tools that manage settings). + */ + +Backend *backend_from_name(const char *name) +{ + Backend **p; + for (p = backends; *p != NULL; p++) + if (!strcmp((*p)->name, name)) + return *p; + return NULL; +} + +Backend *backend_from_proto(int proto) +{ + Backend **p; + for (p = backends; *p != NULL; p++) + if ((*p)->protocol == proto) + return *p; + return NULL; +} + +#if 0 +int get_remote_username(Config *cfg, char *user, size_t len) +{ + if (*cfg->username) { + strncpy(user, cfg->username, len); + user[len-1] = '\0'; + } else { + if (cfg->username_from_env) { + /* Use local username. */ + char *luser = get_username(); + if (luser) { + strncpy(user, luser, len); + user[len-1] = '\0'; + sfree(luser); + } else { + *user = '\0'; + } + } else { + *user = '\0'; + } + } + return (*user != '\0'); +} + +#endif // #if 0 + +static void gpps(void *handle, const char *name, const char *def, + char *val, int len) +{ + if (!read_setting_s(handle, name, val, len)) { + char *pdef; + + pdef = platform_default_s(name); + if (pdef) { + strncpy(val, pdef, len); + sfree(pdef); + } else { + strncpy(val, def, len); + } + + val[len - 1] = '\0'; + } +} + +/* + * gppfont and gppfile cannot have local defaults, since the very + * format of a Filename or Font is platform-dependent. So the + * platform-dependent functions MUST return some sort of value. + */ +static void gppfont(void *handle, const char *name, FontSpec *result) +{ + if (!read_setting_fontspec(handle, name, result)) + *result = platform_default_fontspec(name); +} +static void gppfile(void *handle, const char *name, Filename *result) +{ + if (!read_setting_filename(handle, name, result)) + *result = platform_default_filename(name); +} + +static void gppi(void *handle, char *name, int def, int *i) +{ + def = platform_default_i(name, def); + *i = read_setting_i(handle, name, def); +} + +/* + * Read a set of name-value pairs in the format we occasionally use: + * NAME\tVALUE\0NAME\tVALUE\0\0 in memory + * NAME=VALUE,NAME=VALUE, in storage + * `def' is in the storage format. + */ +static void gppmap(void *handle, char *name, char *def, char *val, int len) +{ + char *buf = snewn(2*len, char), *p, *q; + gpps(handle, name, def, buf, 2*len); + p = buf; + q = val; + while (*p) { + while (*p && *p != ',') { + int c = *p++; + if (c == '=') + c = '\t'; + if (c == '\\') + c = *p++; + *q++ = c; + } + if (*p == ',') + p++; + *q++ = '\0'; + } + *q = '\0'; + sfree(buf); +} + +#if 0 +/* + * Write a set of name/value pairs in the above format. + */ +static void wmap(void *handle, char const *key, char const *value, int len) +{ + char *buf = snewn(2*len, char), *p; + const char *q; + p = buf; + q = value; + while (*q) { + while (*q) { + int c = *q++; + if (c == '=' || c == ',' || c == '\\') + *p++ = '\\'; + if (c == '\t') + c = '='; + *p++ = c; + } + *p++ = ','; + q++; + } + *p = '\0'; + write_setting_s(handle, key, buf); + sfree(buf); +} +#endif + +static int key2val(const struct keyvalwhere *mapping, + int nmaps, char *key) +{ + int i; + for (i = 0; i < nmaps; i++) + if (!strcmp(mapping[i].s, key)) return mapping[i].v; + return -1; +} + +#if 0 +static const char *val2key(const struct keyvalwhere *mapping, + int nmaps, int val) +{ + int i; + for (i = 0; i < nmaps; i++) + if (mapping[i].v == val) return mapping[i].s; + return NULL; +} +#endif + +/* + * Helper function to parse a comma-separated list of strings into + * a preference list array of values. Any missing values are added + * to the end and duplicates are weeded. + * XXX: assumes vals in 'mapping' are small +ve integers + */ +static void gprefs(void *sesskey, char *name, char *def, + const struct keyvalwhere *mapping, int nvals, + int *array) +{ + char commalist[256]; + char *p, *q; + int i, j, n, v, pos; + unsigned long seen = 0; /* bitmap for weeding dups etc */ + + /* + * Fetch the string which we'll parse as a comma-separated list. + */ + gpps(sesskey, name, def, commalist, sizeof(commalist)); + + /* + * Go through that list and convert it into values. + */ + n = 0; + p = commalist; + while (1) { + while (*p && *p == ',') p++; + if (!*p) + break; /* no more words */ + + q = p; + while (*p && *p != ',') p++; + if (*p) *p++ = '\0'; + + v = key2val(mapping, nvals, q); + if (v != -1 && !(seen & (1 << v))) { + seen |= (1 << v); + array[n++] = v; + } + } + + /* + * Now go through 'mapping' and add values that weren't mentioned + * in the list we fetched. We may have to loop over it multiple + * times so that we add values before other values whose default + * positions depend on them. + */ + while (n < nvals) { + for (i = 0; i < nvals; i++) { + assert(mapping[i].v < 32); + + if (!(seen & (1 << mapping[i].v))) { + /* + * This element needs adding. But can we add it yet? + */ + if (mapping[i].vrel != -1 && !(seen & (1 << mapping[i].vrel))) + continue; /* nope */ + + /* + * OK, we can work out where to add this element, so + * do so. + */ + if (mapping[i].vrel == -1) { + pos = (mapping[i].where < 0 ? n : 0); + } else { + for (j = 0; j < n; j++) + if (array[j] == mapping[i].vrel) + break; + assert(j < n); /* implied by (seen & (1<= pos; j--) + array[j+1] = array[j]; + array[pos] = mapping[i].v; + n++; + } + } + } +} + +#if 0 + +/* + * Write out a preference list. + */ +static void wprefs(void *sesskey, char *name, + const struct keyvalwhere *mapping, int nvals, + int *array) +{ + char *buf, *p; + int i, maxlen; + + for (maxlen = i = 0; i < nvals; i++) { + const char *s = val2key(mapping, nvals, array[i]); + if (s) { + maxlen += (maxlen > 0 ? 1 : 0) + strlen(s); + } + } + + buf = snewn(maxlen + 1, char); + p = buf; + + for (i = 0; i < nvals; i++) { + const char *s = val2key(mapping, nvals, array[i]); + if (s) { + p += sprintf(p, "%s%s", (p > buf ? "," : ""), s); + } + } + + assert(p - buf == maxlen); + *p = '\0'; + + write_setting_s(sesskey, name, buf); + + sfree(buf); +} + +char *save_settings(char *section, Config * cfg) +{ + void *sesskey; + char *errmsg; + + sesskey = open_settings_w(section, &errmsg); + if (!sesskey) + return errmsg; + save_open_settings(sesskey, cfg); + close_settings_w(sesskey); + return NULL; +} + +void save_open_settings(void *sesskey, Config *cfg) +{ + int i; + char *p; + + write_setting_i(sesskey, "Present", 1); + write_setting_s(sesskey, "HostName", cfg->host); + write_setting_filename(sesskey, "LogFileName", cfg->logfilename); + write_setting_i(sesskey, "LogType", cfg->logtype); + write_setting_i(sesskey, "LogFileClash", cfg->logxfovr); + write_setting_i(sesskey, "LogFlush", cfg->logflush); + write_setting_i(sesskey, "SSHLogOmitPasswords", cfg->logomitpass); + write_setting_i(sesskey, "SSHLogOmitData", cfg->logomitdata); + p = "raw"; + { + const Backend *b = backend_from_proto(cfg->protocol); + if (b) + p = b->name; + } + write_setting_s(sesskey, "Protocol", p); + write_setting_i(sesskey, "PortNumber", cfg->port); + /* The CloseOnExit numbers are arranged in a different order from + * the standard FORCE_ON / FORCE_OFF / AUTO. */ + write_setting_i(sesskey, "CloseOnExit", (cfg->close_on_exit+2)%3); + write_setting_i(sesskey, "WarnOnClose", !!cfg->warn_on_close); + write_setting_i(sesskey, "PingInterval", cfg->ping_interval / 60); /* minutes */ + write_setting_i(sesskey, "PingIntervalSecs", cfg->ping_interval % 60); /* seconds */ + write_setting_i(sesskey, "TCPNoDelay", cfg->tcp_nodelay); + write_setting_i(sesskey, "TCPKeepalives", cfg->tcp_keepalives); + write_setting_s(sesskey, "TerminalType", cfg->termtype); + write_setting_s(sesskey, "TerminalSpeed", cfg->termspeed); + wmap(sesskey, "TerminalModes", cfg->ttymodes, lenof(cfg->ttymodes)); + + /* Address family selection */ + write_setting_i(sesskey, "AddressFamily", cfg->addressfamily); + + /* proxy settings */ + write_setting_s(sesskey, "ProxyExcludeList", cfg->proxy_exclude_list); + write_setting_i(sesskey, "ProxyDNS", (cfg->proxy_dns+2)%3); + write_setting_i(sesskey, "ProxyLocalhost", cfg->even_proxy_localhost); + write_setting_i(sesskey, "ProxyMethod", cfg->proxy_type); + write_setting_s(sesskey, "ProxyHost", cfg->proxy_host); + write_setting_i(sesskey, "ProxyPort", cfg->proxy_port); + write_setting_s(sesskey, "ProxyUsername", cfg->proxy_username); + write_setting_s(sesskey, "ProxyPassword", cfg->proxy_password); + write_setting_s(sesskey, "ProxyTelnetCommand", cfg->proxy_telnet_command); + wmap(sesskey, "Environment", cfg->environmt, lenof(cfg->environmt)); + write_setting_s(sesskey, "UserName", cfg->username); + write_setting_i(sesskey, "UserNameFromEnvironment", cfg->username_from_env); + write_setting_s(sesskey, "LocalUserName", cfg->localusername); + write_setting_i(sesskey, "NoPTY", cfg->nopty); + write_setting_i(sesskey, "Compression", cfg->compression); + write_setting_i(sesskey, "TryAgent", cfg->tryagent); + write_setting_i(sesskey, "AgentFwd", cfg->agentfwd); + write_setting_i(sesskey, "GssapiFwd", cfg->gssapifwd); + write_setting_i(sesskey, "ChangeUsername", cfg->change_username); + wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, + cfg->ssh_cipherlist); + wprefs(sesskey, "KEX", kexnames, KEX_MAX, cfg->ssh_kexlist); + write_setting_i(sesskey, "RekeyTime", cfg->ssh_rekey_time); + write_setting_s(sesskey, "RekeyBytes", cfg->ssh_rekey_data); + write_setting_i(sesskey, "SshNoAuth", cfg->ssh_no_userauth); + write_setting_i(sesskey, "SshBanner", cfg->ssh_show_banner); + write_setting_i(sesskey, "AuthTIS", cfg->try_tis_auth); + write_setting_i(sesskey, "AuthKI", cfg->try_ki_auth); + write_setting_i(sesskey, "AuthGSSAPI", cfg->try_gssapi_auth); +#ifndef NO_GSSAPI + wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, + cfg->ssh_gsslist); + write_setting_filename(sesskey, "GSSCustom", cfg->ssh_gss_custom); +#endif + write_setting_i(sesskey, "SshNoShell", cfg->ssh_no_shell); + write_setting_i(sesskey, "SshProt", cfg->sshprot); + write_setting_s(sesskey, "LogHost", cfg->loghost); + write_setting_i(sesskey, "SSH2DES", cfg->ssh2_des_cbc); + write_setting_filename(sesskey, "PublicKeyFile", cfg->keyfile); + write_setting_s(sesskey, "RemoteCommand", cfg->remote_cmd); + write_setting_i(sesskey, "RFCEnviron", cfg->rfc_environ); + write_setting_i(sesskey, "PassiveTelnet", cfg->passive_telnet); + write_setting_i(sesskey, "BackspaceIsDelete", cfg->bksp_is_delete); + write_setting_i(sesskey, "RXVTHomeEnd", cfg->rxvt_homeend); + write_setting_i(sesskey, "LinuxFunctionKeys", cfg->funky_type); + write_setting_i(sesskey, "NoApplicationKeys", cfg->no_applic_k); + write_setting_i(sesskey, "NoApplicationCursors", cfg->no_applic_c); + write_setting_i(sesskey, "NoMouseReporting", cfg->no_mouse_rep); + write_setting_i(sesskey, "NoRemoteResize", cfg->no_remote_resize); + write_setting_i(sesskey, "NoAltScreen", cfg->no_alt_screen); + write_setting_i(sesskey, "NoRemoteWinTitle", cfg->no_remote_wintitle); + write_setting_i(sesskey, "RemoteQTitleAction", cfg->remote_qtitle_action); + write_setting_i(sesskey, "NoDBackspace", cfg->no_dbackspace); + write_setting_i(sesskey, "NoRemoteCharset", cfg->no_remote_charset); + write_setting_i(sesskey, "ApplicationCursorKeys", cfg->app_cursor); + write_setting_i(sesskey, "ApplicationKeypad", cfg->app_keypad); + write_setting_i(sesskey, "NetHackKeypad", cfg->nethack_keypad); + write_setting_i(sesskey, "AltF4", cfg->alt_f4); + write_setting_i(sesskey, "AltSpace", cfg->alt_space); + write_setting_i(sesskey, "AltOnly", cfg->alt_only); + write_setting_i(sesskey, "ComposeKey", cfg->compose_key); + write_setting_i(sesskey, "CtrlAltKeys", cfg->ctrlaltkeys); + write_setting_i(sesskey, "TelnetKey", cfg->telnet_keyboard); + write_setting_i(sesskey, "TelnetRet", cfg->telnet_newline); + write_setting_i(sesskey, "LocalEcho", cfg->localecho); + write_setting_i(sesskey, "LocalEdit", cfg->localedit); + write_setting_s(sesskey, "Answerback", cfg->answerback); + write_setting_i(sesskey, "AlwaysOnTop", cfg->alwaysontop); + write_setting_i(sesskey, "FullScreenOnAltEnter", cfg->fullscreenonaltenter); + write_setting_i(sesskey, "HideMousePtr", cfg->hide_mouseptr); + write_setting_i(sesskey, "SunkenEdge", cfg->sunken_edge); + write_setting_i(sesskey, "WindowBorder", cfg->window_border); + write_setting_i(sesskey, "CurType", cfg->cursor_type); + write_setting_i(sesskey, "BlinkCur", cfg->blink_cur); + write_setting_i(sesskey, "Beep", cfg->beep); + write_setting_i(sesskey, "BeepInd", cfg->beep_ind); + write_setting_filename(sesskey, "BellWaveFile", cfg->bell_wavefile); + write_setting_i(sesskey, "BellOverload", cfg->bellovl); + write_setting_i(sesskey, "BellOverloadN", cfg->bellovl_n); + write_setting_i(sesskey, "BellOverloadT", cfg->bellovl_t +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "BellOverloadS", cfg->bellovl_s +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "ScrollbackLines", cfg->savelines); + write_setting_i(sesskey, "DECOriginMode", cfg->dec_om); + write_setting_i(sesskey, "AutoWrapMode", cfg->wrap_mode); + write_setting_i(sesskey, "LFImpliesCR", cfg->lfhascr); + write_setting_i(sesskey, "CRImpliesLF", cfg->crhaslf); + write_setting_i(sesskey, "DisableArabicShaping", cfg->arabicshaping); + write_setting_i(sesskey, "DisableBidi", cfg->bidi); + write_setting_i(sesskey, "WinNameAlways", cfg->win_name_always); + write_setting_s(sesskey, "WinTitle", cfg->wintitle); + write_setting_i(sesskey, "TermWidth", cfg->width); + write_setting_i(sesskey, "TermHeight", cfg->height); + write_setting_fontspec(sesskey, "Font", cfg->font); + write_setting_i(sesskey, "FontQuality", cfg->font_quality); + write_setting_i(sesskey, "FontVTMode", cfg->vtmode); + write_setting_i(sesskey, "UseSystemColours", cfg->system_colour); + write_setting_i(sesskey, "TryPalette", cfg->try_palette); + write_setting_i(sesskey, "ANSIColour", cfg->ansi_colour); + write_setting_i(sesskey, "Xterm256Colour", cfg->xterm_256_colour); + write_setting_i(sesskey, "BoldAsColour", cfg->bold_colour); + + for (i = 0; i < 22; i++) { + char buf[20], buf2[30]; + sprintf(buf, "Colour%d", i); + sprintf(buf2, "%d,%d,%d", cfg->colours[i][0], + cfg->colours[i][1], cfg->colours[i][2]); + write_setting_s(sesskey, buf, buf2); + } + write_setting_i(sesskey, "RawCNP", cfg->rawcnp); + write_setting_i(sesskey, "PasteRTF", cfg->rtf_paste); + write_setting_i(sesskey, "MouseIsXterm", cfg->mouse_is_xterm); + write_setting_i(sesskey, "RectSelect", cfg->rect_select); + write_setting_i(sesskey, "MouseOverride", cfg->mouse_override); + for (i = 0; i < 256; i += 32) { + char buf[20], buf2[256]; + int j; + sprintf(buf, "Wordness%d", i); + *buf2 = '\0'; + for (j = i; j < i + 32; j++) { + sprintf(buf2 + strlen(buf2), "%s%d", + (*buf2 ? "," : ""), cfg->wordness[j]); + } + write_setting_s(sesskey, buf, buf2); + } + write_setting_s(sesskey, "LineCodePage", cfg->line_codepage); + write_setting_i(sesskey, "CJKAmbigWide", cfg->cjk_ambig_wide); + write_setting_i(sesskey, "UTF8Override", cfg->utf8_override); + write_setting_s(sesskey, "Printer", cfg->printer); + write_setting_i(sesskey, "CapsLockCyr", cfg->xlat_capslockcyr); + write_setting_i(sesskey, "ScrollBar", cfg->scrollbar); + write_setting_i(sesskey, "ScrollBarFullScreen", cfg->scrollbar_in_fullscreen); + write_setting_i(sesskey, "ScrollOnKey", cfg->scroll_on_key); + write_setting_i(sesskey, "ScrollOnDisp", cfg->scroll_on_disp); + write_setting_i(sesskey, "EraseToScrollback", cfg->erase_to_scrollback); + write_setting_i(sesskey, "LockSize", cfg->resize_action); + write_setting_i(sesskey, "BCE", cfg->bce); + write_setting_i(sesskey, "BlinkText", cfg->blinktext); + write_setting_i(sesskey, "X11Forward", cfg->x11_forward); + write_setting_s(sesskey, "X11Display", cfg->x11_display); + write_setting_i(sesskey, "X11AuthType", cfg->x11_auth); + write_setting_filename(sesskey, "X11AuthFile", cfg->xauthfile); + write_setting_i(sesskey, "LocalPortAcceptAll", cfg->lport_acceptall); + write_setting_i(sesskey, "RemotePortAcceptAll", cfg->rport_acceptall); + wmap(sesskey, "PortForwardings", cfg->portfwd, lenof(cfg->portfwd)); + write_setting_i(sesskey, "BugIgnore1", 2-cfg->sshbug_ignore1); + write_setting_i(sesskey, "BugPlainPW1", 2-cfg->sshbug_plainpw1); + write_setting_i(sesskey, "BugRSA1", 2-cfg->sshbug_rsa1); + write_setting_i(sesskey, "BugIgnore2", 2-cfg->sshbug_ignore2); + write_setting_i(sesskey, "BugHMAC2", 2-cfg->sshbug_hmac2); + write_setting_i(sesskey, "BugDeriveKey2", 2-cfg->sshbug_derivekey2); + write_setting_i(sesskey, "BugRSAPad2", 2-cfg->sshbug_rsapad2); + write_setting_i(sesskey, "BugPKSessID2", 2-cfg->sshbug_pksessid2); + write_setting_i(sesskey, "BugRekey2", 2-cfg->sshbug_rekey2); + write_setting_i(sesskey, "BugMaxPkt2", 2-cfg->sshbug_maxpkt2); + write_setting_i(sesskey, "StampUtmp", cfg->stamp_utmp); + write_setting_i(sesskey, "LoginShell", cfg->login_shell); + write_setting_i(sesskey, "ScrollbarOnLeft", cfg->scrollbar_on_left); + write_setting_fontspec(sesskey, "BoldFont", cfg->boldfont); + write_setting_fontspec(sesskey, "WideFont", cfg->widefont); + write_setting_fontspec(sesskey, "WideBoldFont", cfg->wideboldfont); + write_setting_i(sesskey, "ShadowBold", cfg->shadowbold); + write_setting_i(sesskey, "ShadowBoldOffset", cfg->shadowboldoffset); + write_setting_s(sesskey, "SerialLine", cfg->serline); + write_setting_i(sesskey, "SerialSpeed", cfg->serspeed); + write_setting_i(sesskey, "SerialDataBits", cfg->serdatabits); + write_setting_i(sesskey, "SerialStopHalfbits", cfg->serstopbits); + write_setting_i(sesskey, "SerialParity", cfg->serparity); + write_setting_i(sesskey, "SerialFlowControl", cfg->serflow); + write_setting_s(sesskey, "WindowClass", cfg->winclass); +} + +void load_settings(char *section, Config * cfg) +{ + void *sesskey; + + sesskey = open_settings_r(section); + load_open_settings(sesskey, cfg); + close_settings_r(sesskey); + + if (cfg_launchable(cfg)) + add_session_to_jumplist(section); +} + +#endif // #if 0 + +void load_open_settings(void *sesskey, Config *cfg) +{ + int i; + char prot[10]; + + cfg->ssh_subsys = 0; /* FIXME: load this properly */ + cfg->remote_cmd_ptr = NULL; + cfg->remote_cmd_ptr2 = NULL; + cfg->ssh_nc_host[0] = '\0'; + + gpps(sesskey, "HostName", "", cfg->host, sizeof(cfg->host)); + gppfile(sesskey, "LogFileName", &cfg->logfilename); + gppi(sesskey, "LogType", 0, &cfg->logtype); + gppi(sesskey, "LogFileClash", LGXF_ASK, &cfg->logxfovr); + gppi(sesskey, "LogFlush", 1, &cfg->logflush); + gppi(sesskey, "SSHLogOmitPasswords", 1, &cfg->logomitpass); + gppi(sesskey, "SSHLogOmitData", 0, &cfg->logomitdata); + + gpps(sesskey, "Protocol", "default", prot, 10); + cfg->protocol = default_protocol; + cfg->port = default_port; + { + const Backend *b = backend_from_name(prot); + if (b) { + cfg->protocol = b->protocol; + gppi(sesskey, "PortNumber", default_port, &cfg->port); + } + } + + /* Address family selection */ + gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, &cfg->addressfamily); + + /* The CloseOnExit numbers are arranged in a different order from + * the standard FORCE_ON / FORCE_OFF / AUTO. */ + gppi(sesskey, "CloseOnExit", 1, &i); cfg->close_on_exit = (i+1)%3; + gppi(sesskey, "WarnOnClose", 1, &cfg->warn_on_close); + { + /* This is two values for backward compatibility with 0.50/0.51 */ + int pingmin, pingsec; + gppi(sesskey, "PingInterval", 0, &pingmin); + gppi(sesskey, "PingIntervalSecs", 0, &pingsec); + cfg->ping_interval = pingmin * 60 + pingsec; + } + gppi(sesskey, "TCPNoDelay", 1, &cfg->tcp_nodelay); + gppi(sesskey, "TCPKeepalives", 0, &cfg->tcp_keepalives); + gpps(sesskey, "TerminalType", "xterm", cfg->termtype, + sizeof(cfg->termtype)); + gpps(sesskey, "TerminalSpeed", "38400,38400", cfg->termspeed, + sizeof(cfg->termspeed)); + { + /* This hardcodes a big set of defaults in any new saved + * sessions. Let's hope we don't change our mind. */ + int i; + char *def = dupstr(""); + /* Default: all set to "auto" */ + for (i = 0; ttymodes[i]; i++) { + char *def2 = dupprintf("%s%s=A,", def, ttymodes[i]); + sfree(def); + def = def2; + } + gppmap(sesskey, "TerminalModes", def, + cfg->ttymodes, lenof(cfg->ttymodes)); + sfree(def); + } + + /* proxy settings */ + gpps(sesskey, "ProxyExcludeList", "", cfg->proxy_exclude_list, + sizeof(cfg->proxy_exclude_list)); + gppi(sesskey, "ProxyDNS", 1, &i); cfg->proxy_dns = (i+1)%3; + gppi(sesskey, "ProxyLocalhost", 0, &cfg->even_proxy_localhost); + gppi(sesskey, "ProxyMethod", -1, &cfg->proxy_type); + if (cfg->proxy_type == -1) { + int i; + gppi(sesskey, "ProxyType", 0, &i); + if (i == 0) + cfg->proxy_type = PROXY_NONE; + else if (i == 1) + cfg->proxy_type = PROXY_HTTP; + else if (i == 3) + cfg->proxy_type = PROXY_TELNET; + else if (i == 4) + cfg->proxy_type = PROXY_CMD; + else { + gppi(sesskey, "ProxySOCKSVersion", 5, &i); + if (i == 5) + cfg->proxy_type = PROXY_SOCKS5; + else + cfg->proxy_type = PROXY_SOCKS4; + } + } + gpps(sesskey, "ProxyHost", "proxy", cfg->proxy_host, + sizeof(cfg->proxy_host)); + gppi(sesskey, "ProxyPort", 80, &cfg->proxy_port); + gpps(sesskey, "ProxyUsername", "", cfg->proxy_username, + sizeof(cfg->proxy_username)); + gpps(sesskey, "ProxyPassword", "", cfg->proxy_password, + sizeof(cfg->proxy_password)); + gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n", + cfg->proxy_telnet_command, sizeof(cfg->proxy_telnet_command)); + gppmap(sesskey, "Environment", "", cfg->environmt, lenof(cfg->environmt)); + gpps(sesskey, "UserName", "", cfg->username, sizeof(cfg->username)); + gppi(sesskey, "UserNameFromEnvironment", 0, &cfg->username_from_env); + gpps(sesskey, "LocalUserName", "", cfg->localusername, + sizeof(cfg->localusername)); + gppi(sesskey, "NoPTY", 0, &cfg->nopty); + gppi(sesskey, "Compression", 0, &cfg->compression); + gppi(sesskey, "TryAgent", 1, &cfg->tryagent); + gppi(sesskey, "AgentFwd", 0, &cfg->agentfwd); + gppi(sesskey, "ChangeUsername", 0, &cfg->change_username); + gppi(sesskey, "GssapiFwd", 0, &cfg->gssapifwd); + gprefs(sesskey, "Cipher", "\0", + ciphernames, CIPHER_MAX, cfg->ssh_cipherlist); + { + /* Backward-compatibility: we used to have an option to + * disable gex under the "bugs" panel after one report of + * a server which offered it then choked, but we never got + * a server version string or any other reports. */ + char *default_kexes; + gppi(sesskey, "BugDHGEx2", 0, &i); i = 2-i; + if (i == FORCE_ON) + default_kexes = "dh-group14-sha1,dh-group1-sha1,rsa,WARN,dh-gex-sha1"; + else + default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN"; + gprefs(sesskey, "KEX", default_kexes, + kexnames, KEX_MAX, cfg->ssh_kexlist); + } + gppi(sesskey, "RekeyTime", 60, &cfg->ssh_rekey_time); + gpps(sesskey, "RekeyBytes", "1G", cfg->ssh_rekey_data, + sizeof(cfg->ssh_rekey_data)); + gppi(sesskey, "SshProt", 2, &cfg->sshprot); + gpps(sesskey, "LogHost", "", cfg->loghost, sizeof(cfg->loghost)); + gppi(sesskey, "SSH2DES", 0, &cfg->ssh2_des_cbc); + gppi(sesskey, "SshNoAuth", 0, &cfg->ssh_no_userauth); + gppi(sesskey, "SshBanner", 1, &cfg->ssh_show_banner); + gppi(sesskey, "AuthTIS", 0, &cfg->try_tis_auth); + gppi(sesskey, "AuthKI", 1, &cfg->try_ki_auth); + gppi(sesskey, "AuthGSSAPI", 1, &cfg->try_gssapi_auth); +#ifndef NO_GSSAPI + gprefs(sesskey, "GSSLibs", "\0", + gsslibkeywords, ngsslibs, cfg->ssh_gsslist); + gppfile(sesskey, "GSSCustom", &cfg->ssh_gss_custom); +#endif + gppi(sesskey, "SshNoShell", 0, &cfg->ssh_no_shell); + gppfile(sesskey, "PublicKeyFile", &cfg->keyfile); + gpps(sesskey, "RemoteCommand", "", cfg->remote_cmd, + sizeof(cfg->remote_cmd)); + gppi(sesskey, "RFCEnviron", 0, &cfg->rfc_environ); + gppi(sesskey, "PassiveTelnet", 0, &cfg->passive_telnet); + gppi(sesskey, "BackspaceIsDelete", 1, &cfg->bksp_is_delete); + gppi(sesskey, "RXVTHomeEnd", 0, &cfg->rxvt_homeend); + gppi(sesskey, "LinuxFunctionKeys", 0, &cfg->funky_type); + gppi(sesskey, "NoApplicationKeys", 0, &cfg->no_applic_k); + gppi(sesskey, "NoApplicationCursors", 0, &cfg->no_applic_c); + gppi(sesskey, "NoMouseReporting", 0, &cfg->no_mouse_rep); + gppi(sesskey, "NoRemoteResize", 0, &cfg->no_remote_resize); + gppi(sesskey, "NoAltScreen", 0, &cfg->no_alt_screen); + gppi(sesskey, "NoRemoteWinTitle", 0, &cfg->no_remote_wintitle); + { + /* Backward compatibility */ + int no_remote_qtitle; + gppi(sesskey, "NoRemoteQTitle", 1, &no_remote_qtitle); + /* We deliberately interpret the old setting of "no response" as + * "empty string". This changes the behaviour, but hopefully for + * the better; the user can always recover the old behaviour. */ + gppi(sesskey, "RemoteQTitleAction", + no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL, + &cfg->remote_qtitle_action); + } + gppi(sesskey, "NoDBackspace", 0, &cfg->no_dbackspace); + gppi(sesskey, "NoRemoteCharset", 0, &cfg->no_remote_charset); + gppi(sesskey, "ApplicationCursorKeys", 0, &cfg->app_cursor); + gppi(sesskey, "ApplicationKeypad", 0, &cfg->app_keypad); + gppi(sesskey, "NetHackKeypad", 0, &cfg->nethack_keypad); + gppi(sesskey, "AltF4", 1, &cfg->alt_f4); + gppi(sesskey, "AltSpace", 0, &cfg->alt_space); + gppi(sesskey, "AltOnly", 0, &cfg->alt_only); + gppi(sesskey, "ComposeKey", 0, &cfg->compose_key); + gppi(sesskey, "CtrlAltKeys", 1, &cfg->ctrlaltkeys); + gppi(sesskey, "TelnetKey", 0, &cfg->telnet_keyboard); + gppi(sesskey, "TelnetRet", 1, &cfg->telnet_newline); + gppi(sesskey, "LocalEcho", AUTO, &cfg->localecho); + gppi(sesskey, "LocalEdit", AUTO, &cfg->localedit); + gpps(sesskey, "Answerback", "PuTTY", cfg->answerback, + sizeof(cfg->answerback)); + gppi(sesskey, "AlwaysOnTop", 0, &cfg->alwaysontop); + gppi(sesskey, "FullScreenOnAltEnter", 0, &cfg->fullscreenonaltenter); + gppi(sesskey, "HideMousePtr", 0, &cfg->hide_mouseptr); + gppi(sesskey, "SunkenEdge", 0, &cfg->sunken_edge); + gppi(sesskey, "WindowBorder", 1, &cfg->window_border); + gppi(sesskey, "CurType", 0, &cfg->cursor_type); + gppi(sesskey, "BlinkCur", 0, &cfg->blink_cur); + /* pedantic compiler tells me I can't use &cfg->beep as an int * :-) */ + gppi(sesskey, "Beep", 1, &cfg->beep); + gppi(sesskey, "BeepInd", 0, &cfg->beep_ind); + gppfile(sesskey, "BellWaveFile", &cfg->bell_wavefile); + gppi(sesskey, "BellOverload", 1, &cfg->bellovl); + gppi(sesskey, "BellOverloadN", 5, &cfg->bellovl_n); + gppi(sesskey, "BellOverloadT", 2*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + , &i); + cfg->bellovl_t = i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ; + gppi(sesskey, "BellOverloadS", 5*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + , &i); + cfg->bellovl_s = i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ; + gppi(sesskey, "ScrollbackLines", 200, &cfg->savelines); + gppi(sesskey, "DECOriginMode", 0, &cfg->dec_om); + gppi(sesskey, "AutoWrapMode", 1, &cfg->wrap_mode); + gppi(sesskey, "LFImpliesCR", 0, &cfg->lfhascr); + gppi(sesskey, "CRImpliesLF", 0, &cfg->crhaslf); + gppi(sesskey, "DisableArabicShaping", 0, &cfg->arabicshaping); + gppi(sesskey, "DisableBidi", 0, &cfg->bidi); + gppi(sesskey, "WinNameAlways", 1, &cfg->win_name_always); + gpps(sesskey, "WinTitle", "", cfg->wintitle, sizeof(cfg->wintitle)); + gppi(sesskey, "TermWidth", 80, &cfg->width); + gppi(sesskey, "TermHeight", 24, &cfg->height); + gppfont(sesskey, "Font", &cfg->font); + gppi(sesskey, "FontQuality", FQ_DEFAULT, &cfg->font_quality); + gppi(sesskey, "FontVTMode", VT_UNICODE, (int *) &cfg->vtmode); + gppi(sesskey, "UseSystemColours", 0, &cfg->system_colour); + gppi(sesskey, "TryPalette", 0, &cfg->try_palette); + gppi(sesskey, "ANSIColour", 1, &cfg->ansi_colour); + gppi(sesskey, "Xterm256Colour", 1, &cfg->xterm_256_colour); + gppi(sesskey, "BoldAsColour", 1, &cfg->bold_colour); + + for (i = 0; i < 22; i++) { + static const char *const defaults[] = { + "187,187,187", "255,255,255", "0,0,0", "85,85,85", "0,0,0", + "0,255,0", "0,0,0", "85,85,85", "187,0,0", "255,85,85", + "0,187,0", "85,255,85", "187,187,0", "255,255,85", "0,0,187", + "85,85,255", "187,0,187", "255,85,255", "0,187,187", + "85,255,255", "187,187,187", "255,255,255" + }; + char buf[20], buf2[30]; + int c0, c1, c2; + sprintf(buf, "Colour%d", i); + gpps(sesskey, buf, defaults[i], buf2, sizeof(buf2)); + if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) { + cfg->colours[i][0] = c0; + cfg->colours[i][1] = c1; + cfg->colours[i][2] = c2; + } + } + gppi(sesskey, "RawCNP", 0, &cfg->rawcnp); + gppi(sesskey, "PasteRTF", 0, &cfg->rtf_paste); + gppi(sesskey, "MouseIsXterm", 0, &cfg->mouse_is_xterm); + gppi(sesskey, "RectSelect", 0, &cfg->rect_select); + gppi(sesskey, "MouseOverride", 1, &cfg->mouse_override); + for (i = 0; i < 256; i += 32) { + static const char *const defaults[] = { + "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0", + "0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1", + "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2", + "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1", + "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", + "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", + "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2", + "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2" + }; + char buf[20], buf2[256], *p; + int j; + sprintf(buf, "Wordness%d", i); + gpps(sesskey, buf, defaults[i / 32], buf2, sizeof(buf2)); + p = buf2; + for (j = i; j < i + 32; j++) { + char *q = p; + while (*p && *p != ',') + p++; + if (*p == ',') + *p++ = '\0'; + cfg->wordness[j] = atoi(q); + } + } + /* + * The empty default for LineCodePage will be converted later + * into a plausible default for the locale. + */ + gpps(sesskey, "LineCodePage", "", cfg->line_codepage, + sizeof(cfg->line_codepage)); + gppi(sesskey, "CJKAmbigWide", 0, &cfg->cjk_ambig_wide); + gppi(sesskey, "UTF8Override", 1, &cfg->utf8_override); + gpps(sesskey, "Printer", "", cfg->printer, sizeof(cfg->printer)); + gppi (sesskey, "CapsLockCyr", 0, &cfg->xlat_capslockcyr); + gppi(sesskey, "ScrollBar", 1, &cfg->scrollbar); + gppi(sesskey, "ScrollBarFullScreen", 0, &cfg->scrollbar_in_fullscreen); + gppi(sesskey, "ScrollOnKey", 0, &cfg->scroll_on_key); + gppi(sesskey, "ScrollOnDisp", 1, &cfg->scroll_on_disp); + gppi(sesskey, "EraseToScrollback", 1, &cfg->erase_to_scrollback); + gppi(sesskey, "LockSize", 0, &cfg->resize_action); + gppi(sesskey, "BCE", 1, &cfg->bce); + gppi(sesskey, "BlinkText", 0, &cfg->blinktext); + gppi(sesskey, "X11Forward", 0, &cfg->x11_forward); + gpps(sesskey, "X11Display", "", cfg->x11_display, + sizeof(cfg->x11_display)); + gppi(sesskey, "X11AuthType", X11_MIT, &cfg->x11_auth); + gppfile(sesskey, "X11AuthFile", &cfg->xauthfile); + + gppi(sesskey, "LocalPortAcceptAll", 0, &cfg->lport_acceptall); + gppi(sesskey, "RemotePortAcceptAll", 0, &cfg->rport_acceptall); + gppmap(sesskey, "PortForwardings", "", cfg->portfwd, lenof(cfg->portfwd)); + gppi(sesskey, "BugIgnore1", 0, &i); cfg->sshbug_ignore1 = 2-i; + gppi(sesskey, "BugPlainPW1", 0, &i); cfg->sshbug_plainpw1 = 2-i; + gppi(sesskey, "BugRSA1", 0, &i); cfg->sshbug_rsa1 = 2-i; + gppi(sesskey, "BugIgnore2", 0, &i); cfg->sshbug_ignore2 = 2-i; + { + int i; + gppi(sesskey, "BugHMAC2", 0, &i); cfg->sshbug_hmac2 = 2-i; + if (cfg->sshbug_hmac2 == AUTO) { + gppi(sesskey, "BuggyMAC", 0, &i); + if (i == 1) + cfg->sshbug_hmac2 = FORCE_ON; + } + } + gppi(sesskey, "BugDeriveKey2", 0, &i); cfg->sshbug_derivekey2 = 2-i; + gppi(sesskey, "BugRSAPad2", 0, &i); cfg->sshbug_rsapad2 = 2-i; + gppi(sesskey, "BugPKSessID2", 0, &i); cfg->sshbug_pksessid2 = 2-i; + gppi(sesskey, "BugRekey2", 0, &i); cfg->sshbug_rekey2 = 2-i; + gppi(sesskey, "BugMaxPkt2", 0, &i); cfg->sshbug_maxpkt2 = 2-i; + cfg->ssh_simple = FALSE; + gppi(sesskey, "StampUtmp", 1, &cfg->stamp_utmp); + gppi(sesskey, "LoginShell", 1, &cfg->login_shell); + gppi(sesskey, "ScrollbarOnLeft", 0, &cfg->scrollbar_on_left); + gppi(sesskey, "ShadowBold", 0, &cfg->shadowbold); + gppfont(sesskey, "BoldFont", &cfg->boldfont); + gppfont(sesskey, "WideFont", &cfg->widefont); + gppfont(sesskey, "WideBoldFont", &cfg->wideboldfont); + gppi(sesskey, "ShadowBoldOffset", 1, &cfg->shadowboldoffset); + gpps(sesskey, "SerialLine", "", cfg->serline, sizeof(cfg->serline)); + gppi(sesskey, "SerialSpeed", 9600, &cfg->serspeed); + gppi(sesskey, "SerialDataBits", 8, &cfg->serdatabits); + gppi(sesskey, "SerialStopHalfbits", 2, &cfg->serstopbits); + gppi(sesskey, "SerialParity", SER_PAR_NONE, &cfg->serparity); + gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, &cfg->serflow); + gpps(sesskey, "WindowClass", "", cfg->winclass, sizeof(cfg->winclass)); +} + +#if 0 +void do_defaults(char *session, Config * cfg) +{ + load_settings(session, cfg); +} +#endif // #if 0 + +static int sessioncmp(const void *av, const void *bv) +{ + const char *a = *(const char *const *) av; + const char *b = *(const char *const *) bv; + + /* + * Alphabetical order, except that "Default Settings" is a + * special case and comes first. + */ + if (!strcmp(a, "Default Settings")) + return -1; /* a comes first */ + if (!strcmp(b, "Default Settings")) + return +1; /* b comes first */ + /* + * FIXME: perhaps we should ignore the first & in determining + * sort order. + */ + return strcmp(a, b); /* otherwise, compare normally */ +} + +void get_sesslist(struct sesslist *list, int allocate) +{ + char otherbuf[2048]; + int buflen, bufsize, i; + char *p, *ret; + void *handle; + + if (allocate) { + + buflen = bufsize = 0; + list->buffer = NULL; + if ((handle = enum_settings_start()) != NULL) { + do { + ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf)); + if (ret) { + int len = strlen(otherbuf) + 1; + if (bufsize < buflen + len) { + bufsize = buflen + len + 2048; + list->buffer = sresize(list->buffer, bufsize, char); + } + strcpy(list->buffer + buflen, otherbuf); + buflen += strlen(list->buffer + buflen) + 1; + } + } while (ret); + enum_settings_finish(handle); + } + list->buffer = sresize(list->buffer, buflen + 1, char); + list->buffer[buflen] = '\0'; + + /* + * Now set up the list of sessions. Note that "Default + * Settings" must always be claimed to exist, even if it + * doesn't really. + */ + + p = list->buffer; + list->nsessions = 1; /* "Default Settings" counts as one */ + while (*p) { + if (strcmp(p, "Default Settings")) + list->nsessions++; + while (*p) + p++; + p++; + } + + list->sessions = snewn(list->nsessions + 1, char *); + list->sessions[0] = "Default Settings"; + p = list->buffer; + i = 1; + while (*p) { + if (strcmp(p, "Default Settings")) + list->sessions[i++] = p; + while (*p) + p++; + p++; + } + + qsort(list->sessions, i, sizeof(char *), sessioncmp); + } else { + sfree(list->buffer); + sfree(list->sessions); + list->buffer = NULL; + list->sessions = NULL; + } +} diff --git a/puttysrc/WINDOWS/windefs.C b/puttysrc/WINDOWS/windefs.C new file mode 100644 index 0000000..6fd6f0c --- /dev/null +++ b/puttysrc/WINDOWS/windefs.C @@ -0,0 +1,43 @@ +/* + * windefs.c: default settings that are specific to Windows. + */ + +#include "putty.h" + +#include + +FontSpec platform_default_fontspec(const char *name) +{ + FontSpec ret; + if (!strcmp(name, "Font")) { + strcpy(ret.name, "Courier New"); + ret.isbold = 0; + ret.charset = ANSI_CHARSET; + ret.height = 10; + } else { + ret.name[0] = '\0'; + } + return ret; +} + +Filename platform_default_filename(const char *name) +{ + Filename ret; + if (!strcmp(name, "LogFileName")) + strcpy(ret.path, "putty.log"); + else + *ret.path = '\0'; + return ret; +} + +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "SerialLine")) + return dupstr("COM1"); + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} diff --git a/puttysrc/WINDOWS/winstore.C b/puttysrc/WINDOWS/winstore.C new file mode 100644 index 0000000..c5fa9be --- /dev/null +++ b/puttysrc/WINDOWS/winstore.C @@ -0,0 +1,879 @@ +/* + * winstore.c: Windows-specific implementation of the interface + * defined in storage.h. + */ + +#include "windows.h" +#include +#include +#include +#include "putty.h" +#include "storage.h" + +#include +#ifndef CSIDL_APPDATA +#define CSIDL_APPDATA 0x001a +#endif +#ifndef CSIDL_LOCAL_APPDATA +#define CSIDL_LOCAL_APPDATA 0x001c +#endif + +#if 0 +static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist"; +static const char *const reg_jumplist_value = "Recent sessions"; +#endif // #if 0 +static const char *puttystr = PUTTY_REG_POS "\\Sessions"; + +static const char hex[16] = "0123456789ABCDEF"; + +#if 0 +static int tried_shgetfolderpath = FALSE; +static HMODULE shell32_module = NULL; +DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, + (HWND, int, HANDLE, DWORD, LPSTR)); +#endif // #if 0 + +static void mungestr(const char *in, char *out) +{ + int candot = 0; + + while (*in) { + if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || + *in == '%' || *in < ' ' || *in > '~' || (*in == '.' + && !candot)) { + *out++ = '%'; + *out++ = hex[((unsigned char) *in) >> 4]; + *out++ = hex[((unsigned char) *in) & 15]; + } else + *out++ = *in; + in++; + candot = 1; + } + *out = '\0'; + return; +} + +static void unmungestr(const char *in, char *out, int outlen) +{ + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + *out++ = (i << 4) + j; + if (!--outlen) + return; + in += 3; + } else { + *out++ = *in++; + if (!--outlen) + return; + } + } + *out = '\0'; + return; +} + +#if 0 + +void *open_settings_w(const char *sessionname, char **errmsg) +{ + HKEY subkey1, sesskey; + int ret; + char *p; + + *errmsg = NULL; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + + ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); + if (ret != ERROR_SUCCESS) { + sfree(p); + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s", puttystr); + return NULL; + } + ret = RegCreateKey(subkey1, p, &sesskey); + RegCloseKey(subkey1); + if (ret != ERROR_SUCCESS) { + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s\\%s", puttystr, p); + sfree(p); + return NULL; + } + sfree(p); + return (void *) sesskey; +} + +void write_setting_s(void *handle, const char *key, const char *value) +{ + if (handle) + RegSetValueEx((HKEY) handle, key, 0, REG_SZ, value, + 1 + strlen(value)); +} + +void write_setting_i(void *handle, const char *key, int value) +{ + if (handle) + RegSetValueEx((HKEY) handle, key, 0, REG_DWORD, + (CONST BYTE *) &value, sizeof(value)); +} + +void close_settings_w(void *handle) +{ + RegCloseKey((HKEY) handle); +} + +#endif // #if 0 + +void *open_settings_r(const char *sessionname) +{ + HKEY subkey1, sesskey; + char *p; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + + if (RegOpenKeyA(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { + sesskey = NULL; + } else { + if (RegOpenKeyA(subkey1, p, &sesskey) != ERROR_SUCCESS) { + sesskey = NULL; + } + RegCloseKey(subkey1); + } + + sfree(p); + + return (void *) sesskey; +} + +char *read_setting_s(void *handle, const char *key, char *buffer, int buflen) +{ + DWORD type, size; + size = buflen; + + if (!handle || + RegQueryValueExA((HKEY) handle, key, 0, + &type, (BYTE *) buffer, &size) != ERROR_SUCCESS || + type != REG_SZ) return NULL; + else + return buffer; +} + +int read_setting_i(void *handle, const char *key, int defvalue) +{ + DWORD type, val, size; + size = sizeof(val); + + if (!handle || + RegQueryValueExA((HKEY) handle, key, 0, &type, + (BYTE *) &val, &size) != ERROR_SUCCESS || + size != sizeof(val) || type != REG_DWORD) + return defvalue; + else + return val; +} + +int read_setting_fontspec(void *handle, const char *name, FontSpec *result) +{ + char *settingname; + FontSpec ret; + + if (!read_setting_s(handle, name, ret.name, sizeof(ret.name))) + return 0; + settingname = dupcat(name, "IsBold", NULL); + ret.isbold = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (ret.isbold == -1) return 0; + settingname = dupcat(name, "CharSet", NULL); + ret.charset = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (ret.charset == -1) return 0; + settingname = dupcat(name, "Height", NULL); + ret.height = read_setting_i(handle, settingname, INT_MIN); + sfree(settingname); + if (ret.height == INT_MIN) return 0; + *result = ret; + return 1; +} + +#if 0 +void write_setting_fontspec(void *handle, const char *name, FontSpec font) +{ + char *settingname; + + write_setting_s(handle, name, font.name); + settingname = dupcat(name, "IsBold", NULL); + write_setting_i(handle, settingname, font.isbold); + sfree(settingname); + settingname = dupcat(name, "CharSet", NULL); + write_setting_i(handle, settingname, font.charset); + sfree(settingname); + settingname = dupcat(name, "Height", NULL); + write_setting_i(handle, settingname, font.height); + sfree(settingname); +} +#endif // #if 0 + +int read_setting_filename(void *handle, const char *name, Filename *result) +{ + return !!read_setting_s(handle, name, result->path, sizeof(result->path)); +} + +#if 0 +void write_setting_filename(void *handle, const char *name, Filename result) +{ + write_setting_s(handle, name, result.path); +} +#endif // #if 0 + +void close_settings_r(void *handle) +{ + RegCloseKey((HKEY) handle); +} + +#if 0 +void del_settings(const char *sessionname) +{ + HKEY subkey1; + char *p; + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) + return; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + RegDeleteKey(subkey1, p); + sfree(p); + + RegCloseKey(subkey1); + + remove_session_from_jumplist(sessionname); +} +#endif // #if 0 + +struct enumsettings { + HKEY key; + int i; +}; + +void *enum_settings_start(void) +{ + struct enumsettings *ret; + HKEY key; + + if (RegOpenKeyA(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) + return NULL; + + ret = snew(struct enumsettings); + if (ret) { + ret->key = key; + ret->i = 0; + } + + return ret; +} + +char *enum_settings_next(void *handle, char *buffer, int buflen) +{ + struct enumsettings *e = (struct enumsettings *) handle; + char *otherbuf; + otherbuf = snewn(3 * buflen, char); + if (RegEnumKeyA(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) { + unmungestr(otherbuf, buffer, buflen); + sfree(otherbuf); + return buffer; + } else { + sfree(otherbuf); + return NULL; + } +} + +void enum_settings_finish(void *handle) +{ + struct enumsettings *e = (struct enumsettings *) handle; + RegCloseKey(e->key); + sfree(e); +} + +static void hostkey_regname(char *buffer, const char *hostname, + int port, const char *keytype) +{ + int len; + strcpy(buffer, keytype); + strcat(buffer, "@"); + len = strlen(buffer); + len += sprintf(buffer + len, "%d:", port); + mungestr(hostname, buffer + strlen(buffer)); +} + + +void *enum_sshhostkey_start(void) +{ + struct enumsettings *ret; + HKEY key; + + if (RegOpenKeyA(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", &key) != ERROR_SUCCESS) + return NULL; + + ret = snew(struct enumsettings); + if (ret) { + ret->key = key; + ret->i = 0; + } + + return ret; +} + +int enum_sshhostkey_next(void *handle, unsigned char *hostkey, DWORD hostkeylen, + unsigned char *hostkey_val, DWORD hostkey_val_len) +{ + struct enumsettings *e = (struct enumsettings *) handle; + return RegEnumValueA(e->key, e->i++, + hostkey, &hostkeylen, NULL, NULL, + hostkey_val, &hostkey_val_len) == ERROR_SUCCESS; +} + +void enum_sshhostkey_finish(void *handle) +{ + struct enumsettings *e = (struct enumsettings *) handle; + RegCloseKey(e->key); + sfree(e); +} + +#if 0 + +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + char *otherstr, *regname; + int len; + HKEY rkey; + DWORD readlen; + DWORD type; + int ret, compare; + + len = 1 + strlen(key); + + /* + * Now read a saved key in from the registry and see what it + * says. + */ + otherstr = snewn(len, char); + regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); + + hostkey_regname(regname, hostname, port, keytype); + + if (RegOpenKeyA(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) != ERROR_SUCCESS) + return 1; /* key does not exist in registry */ + + readlen = len; + ret = RegQueryValueExA(rkey, regname, NULL, &type, otherstr, &readlen); + + if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && + !strcmp(keytype, "rsa")) { + /* + * Key didn't exist. If the key type is RSA, we'll try + * another trick, which is to look up the _old_ key format + * under just the hostname and translate that. + */ + char *justhost = regname + 1 + strcspn(regname, ":"); + char *oldstyle = snewn(len + 10, char); /* safety margin */ + readlen = len; + ret = RegQueryValueExA(rkey, justhost, NULL, &type, + oldstyle, &readlen); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + /* + * The old format is two old-style bignums separated by + * a slash. An old-style bignum is made of groups of + * four hex digits: digits are ordered in sensible + * (most to least significant) order within each group, + * but groups are ordered in silly (least to most) + * order within the bignum. The new format is two + * ordinary C-format hex numbers (0xABCDEFG...XYZ, with + * A nonzero except in the special case 0x0, which + * doesn't appear anyway in RSA keys) separated by a + * comma. All hex digits are lowercase in both formats. + */ + char *p = otherstr; + char *q = oldstyle; + int i, j; + + for (i = 0; i < 2; i++) { + int ndigits, nwords; + *p++ = '0'; + *p++ = 'x'; + ndigits = strcspn(q, "/"); /* find / or end of string */ + nwords = ndigits / 4; + /* now trim ndigits to remove leading zeros */ + while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1) + ndigits--; + /* now move digits over to new string */ + for (j = 0; j < ndigits; j++) + p[ndigits - 1 - j] = q[j ^ 3]; + p += ndigits; + q += nwords * 4; + if (*q) { + q++; /* eat the slash */ + *p++ = ','; /* add a comma */ + } + *p = '\0'; /* terminate the string */ + } + + /* + * Now _if_ this key matches, we'll enter it in the new + * format. If not, we'll assume something odd went + * wrong, and hyper-cautiously do nothing. + */ + if (!strcmp(otherstr, key)) + RegSetValueExA(rkey, regname, 0, REG_SZ, otherstr, + strlen(otherstr) + 1); + } + } + + RegCloseKey(rkey); + + compare = strcmp(otherstr, key); + + sfree(otherstr); + sfree(regname); + + if (ret == ERROR_MORE_DATA || + (ret == ERROR_SUCCESS && type == REG_SZ && compare)) + return 2; /* key is different in registry */ + else if (ret != ERROR_SUCCESS || type != REG_SZ) + return 1; /* key does not exist in registry */ + else + return 0; /* key matched OK in registry */ +} + +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + char *regname; + HKEY rkey; + + regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); + + hostkey_regname(regname, hostname, port, keytype); + + if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) == ERROR_SUCCESS) { + RegSetValueEx(rkey, regname, 0, REG_SZ, key, strlen(key) + 1); + RegCloseKey(rkey); + } /* else key does not exist in registry */ + + sfree(regname); +} + +/* + * Open (or delete) the random seed file. + */ +enum { DEL, OPEN_R, OPEN_W }; +static int try_random_seed(char const *path, int action, HANDLE *ret) +{ + if (action == DEL) { + remove(path); + *ret = INVALID_HANDLE_VALUE; + return FALSE; /* so we'll do the next ones too */ + } + + *ret = CreateFile(path, + action == OPEN_W ? GENERIC_WRITE : GENERIC_READ, + action == OPEN_W ? 0 : (FILE_SHARE_READ | + FILE_SHARE_WRITE), + NULL, + action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING, + action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0, + NULL); + + return (*ret != INVALID_HANDLE_VALUE); +} + +static HANDLE access_random_seed(int action) +{ + HKEY rkey; + DWORD type, size; + HANDLE rethandle; + char seedpath[2 * MAX_PATH + 10] = "\0"; + + /* + * Iterate over a selection of possible random seed paths until + * we find one that works. + * + * We do this iteration separately for reading and writing, + * meaning that we will automatically migrate random seed files + * if a better location becomes available (by reading from the + * best location in which we actually find one, and then + * writing to the best location in which we can _create_ one). + */ + + /* + * First, try the location specified by the user in the + * Registry, if any. + */ + size = sizeof(seedpath); + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == + ERROR_SUCCESS) { + int ret = RegQueryValueEx(rkey, "RandSeedFile", + 0, &type, seedpath, &size); + if (ret != ERROR_SUCCESS || type != REG_SZ) + seedpath[0] = '\0'; + RegCloseKey(rkey); + + if (*seedpath && try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + + /* + * Next, try the user's local Application Data directory, + * followed by their non-local one. This is found using the + * SHGetFolderPath function, which won't be present on all + * versions of Windows. + */ + if (!tried_shgetfolderpath) { + /* This is likely only to bear fruit on systems with IE5+ + * installed, or WinMe/2K+. There is some faffing with + * SHFOLDER.DLL we could do to try to find an equivalent + * on older versions of Windows if we cared enough. + * However, the invocation below requires IE5+ anyway, + * so stuff that. */ + shell32_module = load_system32_dll("shell32.dll"); + GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA); + tried_shgetfolderpath = TRUE; + } + if (p_SHGetFolderPathA) { + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, seedpath))) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, seedpath))) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + } + + /* + * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the + * user's home directory. + */ + { + int len, ret; + + len = + GetEnvironmentVariable("HOMEDRIVE", seedpath, + sizeof(seedpath)); + ret = + GetEnvironmentVariable("HOMEPATH", seedpath + len, + sizeof(seedpath) - len); + if (ret != 0) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + } + + /* + * And finally, fall back to C:\WINDOWS. + */ + GetWindowsDirectory(seedpath, sizeof(seedpath)); + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + + /* + * If even that failed, give up. + */ + return INVALID_HANDLE_VALUE; +} + +void read_random_seed(noise_consumer_t consumer) +{ + HANDLE seedf = access_random_seed(OPEN_R); + + if (seedf != INVALID_HANDLE_VALUE) { + while (1) { + char buf[1024]; + DWORD len; + + if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len) + consumer(buf, len); + else + break; + } + CloseHandle(seedf); + } +} + +void write_random_seed(void *data, int len) +{ + HANDLE seedf = access_random_seed(OPEN_W); + + if (seedf != INVALID_HANDLE_VALUE) { + DWORD lenwritten; + + WriteFile(seedf, data, len, &lenwritten, NULL); + CloseHandle(seedf); + } +} + +/* + * Internal function supporting the jump list registry code. All the + * functions to add, remove and read the list have substantially + * similar content, so this is a generalisation of all of them which + * transforms the list in the registry by prepending 'add' (if + * non-null), removing 'rem' from what's left (if non-null), and + * returning the resulting concatenated list of strings in 'out' (if + * non-null). + */ +static int transform_jumplist_registry + (const char *add, const char *rem, char **out) +{ + int ret; + HKEY pjumplist_key, psettings_tmp; + DWORD type; + int value_length; + char *old_value, *new_value; + char *piterator_old, *piterator_new, *piterator_tmp; + + ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL, + REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL, + &pjumplist_key, NULL); + if (ret != ERROR_SUCCESS) { + return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; + } + + /* Get current list of saved sessions in the registry. */ + value_length = 200; + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + old_value, &value_length); + /* When the passed buffer is too small, ERROR_MORE_DATA is + * returned and the required size is returned in the length + * argument. */ + if (ret == ERROR_MORE_DATA) { + sfree(old_value); + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + old_value, &value_length); + } + + if (ret == ERROR_FILE_NOT_FOUND) { + /* Value doesn't exist yet. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } else if (ret != ERROR_SUCCESS) { + /* Some non-recoverable error occurred. */ + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } else if (type != REG_MULTI_SZ) { + /* The value present in the registry has the wrong type: we + * try to delete it and start from an empty value. */ + ret = RegDeleteValue(pjumplist_key, reg_jumplist_value); + if (ret != ERROR_SUCCESS) { + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } + + *old_value = '\0'; + *(old_value + 1) = '\0'; + } + + /* Check validity of registry data: REG_MULTI_SZ value must end + * with \0\0. */ + piterator_tmp = old_value; + while (((piterator_tmp - old_value) < (value_length - 1)) && + !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) { + ++piterator_tmp; + } + + if ((piterator_tmp - old_value) >= (value_length-1)) { + /* Invalid value. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } + + /* + * Modify the list, if we're modifying. + */ + if (add || rem) { + /* Walk through the existing list and construct the new list of + * saved sessions. */ + new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char); + piterator_new = new_value; + piterator_old = old_value; + + /* First add the new item to the beginning of the list. */ + if (add) { + strcpy(piterator_new, add); + piterator_new += strlen(piterator_new) + 1; + } + /* Now add the existing list, taking care to leave out the removed + * item, if it was already in the existing list. */ + while (*piterator_old != '\0') { + if (!rem || strcmp(piterator_old, rem) != 0) { + /* Check if this is a valid session, otherwise don't add. */ + psettings_tmp = open_settings_r(piterator_old); + if (psettings_tmp != NULL) { + close_settings_r(psettings_tmp); + strcpy(piterator_new, piterator_old); + piterator_new += strlen(piterator_new) + 1; + } + } + piterator_old += strlen(piterator_old) + 1; + } + *piterator_new = '\0'; + ++piterator_new; + + /* Save the new list to the registry. */ + ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ, + new_value, piterator_new - new_value); + + sfree(old_value); + old_value = new_value; + } else + ret = ERROR_SUCCESS; + + /* + * Either return or free the result. + */ + if (out) + *out = old_value; + else + sfree(old_value); + + /* Clean up and return. */ + RegCloseKey(pjumplist_key); + + if (ret != ERROR_SUCCESS) { + return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; + } else { + return JUMPLISTREG_OK; + } +} + +/* Adds a new entry to the jumplist entries in the registry. */ +int add_to_jumplist_registry(const char *item) +{ + return transform_jumplist_registry(item, item, NULL); +} + +/* Removes an item from the jumplist entries in the registry. */ +int remove_from_jumplist_registry(const char *item) +{ + return transform_jumplist_registry(NULL, item, NULL); +} + +/* Returns the jumplist entries from the registry. Caller must free + * the returned pointer. */ +char *get_jumplist_registry_entries (void) +{ + char *list_value; + + if (transform_jumplist_registry(NULL,NULL,&list_value) != ERROR_SUCCESS) { + list_value = snewn(2, char); + *list_value = '\0'; + *(list_value + 1) = '\0'; + } + return list_value; +} + +/* + * Recursively delete a registry key and everything under it. + */ +static void registry_recursive_remove(HKEY key) +{ + DWORD i; + char name[MAX_PATH + 1]; + HKEY subkey; + + i = 0; + while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) { + if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) { + registry_recursive_remove(subkey); + RegCloseKey(subkey); + } + RegDeleteKey(key, name); + } +} + +void cleanup_all(void) +{ + HKEY key; + int ret; + char name[MAX_PATH + 1]; + + /* ------------------------------------------------------------ + * Wipe out the random seed file, in all of its possible + * locations. + */ + access_random_seed(DEL); + + /* ------------------------------------------------------------ + * Ask Windows to delete any jump list information associated + * with this installation of PuTTY. + */ + clear_jumplist(); + + /* ------------------------------------------------------------ + * Destroy all registry information associated with PuTTY. + */ + + /* + * Open the main PuTTY registry key and remove everything in it. + */ + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == + ERROR_SUCCESS) { + registry_recursive_remove(key); + RegCloseKey(key); + } + /* + * Now open the parent key and remove the PuTTY main key. Once + * we've done that, see if the parent key has any other + * children. + */ + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT, + &key) == ERROR_SUCCESS) { + RegDeleteKey(key, PUTTY_REG_PARENT_CHILD); + ret = RegEnumKey(key, 0, name, sizeof(name)); + RegCloseKey(key); + /* + * If the parent key had no other children, we must delete + * it in its turn. That means opening the _grandparent_ + * key. + */ + if (ret != ERROR_SUCCESS) { + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT, + &key) == ERROR_SUCCESS) { + RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD); + RegCloseKey(key); + } + } + } + /* + * Now we're done. + */ +} + +#endif // #if 0 diff --git a/puttysrc/logging.c b/puttysrc/logging.c index 7465480..10ef833 100644 --- a/puttysrc/logging.c +++ b/puttysrc/logging.c @@ -159,7 +159,7 @@ void logfopen(void *handle) ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */ if (ctx->lgfp) { fclose(ctx->lgfp); - if (ctx->cfg.logxfovr != LGXF_ASK) { + if (ctx->cfg.logxfovr != LGXF_ASK && ctx->cfg.logxfovr != LGXF_ASK__) { mode = ((ctx->cfg.logxfovr == LGXF_OVR) ? 2 : 1); } else mode = askappend(ctx->frontend, ctx->currlogfilename, diff --git a/puttysrc/putty.h b/puttysrc/putty.h index e1b123e..53ff57f 100644 --- a/puttysrc/putty.h +++ b/puttysrc/putty.h @@ -162,6 +162,7 @@ struct unicode_data { #define LGXF_OVR 1 /* existing logfile overwrite */ #define LGXF_APN 0 /* existing logfile append */ #define LGXF_ASK -1 /* existing logfile ask */ +#define LGXF_ASK__ 2 /* existing logfile ask +ve val */ #define LGTYP_NONE 0 /* logmode: no logging */ #define LGTYP_ASCII 1 /* logmode: pure ascii */ #define LGTYP_DEBUG 2 /* logmode: all chars of traffic */ diff --git a/puttysrc/terminal.c b/puttysrc/terminal.c index c976741..e15cc74 100644 --- a/puttysrc/terminal.c +++ b/puttysrc/terminal.c @@ -4511,7 +4511,7 @@ static void term_out(Terminal *term) term_print_flush(term); if (term->cfg.logflush) - logflush(term->logctx); + logflush(term->logctx); } /* diff --git a/terminalmisc.cpp b/terminalmisc.cpp index a226ac5..7664027 100644 --- a/terminalmisc.cpp +++ b/terminalmisc.cpp @@ -180,15 +180,6 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, { } -Backend *backend_from_proto(int proto) -{ - Backend **p; - for (p = backends; *p != NULL; p++) - if ((*p)->protocol == proto) - return *p; - return NULL; -} - /*void logevent(const char *s) { return logevent(NULL, s); // return logevent(ssh->frontend, s);