みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

Cプログラムでの標準入力からの認証の仕方について

現在、pop3クライアントを作っており、そのユーザ認証の箇所で認証がうまくいかず困っています。

初めての試みなので、参考書などをいろいろ拝見しました。

私が参考にしたのは、繋げるサーバやユーザ名、そしてパスワードを初めからプログラムに書き込んで、あとはプログラムを実行すると勝手にサーバ接続から認証まで勝手に行ってくれるものでした。
それには、コマンドを入力する際のプログラムは
#define BUFF_SIZE 512;

char strBuf[BUFF_SIZE];
char *user = "test";

void user_and_pass(void){
snprintf(strBuf,sizeof(strBuf),"USER %s\r\n",user);
send(server,strBuf,strlen(strBuf),0);

のような感じ(パスワードも同様)のプログラムが載っており、その通りプログラムしたところ、問題なく実行されました。

しかし、私が作りたいのはキーボード入力からの認証なので、以下のような
char *user;

void user_and_pass(void){
gets(strBuf);
user = strBuf;
snprintf(strBuf,sizeof(strBuf),"USER %s\r\n",user);
send(server,strBuf,strlen(strBuf),0);


とかえたところ、サーバ接続はこれと同じ入力方法でサーバ名を入力したところうまくいったのに、ユーザ名とパスワードについてはPASSコマンド入力後、「Bad login」と表示されエラーが出てしまいました。
サーバ接続は問題なく行っているのに、なぜユーザ認証はできないのでしょうか?

また、gets関数は危ないと読んだのでfgetsを用いて、
fgets(strBuf,sizeof(strBuf),stdin);
に変更したところ、今度はサーバ接続までもがうまくいかず、正しいサーバ名を入力したにも関わらず「サーバが見つからない」と言われます。printfで確認したところ、ちゃんと入力した文字列は格納されているのですが・・・。

2つも質問して申し訳ありませんが、よろしくお願いします。

投稿日時 - 2007-01-22 01:02:15

QNo.2685610

すぐに回答ほしいです

質問者が選んだベストアンサー

★また間違いを発見してしまいました。
・『char user[BUFF_SIZE];』の行は『char *user;』というポインタ宣言にします。
・上記の宣言でよくエラーがでませんでしたね。不思議だ?

本題:
・『gets』関数と『fgets』関数は改行コードの扱いが微妙に違っています。
・『gets』関数は改行コードを含まない文字列を取得します。
・『fgets』関数は改行コードを含む文字列を取得します。
・このことから次のように文字列が作成されています。
・『gets』関数は『USER xxxxx\r\n』←『\r\n』
・『fgets』関数は『USER xxxxx\n\r\n』←『\n\r\n』
・『xxxxx』は入力されたサーバ名です。
・上記は改行の扱いが違ったために『\r\n』と『\n\r\n』の違いがでて、サーバー名
 が正しくても改行コードの表現が正しくないため『見つからない』と表示されると
 思います。
・下に正しい fgets 版を載せます。

getsの場合:
user = gets( strGet );
snprintf( strBuf, sizeof(strBuf), "USER %s\r\n", user );

fgetsの場合:
char *find; ←宣言に追加

user = fgets( strGet, sizeof(strGet), stdin );
find = (user + strlen(user) - 1); ←文字列の最後のポインタ

if ( *find == '\n' ){
 *find = '\0'; ←『\n』を削除
}
snprintf( strBuf, sizeof(strBuf), "USER %s\r\n", user );

最後に:
・上記の gets と fgets の違いは『snprintf』の書式指定文字列で作られる文字列の
 改行コード部分『\r\n』と『\n\r\n』が違ったため、fgets 関数の場合は改行コード
 『\n』を削除して gets 関数と同じ文字列に変換します。その後は同じです。
・上記の新しい fgets サンプルで多分もう『サーバーが見つからない』というメッセージ
 にはならないと思います。
・以上。おわり。→結果報告をお願いします。

投稿日時 - 2007-01-22 17:49:34

補足

すみません、お礼で報告すればいいのですが、念のため補足で。

Oh-orangeさんの書かれたとおりに書き直しましたら、ちゃんとプログラムが実行されました。ありがとうございます。

それとすみません。char *user; はすでに宣言済みということで前の補足を書いてしまいました。わかりづらくて申し訳ありませんでした。

なるほど、fgets()のほうだと入力した文字列の後ろに[\n]がこっそり入ってしまっていたのですね。

それと最後に、Oh-orangeさんの書いていただいた部分
find = (user + strlen(user) - 1); ←文字列の最後のポインタ

if ( *find == '\n' ){
 *find = '\0'; ←『\n』を削除
}
のところを、自分なりに考えて、
if(user[strlen(user)-1]=='\n'){
user[strlen(user)-1] = '\0';
}
としてみたところ、これもプログラムが動いたのですが、この書き方には問題はないのでしょうか?

これで最後です。何度も本当に申し訳ありませんが、どうしても納得したいので、どうかよろしくお願いします。

投稿日時 - 2007-01-22 22:24:07

お礼

補足にある質問も解決しましたので、お礼のほうを述べさせてもらおうと思います。

ポインタのことにしても、gets()とfgets()のことにしても、わざわざ詳しく書いて下さってありがとうございました。

ポインタについて理解の浅かった私にとっては、本当に助かりました。再度、本当にありがとうございました。

投稿日時 - 2007-01-23 02:34:58

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(5)

ANo.5

>if(user[strlen(user)-1]=='\n'){
>user[strlen(user)-1] = '\0';
>}

ちょっとだけ問題あり。
strlen(user)が0だったら、自分で用意したバッファじゃない
エリアの参照ですね。

境界条件もチェックの対象です。

もう一つ苦言を呈しますが、getsとfgetsで検索すれば
http://www.linux.or.jp/JM/html/LDP_man-pages/man3/fgets.3.html
は引っかかる筈。

この中で
>fgets() は stream から最大で size - 1 個の文字を読み込み、 s が指すバッファに格納する。読み込みは EOF または改行文字を読み込んだ後で停止する。読み込まれた改行文字はバッファに格納される。 '\0' 文字が一つバッファの中の最後の文字の後に書き込まれる。

は、見つかる筈だし、デバッグもprintfで確認時に【】で%sを括るとか16進ダンプするとかしてれば今回のバグくらい見つかる筈です。

投稿日時 - 2007-01-23 01:20:05

お礼

なるほど、確かにuserの長さが0だと問題ありですね。

t_nojiriさんの言うとおり、もっと粘り強く調べるべきでしたのですが、時間の関係でここに質問させていただきました。

とはいえ、t_nojiriさんの示したページもすでに何度も読んでいたにも関わらず違いに気づかず、しかもポインタについて軽く考えていた自分が情けないです。

いろいろご面倒をおかけしました。本当にありがとうございます。

投稿日時 - 2007-01-23 02:30:43

ANo.3

★回答者 No.1、No.2 さんと同じです。
・『snprintf』関数で受け取るバッファと、参照するバッファが重なっているため、
 文字列を繰り返してのコピーが発生します。実行時に予期せぬ動作をします。
・よって正しい文字列を作成させるバッファ指定は2つのバッファを用意することです。
・具体的には下のサンプルをどうぞ。

サンプル:
void user_and_pass( void )
{
 char strBuf[BUFF_SIZE];
 char strGet[BUFF_SIZE];
 
 fgets( strGet, sizeof(strGet), stdin ); ←gets( strGet );と等価(fgetsの方が安全)
 snprintf( strBuf, sizeof(strBuf), "USER %s\r\n", strGet );
 send( server, strBuf, strlen(strBuf), 0 );
 …
}

最後に:
・『user_and_pass』関数の内部でバッファを宣言してもよい。
・以上。おわり。

投稿日時 - 2007-01-22 06:54:31

補足

詳しい説明ありがとうございます。

回答くださった皆さんの返事を何度も読んで、やっと自分がどれだけ間違えているのか気づきました。

そこで、皆さんの返事を参考にして書き換えたところ、
まず、*userに領域を与え、そしてgets()で入力する文字列を格納する新しい配列を以下のように加え、書き換えました。

char user[BUFF_SIZE];
char strGet[BUFF_SIZE];

user = gets(strGet);
snprintf(strBuf,sizeof(strBuf),"USER %s\r\n",user);

すると、今度は「Bad login」と表示されることなく、無事ログインすることができました。

しかし、まだわからないところがありまして、gets(strGet);の部分をfgets(strGet,sizeof(strGet),stdin);と書き換えますと、以前としてサーバ名を入力した時点で、正しく入力しているにも関わらず「サーバが見つからない」という返事が返ってきてしまいます。

いろいろ調べてもget()とfgets()はバッファオーバーフローが起きるか起きないかの違いだけで特に変わりないということしかわからないのですが、なぜうまくいかないのでしょうか?

もし、まだ私のプログラムが間違っていましたらすみません。

投稿日時 - 2007-01-22 12:21:57

ANo.2

がると申します。
ちと全体を拝見していないのですが。
もし、書かれたまんまのコードだと仮定いたしますと。
何はともあれ

char *user;

の部分がバッファオーバフローしてます。
mallocという関数で動的に領域を指定する、ないし

$define USER_MAX_SIZE 適当な数値
char user[USER_MAX_SIZE];

などで固定的に領域をきちんと確保されることをまずはお勧めいたします。

投稿日時 - 2007-01-22 01:16:42

お礼

確かに*userに配列を持たせないと、普通にバッファオーバーフローを起こしてしまいますね。

とはいえ、今でこそ理解できるものの、質問当初はさっぱり理解できていなかったので助かりました。

本当にありがとうございました。

投稿日時 - 2007-01-23 02:37:47

ANo.1

あのー、strBufに文字列取ってきて
>user = strBuf;
>snprintf(strBuf,sizeof(strBuf),"USER %s\r\n",user);
こんな事したら、コピーするバッファ書き潰してるんです。

gets();は、新たに文字配列等用意して、そこに代入しましょう。

まあ、デバッグすればすぐ判る筈ですが。

投稿日時 - 2007-01-22 01:14:21

あなたにオススメの質問